mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* include/ruby/thread.h (rb_thread_call_without_gvl2): change
meaning of function. This function is called with same parameters of `rb_thread_call_without_gvl()'. However, if interrupts are detected, when return immediately. * thread.c: implement `rb_thread_call_without_gvl2()'. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37938 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
f5dc27aa77
commit
9d0de48e66
3 changed files with 114 additions and 89 deletions
10
ChangeLog
10
ChangeLog
|
@ -1,3 +1,13 @@
|
||||||
|
Wed Nov 28 21:58:47 2012 Koichi Sasada <ko1@atdot.net>
|
||||||
|
|
||||||
|
* include/ruby/thread.h (rb_thread_call_without_gvl2): change
|
||||||
|
meaning of function.
|
||||||
|
This function is called with same parameters of
|
||||||
|
`rb_thread_call_without_gvl()'.
|
||||||
|
However, if interrupts are detected, when return immediately.
|
||||||
|
|
||||||
|
* thread.c: implement `rb_thread_call_without_gvl2()'.
|
||||||
|
|
||||||
Wed Nov 28 21:31:21 2012 Masaya Tarui <tarui@ruby-lang.org>
|
Wed Nov 28 21:31:21 2012 Masaya Tarui <tarui@ruby-lang.org>
|
||||||
|
|
||||||
* thread.c (thread_join_sleep): check spurious wakeup by itself for
|
* thread.c (thread_join_sleep): check spurious wakeup by itself for
|
||||||
|
|
|
@ -26,12 +26,14 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void *rb_thread_call_with_gvl(void *(*func)(void *), void *data1);
|
void *rb_thread_call_with_gvl(void *(*func)(void *), void *data1);
|
||||||
|
|
||||||
void *rb_thread_call_without_gvl(void *(*func)(void *), void *data1,
|
void *rb_thread_call_without_gvl(void *(*func)(void *), void *data1,
|
||||||
rb_unblock_function_t *ubf, void *data2);
|
rb_unblock_function_t *ubf, void *data2);
|
||||||
void *rb_thread_call_without_gvl2(void *(*func)(void *, VALUE *), void *data1,
|
void *rb_thread_call_without_gvl2(void *(*func)(void *), void *data1,
|
||||||
rb_unblock_function_t *ubf, void *data2);
|
rb_unblock_function_t *ubf, void *data2);
|
||||||
|
|
||||||
#define RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS 0x01
|
#define RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS_AFTER 0x01
|
||||||
|
#define RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS_
|
||||||
|
|
||||||
#if defined __GNUC__ && __GNUC__ >= 4
|
#if defined __GNUC__ && __GNUC__ >= 4
|
||||||
#pragma GCC visibility pop
|
#pragma GCC visibility pop
|
||||||
|
|
181
thread.c
181
thread.c
|
@ -91,10 +91,12 @@ struct rb_blocking_region_buffer {
|
||||||
struct rb_unblock_callback oldubf;
|
struct rb_unblock_callback oldubf;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg,
|
static int set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg,
|
||||||
struct rb_unblock_callback *old);
|
struct rb_unblock_callback *old, int fail_if_interrupted);
|
||||||
static void reset_unblock_function(rb_thread_t *th, const struct rb_unblock_callback *old);
|
static void reset_unblock_function(rb_thread_t *th, const struct rb_unblock_callback *old);
|
||||||
|
|
||||||
|
static inline int blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region,
|
||||||
|
rb_unblock_function_t *ubf, void *arg, int fail_if_interrupted);
|
||||||
static inline void blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region);
|
static inline void blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region);
|
||||||
|
|
||||||
#define RB_GC_SAVE_MACHINE_CONTEXT(th) \
|
#define RB_GC_SAVE_MACHINE_CONTEXT(th) \
|
||||||
|
@ -113,23 +115,13 @@ static inline void blocking_region_end(rb_thread_t *th, struct rb_blocking_regio
|
||||||
rb_thread_set_current(_th_stored); \
|
rb_thread_set_current(_th_stored); \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
#define blocking_region_begin(th, region, func, arg) \
|
#define BLOCKING_REGION(exec, ubf, ubfarg, fail_if_interrupted) do { \
|
||||||
do { \
|
|
||||||
(region)->prev_status = (th)->status; \
|
|
||||||
set_unblock_function((th), (func), (arg), &(region)->oldubf); \
|
|
||||||
(th)->blocking_region_buffer = (region); \
|
|
||||||
(th)->status = THREAD_STOPPED; \
|
|
||||||
thread_debug("enter blocking region (%p)\n", (void *)(th)); \
|
|
||||||
RB_GC_SAVE_MACHINE_CONTEXT(th); \
|
|
||||||
gvl_release((th)->vm); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define BLOCKING_REGION(exec, ubf, ubfarg) do { \
|
|
||||||
rb_thread_t *__th = GET_THREAD(); \
|
rb_thread_t *__th = GET_THREAD(); \
|
||||||
struct rb_blocking_region_buffer __region; \
|
struct rb_blocking_region_buffer __region; \
|
||||||
blocking_region_begin(__th, &__region, (ubf), (ubfarg)); \
|
if (blocking_region_begin(__th, &__region, (ubf), (ubfarg), fail_if_interrupted)) { \
|
||||||
exec; \
|
exec; \
|
||||||
blocking_region_end(__th, &__region); \
|
blocking_region_end(__th, &__region); \
|
||||||
|
}; \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
#if THREAD_DEBUG
|
#if THREAD_DEBUG
|
||||||
|
@ -260,12 +252,20 @@ rb_thread_lock_destroy(rb_thread_lock_t *lock)
|
||||||
native_mutex_destroy(lock);
|
native_mutex_destroy(lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg,
|
set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg,
|
||||||
struct rb_unblock_callback *old)
|
struct rb_unblock_callback *old, int fail_if_interrupted)
|
||||||
{
|
{
|
||||||
check_ints:
|
check_ints:
|
||||||
RUBY_VM_CHECK_INTS(th); /* check signal or so */
|
if (fail_if_interrupted) {
|
||||||
|
if (RUBY_VM_INTERRUPTED_ANY(th)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RUBY_VM_CHECK_INTS(th);
|
||||||
|
}
|
||||||
|
|
||||||
native_mutex_lock(&th->interrupt_lock);
|
native_mutex_lock(&th->interrupt_lock);
|
||||||
if (RUBY_VM_INTERRUPTED_ANY(th)) {
|
if (RUBY_VM_INTERRUPTED_ANY(th)) {
|
||||||
native_mutex_unlock(&th->interrupt_lock);
|
native_mutex_unlock(&th->interrupt_lock);
|
||||||
|
@ -277,6 +277,8 @@ set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg,
|
||||||
th->unblock.arg = arg;
|
th->unblock.arg = arg;
|
||||||
}
|
}
|
||||||
native_mutex_unlock(&th->interrupt_lock);
|
native_mutex_unlock(&th->interrupt_lock);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1075,6 +1077,24 @@ rb_thread_schedule(void)
|
||||||
|
|
||||||
/* blocking region */
|
/* blocking region */
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region,
|
||||||
|
rb_unblock_function_t *ubf, void *arg, int fail_if_interrupted)
|
||||||
|
{
|
||||||
|
region->prev_status = th->status;
|
||||||
|
if (set_unblock_function(th, ubf, arg, ®ion->oldubf, fail_if_interrupted)) {
|
||||||
|
th->blocking_region_buffer = region;
|
||||||
|
th->status = THREAD_STOPPED;
|
||||||
|
thread_debug("enter blocking region (%p)\n", (void *)th);
|
||||||
|
RB_GC_SAVE_MACHINE_CONTEXT(th);
|
||||||
|
gvl_release(th->vm);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region)
|
blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region)
|
||||||
{
|
{
|
||||||
|
@ -1094,7 +1114,7 @@ rb_thread_blocking_region_begin(void)
|
||||||
{
|
{
|
||||||
rb_thread_t *th = GET_THREAD();
|
rb_thread_t *th = GET_THREAD();
|
||||||
struct rb_blocking_region_buffer *region = ALLOC(struct rb_blocking_region_buffer);
|
struct rb_blocking_region_buffer *region = ALLOC(struct rb_blocking_region_buffer);
|
||||||
blocking_region_begin(th, region, ubf_select, th);
|
blocking_region_begin(th, region, ubf_select, th, FALSE);
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1109,24 +1129,54 @@ rb_thread_blocking_region_end(struct rb_blocking_region_buffer *region)
|
||||||
errno = saved_errno;
|
errno = saved_errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
call_without_gvl(void *(*func)(void *), void *data1,
|
||||||
|
rb_unblock_function_t *ubf, void *data2, int fail_if_interrupted)
|
||||||
|
{
|
||||||
|
void *val = 0;
|
||||||
|
|
||||||
|
rb_thread_t *th = GET_THREAD();
|
||||||
|
int saved_errno = 0;
|
||||||
|
|
||||||
|
th->waiting_fd = -1;
|
||||||
|
if (ubf == RUBY_UBF_IO || ubf == RUBY_UBF_PROCESS) {
|
||||||
|
ubf = ubf_select;
|
||||||
|
data2 = th;
|
||||||
|
}
|
||||||
|
|
||||||
|
BLOCKING_REGION({
|
||||||
|
val = func(data1);
|
||||||
|
saved_errno = errno;
|
||||||
|
}, ubf, data2, fail_if_interrupted);
|
||||||
|
|
||||||
|
if (!fail_if_interrupted) {
|
||||||
|
RUBY_VM_CHECK_INTS_BLOCKING(th);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = saved_errno;
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* rb_thread_call_without_gvl - permit concurrent/parallel execution.
|
* rb_thread_call_without_gvl - permit concurrent/parallel execution.
|
||||||
* rb_thread_call_without_gvl2 - permit concurrent/parallel execution with
|
* rb_thread_call_without_gvl2 - permit concurrent/parallel execution
|
||||||
* optional interrupt checking.
|
* without interrupt proceess.
|
||||||
*
|
*
|
||||||
* rb_thread_call_without_gvl() does:
|
* rb_thread_call_without_gvl() does:
|
||||||
* (1) release GVL.
|
* (1) Check interrupts.
|
||||||
|
* (2) release GVL.
|
||||||
* Other Ruby threads may run in parallel.
|
* Other Ruby threads may run in parallel.
|
||||||
* (2) call func with data1
|
* (3) call func with data1
|
||||||
* (3) acquire GVL.
|
* (4) acquire GVL.
|
||||||
* Other Ruby threads can not run in parallel any more.
|
* Other Ruby threads can not run in parallel any more.
|
||||||
* (4) Check interrupts.
|
* (5) Check interrupts.
|
||||||
*
|
*
|
||||||
* rb_thread_call_without_gvl2() does:
|
* rb_thread_call_without_gvl2() does:
|
||||||
* (1) release GVL.
|
* (1) Check interrupt and return if interrupted.
|
||||||
* (2) call func with data1 and a pointer to the flags.
|
* (2) release GVL.
|
||||||
* (3) acquire GVL.
|
* (3) call func with data1 and a pointer to the flags.
|
||||||
* (4) Check interrupts if (flags & RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS) is 0.
|
* (4) acquire GVL.
|
||||||
*
|
*
|
||||||
* If another thread interrupts this thread (Thread#kill, signal delivery,
|
* If another thread interrupts this thread (Thread#kill, signal delivery,
|
||||||
* VM-shutdown request, and so on), `ubf()' is called (`ubf()' means
|
* VM-shutdown request, and so on), `ubf()' is called (`ubf()' means
|
||||||
|
@ -1144,7 +1194,7 @@ rb_thread_blocking_region_end(struct rb_blocking_region_buffer *region)
|
||||||
* provide proper ubf(), your program will not stop for Control+C or other
|
* provide proper ubf(), your program will not stop for Control+C or other
|
||||||
* shutdown events.
|
* shutdown events.
|
||||||
*
|
*
|
||||||
* "Check interrupts" on above list (4) means that check asynchronous
|
* "Check interrupts" on above list means that check asynchronous
|
||||||
* interrupt events (such as Thread#kill, signal delivery, VM-shutdown
|
* interrupt events (such as Thread#kill, signal delivery, VM-shutdown
|
||||||
* request, and so on) and call corresponding procedures
|
* request, and so on) and call corresponding procedures
|
||||||
* (such as `trap' for signals, raise an exception for Thread#raise).
|
* (such as `trap' for signals, raise an exception for Thread#raise).
|
||||||
|
@ -1161,16 +1211,16 @@ rb_thread_blocking_region_end(struct rb_blocking_region_buffer *region)
|
||||||
* `read_func()' and interrupts are checked. However, if an interrupt occurs
|
* `read_func()' and interrupts are checked. However, if an interrupt occurs
|
||||||
* at (c), after *read* operation is completed, check intterrupts is harmful
|
* at (c), after *read* operation is completed, check intterrupts is harmful
|
||||||
* because it causes irrevocable side-effect, the read data will vanish. To
|
* because it causes irrevocable side-effect, the read data will vanish. To
|
||||||
* avoid such problem, the `read_func()' should be:
|
* avoid such problem, the `read_func()' should be used with
|
||||||
|
* `rb_thread_call_without_gvl2()'.
|
||||||
*
|
*
|
||||||
* read_func(void *data, VALUE *flags) {
|
* If `rb_thread_call_without_gvl2()' detects interrupt, return its execution
|
||||||
* // (a) before read
|
* immediately. This function does not show when the execution was interrupted.
|
||||||
* read(buffer); // (b) reading
|
* For example, there are 4 possible timing (a), (b), (c) and before calling
|
||||||
* // (c) after read
|
* read_func(). You need to record progress of a read_func() and check
|
||||||
* if (read is complete) {
|
* the progress after `rb_thread_call_without_gvl2()'. You may need to call
|
||||||
* *flags |= RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS;
|
* `rb_thread_check_ints()' correctly or your program can not process proper
|
||||||
* }
|
* process such as `trap' and so on.
|
||||||
* }
|
|
||||||
*
|
*
|
||||||
* NOTE: You can not execute most of Ruby C API and touch Ruby
|
* NOTE: You can not execute most of Ruby C API and touch Ruby
|
||||||
* objects in `func()' and `ubf()', including raising an
|
* objects in `func()' and `ubf()', including raising an
|
||||||
|
@ -1194,54 +1244,17 @@ rb_thread_blocking_region_end(struct rb_blocking_region_buffer *region)
|
||||||
* they will work without GVL, and may acquire GVL when GC is needed.
|
* they will work without GVL, and may acquire GVL when GC is needed.
|
||||||
*/
|
*/
|
||||||
void *
|
void *
|
||||||
rb_thread_call_without_gvl2(void *(*func)(void *data, VALUE *flags), void *data1,
|
rb_thread_call_without_gvl2(void *(*func)(void *), void *data1,
|
||||||
rb_unblock_function_t *ubf, void *data2)
|
rb_unblock_function_t *ubf, void *data2)
|
||||||
{
|
{
|
||||||
void *val;
|
return call_without_gvl(func, data1, ubf, data2, TRUE);
|
||||||
rb_thread_t *th = GET_THREAD();
|
|
||||||
int saved_errno = 0;
|
|
||||||
VALUE flags = 0;
|
|
||||||
|
|
||||||
th->waiting_fd = -1;
|
|
||||||
if (ubf == RUBY_UBF_IO || ubf == RUBY_UBF_PROCESS) {
|
|
||||||
ubf = ubf_select;
|
|
||||||
data2 = th;
|
|
||||||
}
|
|
||||||
|
|
||||||
BLOCKING_REGION({
|
|
||||||
val = func(data1, &flags);
|
|
||||||
saved_errno = errno;
|
|
||||||
}, ubf, data2);
|
|
||||||
|
|
||||||
if ((flags & RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS) == 0) {
|
|
||||||
RUBY_VM_CHECK_INTS_BLOCKING(th);
|
|
||||||
}
|
|
||||||
|
|
||||||
errno = saved_errno;
|
|
||||||
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct without_gvl_wrapper_arg {
|
|
||||||
void *(*func)(void *data);
|
|
||||||
void *data;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void *
|
|
||||||
without_gvl_wrapper(void *data, VALUE *flags)
|
|
||||||
{
|
|
||||||
struct without_gvl_wrapper_arg *arg = (struct without_gvl_wrapper_arg*)data;
|
|
||||||
return arg->func(arg->data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void *
|
void *
|
||||||
rb_thread_call_without_gvl(void *(*func)(void *data), void *data1,
|
rb_thread_call_without_gvl(void *(*func)(void *data), void *data1,
|
||||||
rb_unblock_function_t *ubf, void *data2)
|
rb_unblock_function_t *ubf, void *data2)
|
||||||
{
|
{
|
||||||
struct without_gvl_wrapper_arg arg;
|
return call_without_gvl(func, data1, ubf, data2, FALSE);
|
||||||
arg.func = func;
|
|
||||||
arg.data = data1;
|
|
||||||
return rb_thread_call_without_gvl2(without_gvl_wrapper, &arg, ubf, data2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VALUE
|
VALUE
|
||||||
|
@ -1259,7 +1272,7 @@ rb_thread_io_blocking_region(rb_blocking_function_t *func, void *data1, int fd)
|
||||||
BLOCKING_REGION({
|
BLOCKING_REGION({
|
||||||
val = func(data1);
|
val = func(data1);
|
||||||
saved_errno = errno;
|
saved_errno = errno;
|
||||||
}, ubf_select, th);
|
}, ubf_select, th, FALSE);
|
||||||
}
|
}
|
||||||
TH_POP_TAG();
|
TH_POP_TAG();
|
||||||
|
|
||||||
|
@ -1343,7 +1356,7 @@ rb_thread_call_with_gvl(void *(*func)(void *), void *data1)
|
||||||
/* enter to Ruby world: You can access Ruby values, methods and so on. */
|
/* enter to Ruby world: You can access Ruby values, methods and so on. */
|
||||||
r = (*func)(data1);
|
r = (*func)(data1);
|
||||||
/* leave from Ruby world: You can not access Ruby values, etc. */
|
/* leave from Ruby world: You can not access Ruby values, etc. */
|
||||||
blocking_region_begin(th, brb, prev_unblock.func, prev_unblock.arg);
|
blocking_region_begin(th, brb, prev_unblock.func, prev_unblock.arg, FALSE);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3211,7 +3224,7 @@ do_select(int n, rb_fdset_t *read, rb_fdset_t *write, rb_fdset_t *except,
|
||||||
BLOCKING_REGION({
|
BLOCKING_REGION({
|
||||||
result = native_fd_select(n, read, write, except, timeout, th);
|
result = native_fd_select(n, read, write, except, timeout, th);
|
||||||
if (result < 0) lerrno = errno;
|
if (result < 0) lerrno = errno;
|
||||||
}, ubf_select, th);
|
}, ubf_select, th, FALSE);
|
||||||
|
|
||||||
RUBY_VM_CHECK_INTS_BLOCKING(th);
|
RUBY_VM_CHECK_INTS_BLOCKING(th);
|
||||||
|
|
||||||
|
@ -3437,7 +3450,7 @@ retry:
|
||||||
BLOCKING_REGION({
|
BLOCKING_REGION({
|
||||||
result = ppoll(&fds, 1, timeout, NULL);
|
result = ppoll(&fds, 1, timeout, NULL);
|
||||||
if (result < 0) lerrno = errno;
|
if (result < 0) lerrno = errno;
|
||||||
}, ubf_select, th);
|
}, ubf_select, th, FALSE);
|
||||||
|
|
||||||
RUBY_VM_CHECK_INTS_BLOCKING(th);
|
RUBY_VM_CHECK_INTS_BLOCKING(th);
|
||||||
|
|
||||||
|
@ -4161,7 +4174,7 @@ rb_mutex_lock(VALUE self)
|
||||||
int timeout_ms = 0;
|
int timeout_ms = 0;
|
||||||
struct rb_unblock_callback oldubf;
|
struct rb_unblock_callback oldubf;
|
||||||
|
|
||||||
set_unblock_function(th, lock_interrupt, mutex, &oldubf);
|
set_unblock_function(th, lock_interrupt, mutex, &oldubf, FALSE);
|
||||||
th->status = THREAD_STOPPED_FOREVER;
|
th->status = THREAD_STOPPED_FOREVER;
|
||||||
th->locking_mutex = self;
|
th->locking_mutex = self;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue