[前][次][番号順一覧][スレッド一覧]

ruby-changes:54497

From: normal <ko1@a...>
Date: Fri, 4 Jan 2019 22:14:15 +0900 (JST)
Subject: [ruby-changes:54497] normal:r66712 (trunk): introduce rb_nogvl C-API to mark ubf as async-signal-safe

normal	2019-01-04 22:14:11 +0900 (Fri, 04 Jan 2019)

  New Revision: 66712

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=66712

  Log:
    introduce rb_nogvl C-API to mark ubf as async-signal-safe
    
    zlib and bignum both contain unblocking functions which are
    async-signal-safe and do not require spawning additional
    threads.
    
    We can execute those functions directly in signal handlers
    without incurring overhead of extra threads, so provide C-API
    users the ability to deal with that.  Other C-API users may
    have similar need.
    
    This flexible API can supercede existing uses of
    rb_thread_call_without_gvl and rb_thread_call_without_gvl2 by
    introducing a flags argument to control behavior.
    
    Note: this API is NOT finalized.  It needs approval from other
    committers.  I prefer shorter name than previous
    rb_thread_call_without_gvl* functions because my eyes requires
    big fonts.
    
    [Bug #15499]

  Added files:
    trunk/test/-ext-/gvl/test_ubf_async_safe.rb
  Modified files:
    trunk/bignum.c
    trunk/ext/-test-/gvl/call_without_gvl/call_without_gvl.c
    trunk/ext/zlib/zlib.c
    trunk/include/ruby/thread.h
    trunk/thread.c
    trunk/thread_pthread.c
    trunk/vm_core.h
Index: ext/zlib/zlib.c
===================================================================
--- ext/zlib/zlib.c	(revision 66711)
+++ ext/zlib/zlib.c	(revision 66712)
@@ -1011,6 +1011,7 @@ zstream_run_func(void *ptr) https://github.com/ruby/ruby/blob/trunk/ext/zlib/zlib.c#L1011
 
 /*
  * There is no safe way to interrupt z->run->func().
+ * async-signal-safe
  */
 static void
 zstream_unblock_func(void *ptr)
@@ -1053,8 +1054,9 @@ zstream_run(struct zstream *z, Bytef *sr https://github.com/ruby/ruby/blob/trunk/ext/zlib/zlib.c#L1054
     }
 
 loop:
-    err = (int)(VALUE)rb_thread_call_without_gvl(zstream_run_func, (void *)&args,
-						 zstream_unblock_func, (void *)&args);
+    err = (int)(VALUE)rb_nogvl(zstream_run_func, (void *)&args,
+                               zstream_unblock_func, (void *)&args,
+                               RB_NOGVL_UBF_ASYNC_SAFE);
 
     if (flush != Z_FINISH && err == Z_BUF_ERROR
 	    && z->stream.avail_out > 0) {
Index: ext/-test-/gvl/call_without_gvl/call_without_gvl.c
===================================================================
--- ext/-test-/gvl/call_without_gvl/call_without_gvl.c	(revision 66711)
+++ ext/-test-/gvl/call_without_gvl/call_without_gvl.c	(revision 66712)
@@ -27,8 +27,49 @@ thread_runnable_sleep(VALUE thread, VALU https://github.com/ruby/ruby/blob/trunk/ext/-test-/gvl/call_without_gvl/call_without_gvl.c#L27
     return thread;
 }
 
+struct loop_ctl {
+    int notify_fd;
+    volatile int stop;
+};
+
+static void *
+do_loop(void *p)
+{
+    struct loop_ctl *ctl = p;
+
+    /* tell the waiting process they can interrupt us, now */
+    write(ctl->notify_fd, "", 1);
+
+    while (!ctl->stop) {
+        struct timeval tv = { 0, 10000 };
+        select(0, NULL, NULL, NULL, &tv);
+    }
+    return 0;
+}
+
+static void
+stop_set(void *p)
+{
+    struct loop_ctl *ctl = p;
+
+    ctl->stop = 1;
+}
+
+static VALUE
+thread_ubf_async_safe(VALUE thread, VALUE notify_fd)
+{
+    struct loop_ctl ctl;
+
+    ctl.notify_fd = NUM2INT(notify_fd);
+    ctl.stop = 0;
+
+    rb_nogvl(do_loop, &ctl, stop_set, &ctl, RB_NOGVL_UBF_ASYNC_SAFE);
+    return thread;
+}
+
 void
 Init_call_without_gvl(void)
 {
     rb_define_method(rb_cThread, "__runnable_sleep__", thread_runnable_sleep, 1);
+    rb_define_method(rb_cThread, "__ubf_async_safe__", thread_ubf_async_safe, 1);
 }
Index: include/ruby/thread.h
===================================================================
--- include/ruby/thread.h	(revision 66711)
+++ include/ruby/thread.h	(revision 66712)
@@ -21,6 +21,10 @@ extern "C" { https://github.com/ruby/ruby/blob/trunk/include/ruby/thread.h#L21
 
 #include "ruby/intern.h"
 
+/* flags for rb_nogvl */
+#define RB_NOGVL_INTR_FAIL       (0x1)
+#define RB_NOGVL_UBF_ASYNC_SAFE  (0x2)
+
 RUBY_SYMBOL_EXPORT_BEGIN
 
 void *rb_thread_call_with_gvl(void *(*func)(void *), void *data1);
@@ -30,6 +34,14 @@ void *rb_thread_call_without_gvl(void *( https://github.com/ruby/ruby/blob/trunk/include/ruby/thread.h#L34
 void *rb_thread_call_without_gvl2(void *(*func)(void *), void *data1,
 				  rb_unblock_function_t *ubf, void *data2);
 
+/*
+ * XXX: unstable/unapproved - out-of-tree code should NOT not depend
+ * on this until it hits Ruby 2.6.1
+ */
+void *rb_nogvl(void *(*func)(void *), void *data1,
+               rb_unblock_function_t *ubf, void *data2,
+               int flags);
+
 #define RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS_AFTER 0x01
 #define RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS_
 
Index: thread_pthread.c
===================================================================
--- thread_pthread.c	(revision 66711)
+++ thread_pthread.c	(revision 66712)
@@ -1542,6 +1542,11 @@ rb_thread_wakeup_timer_thread(int sig) https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L1542
             if (ec) {
                 RUBY_VM_SET_TRAP_INTERRUPT(ec);
                 ubf_timer_arm(current);
+
+                /* some ubfs can interrupt single-threaded process directly */
+                if (vm->ubf_async_safe && mth->unblock.func) {
+                    (mth->unblock.func)(mth->unblock.arg);
+                }
             }
         }
     }
Index: test/-ext-/gvl/test_ubf_async_safe.rb
===================================================================
--- test/-ext-/gvl/test_ubf_async_safe.rb	(nonexistent)
+++ test/-ext-/gvl/test_ubf_async_safe.rb	(revision 66712)
@@ -0,0 +1,20 @@ https://github.com/ruby/ruby/blob/trunk/test/-ext-/gvl/test_ubf_async_safe.rb#L1
+# frozen_string_literal: true
+class TestUbfAsyncSafe < Test::Unit::TestCase
+  def test_ubf_async_safe
+    skip 'need fork for single-threaded test' unless Process.respond_to?(:fork)
+    IO.pipe do |r, w|
+      pid = fork do
+        require '-test-/gvl/call_without_gvl'
+        r.close
+        trap(:INT) { exit!(0) }
+        Thread.current.__ubf_async_safe__(w.fileno)
+        exit!(1)
+      end
+      w.close
+      assert IO.select([r], nil, nil, 30), 'child did not become ready'
+      Process.kill(:INT, pid)
+      _, st = Process.waitpid2(pid)
+      assert_predicate st, :success?, ':INT signal triggered exit'
+    end
+  end
+end
Index: vm_core.h
===================================================================
--- vm_core.h	(revision 66711)
+++ vm_core.h	(revision 66712)
@@ -608,6 +608,9 @@ typedef struct rb_vm_struct { https://github.com/ruby/ruby/blob/trunk/vm_core.h#L608
     VALUE thgroup_default;
     int living_thread_num;
 
+    /* set in single-threaded processes only: */
+    volatile int ubf_async_safe;
+
     unsigned int running: 1;
     unsigned int thread_abort_on_exception: 1;
     unsigned int thread_report_on_exception: 1;
Index: thread.c
===================================================================
--- thread.c	(revision 66711)
+++ thread.c	(revision 66712)
@@ -1421,9 +1421,10 @@ blocking_region_end(rb_thread_t *th, str https://github.com/ruby/ruby/blob/trunk/thread.c#L1421
     }
 }
 
-static void *
-call_without_gvl(void *(*func)(void *), void *data1,
-		 rb_unblock_function_t *ubf, void *data2, int fail_if_interrupted)
+void *
+rb_nogvl(void *(*func)(void *), void *data1,
+         rb_unblock_function_t *ubf, void *data2,
+         int flags)
 {
     void *val = 0;
     rb_execution_context_t *ec = GET_EC();
@@ -1436,15 +1437,22 @@ call_without_gvl(void *(*func)(void *), https://github.com/ruby/ruby/blob/trunk/thread.c#L1437
 	data2 = th;
     }
     else if (ubf && vm_living_thread_num(th->vm) == 1) {
-        ubf_th = rb_thread_start_unblock_thread();
+        if (RB_NOGVL_UBF_ASYNC_SAFE) {
+            th->vm->ubf_async_safe = 1;
+        }
+        else {
+            ubf_th = rb_thread_start_unblock_thread();
+        }
     }
 
     BLOCKING_REGION(th, {
 	val = func(data1);
 	saved_errno = errno;
-    }, ubf, data2, fail_if_interrupted);
+    }, ubf, data2, flags & RB_NOGVL_INTR_FAIL);
+
+    th->vm->ubf_async_safe = 0;
 
-    if (!fail_if_interrupted) {
+    if ((flags & RB_NOGVL_INTR_FAIL) == 0) {
 	RUBY_VM_CHECK_INTS_BLOCKING(ec);
     }
 
@@ -1546,14 +1554,14 @@ void * https://github.com/ruby/ruby/blob/trunk/thread.c#L1554
 rb_thread_call_without_gvl2(void *(*func)(void *), void *data1,
 			    rb_unblock_function_t *ubf, void *data2)
 {
-    return call_without_gvl(func, data1, ubf, data2, TRUE);
+    return rb_nogvl(func, data1, ubf, data2, RB_NOGVL_INTR_FAIL);
 }
 
 void *
 rb_thread_call_without_gvl(void *(*func)(void *data), void *data1,
 			    rb_unblock_function_t *ubf, void *data2)
 {
-    return call_without_gvl(func, data1, ubf, data2, FALSE);
+    return rb_nogvl(func, data1, ubf, data2, 0);
 }
 
 VALUE
Index: bignum.c
===================================================================
--- bignum.c	(revision 66711)
+++ bignum.c	(revision 66712)
@@ -2572,6 +2572,7 @@ bigdivrem1(void *ptr) https://github.com/ruby/ruby/blob/trunk/bignum.c#L2572
     return 0;
 }
 
+/* async-signal-safe */
 static void
 rb_big_stop(void *ptr)
 {
@@ -2636,7 +2637,7 @@ bigdivrem_restoring(BDIGIT *zds, size_t https://github.com/ruby/ruby/blob/trunk/bignum.c#L2637
     if (bds.zn > 10000 || bds.yn > 10000) {
       retry:
 	bds.stop = Qfalse;
-	rb_thread_call_without_gvl(bigdivrem1, &bds, rb_big_stop, &bds);
+	rb_nogvl(bigdivrem1, &bds, rb_big_stop, &bds, RB_NOGVL_UBF_ASYNC_SAFE);
 
 	if (bds.stop == Qtrue) {
 	    /* execute trap handler, but exception was not raised. */

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]