Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix for 14634 #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions test/ruby/test_autoload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,32 @@ class AutoloadTest
end
end

def test_autoload_fork
EnvUtil.default_warning do
Tempfile.create(['autoload', '.rb']) {|file|
file.puts 'sleep 0.3; class AutoloadTest; end'
file.close
add_autoload(file.path)
begin
thrs = []
3.times do
thrs << Thread.new { AutoloadTest; nil }
thrs << Thread.new { fork { AutoloadTest } }
end
thrs.each(&:join)
thrs.each do |th|
pid = th.value or next
_, status = Process.waitpid2(pid)
assert_predicate status, :success?
end
ensure
remove_autoload_constant
assert_nil $!, '[ruby-core:86410] [Bug #14634]'
end
}
end
end if Process.respond_to?(:fork)

def add_autoload(path)
(@autoload_paths ||= []) << path
::Object.class_eval {autoload(:AutoloadTest, path)}
Expand Down
19 changes: 19 additions & 0 deletions test/thread/test_cv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,23 @@ def test_dump
Marshal.dump(condvar)
end
end

def test_condvar_fork
mutex = Mutex.new
condvar = ConditionVariable.new
thrs = (1..10).map do
Thread.new { mutex.synchronize { condvar.wait(mutex) } }
end
thrs.each { 3.times { Thread.pass } }
pid = fork do
mutex.synchronize { condvar.broadcast }
exit!(0)
end
_, s = Process.waitpid2(pid)
assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]'
until thrs.empty?
mutex.synchronize { condvar.broadcast }
thrs.delete_if { |t| t.join(0.01) }
end
end if Process.respond_to?(:fork)
end
48 changes: 48 additions & 0 deletions test/thread/test_queue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -565,4 +565,52 @@ def test_queue_with_trap
puts 'exit'
INPUT
end

def test_fork_while_queue_waiting
q = Queue.new
sq = SizedQueue.new(1)
thq = Thread.new { q.pop }
thsq = Thread.new { sq.pop }
Thread.pass until thq.stop? && thsq.stop?

pid = fork do
exit!(1) if q.num_waiting != 0
exit!(2) if sq.num_waiting != 0
exit!(6) unless q.empty?
exit!(7) unless sq.empty?
q.push :child_q
sq.push :child_sq
exit!(3) if q.pop != :child_q
exit!(4) if sq.pop != :child_sq
exit!(0)
end
_, s = Process.waitpid2(pid)
assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]'

q.push :thq
sq.push :thsq
assert_equal :thq, thq.value
assert_equal :thsq, thsq.value

sq.push(1)
th = Thread.new { q.pop; sq.pop }
thsq = Thread.new { sq.push(2) }
Thread.pass until th.stop? && thsq.stop?
pid = fork do
exit!(1) if q.num_waiting != 0
exit!(2) if sq.num_waiting != 0
exit!(3) unless q.empty?
exit!(4) if sq.empty?
exit!(5) if sq.pop != 1
exit!(0)
end
_, s = Process.waitpid2(pid)
assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]'

assert_predicate thsq, :stop?
assert_equal 1, sq.pop
assert_same sq, thsq.value
q.push('restart th')
assert_equal 2, th.value
end if Process.respond_to?(:fork)
end
2 changes: 2 additions & 0 deletions thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -4188,6 +4188,8 @@ rb_thread_atfork_internal(rb_thread_t *th, void (*atfork)(rb_thread_t *, const r
}
rb_vm_living_threads_init(vm);
rb_vm_living_threads_insert(vm, th);
vm->fork_gen++;

vm->sleeper = 0;
clear_coverage();
}
Expand Down
44 changes: 41 additions & 3 deletions thread_sync.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
static VALUE rb_cMutex, rb_cQueue, rb_cSizedQueue, rb_cConditionVariable;
static VALUE rb_eClosedQueueError;

/*
* keep these globally so we can walk and reinitialize them at fork
* in the child process
*/
static LIST_HEAD(szqueue_list);
static LIST_HEAD(queue_list);
static LIST_HEAD(condvar_list);

/* sync_waiter is always on-stack */
struct sync_waiter {
rb_thread_t *th;
Expand Down Expand Up @@ -537,6 +545,7 @@ void rb_mutex_allow_trap(VALUE self, int val)
#define queue_waitq(q) UNALIGNED_MEMBER_PTR(q, waitq)
PACKED_STRUCT_UNALIGNED(struct rb_queue {
struct list_head waitq;
rb_serial_t fork_gen;
const VALUE que;
int num_waiting;
});
Expand Down Expand Up @@ -582,12 +591,29 @@ queue_alloc(VALUE klass)
return obj;
}

static int
queue_fork_check(struct rb_queue *q)
{
rb_serial_t fork_gen = GET_VM()->fork_gen;

if (q->fork_gen == fork_gen) {
return 0;
}
/* forked children can't reach into parent thread stacks */
q->fork_gen = fork_gen;
list_head_init(queue_waitq(q));
q->num_waiting = 0;
return 1;
}

static struct rb_queue *
queue_ptr(VALUE obj)
{
struct rb_queue *q;

TypedData_Get_Struct(obj, struct rb_queue, &queue_data_type, q);
queue_fork_check(q);

return q;
}

Expand Down Expand Up @@ -630,6 +656,11 @@ szqueue_ptr(VALUE obj)
struct rb_szqueue *sq;

TypedData_Get_Struct(obj, struct rb_szqueue, &szqueue_data_type, sq);
if (queue_fork_check(&sq->q)) {
list_head_init(szqueue_pushq(sq));
sq->num_waiting_push = 0;
}

return sq;
}

Expand Down Expand Up @@ -866,7 +897,7 @@ queue_do_pop(VALUE self, struct rb_queue *q, int should_block)
list_add_tail(&qw.as.q->waitq, &qw.w.node);
qw.as.q->num_waiting++;

rb_ensure(queue_sleep, Qfalse, queue_sleep_done, (VALUE)&qw);
rb_ensure(queue_sleep, self, queue_sleep_done, (VALUE)&qw);
}
}

Expand Down Expand Up @@ -1108,7 +1139,7 @@ rb_szqueue_push(int argc, VALUE *argv, VALUE self)
list_add_tail(pushq, &qw.w.node);
sq->num_waiting_push++;

rb_ensure(queue_sleep, Qfalse, szqueue_sleep_done, (VALUE)&qw);
rb_ensure(queue_sleep, self, szqueue_sleep_done, (VALUE)&qw);
}
}

Expand Down Expand Up @@ -1209,9 +1240,9 @@ rb_szqueue_empty_p(VALUE self)


/* ConditionalVariable */
/* TODO: maybe this can be IMEMO */
struct rb_condvar {
struct list_head waitq;
rb_serial_t fork_gen;
};

/*
Expand Down Expand Up @@ -1258,9 +1289,16 @@ static struct rb_condvar *
condvar_ptr(VALUE self)
{
struct rb_condvar *cv;
rb_serial_t fork_gen = GET_VM()->fork_gen;

TypedData_Get_Struct(self, struct rb_condvar, &cv_data_type, cv);

/* forked children can't reach into parent thread stacks */
if (cv->fork_gen != fork_gen) {
cv->fork_gen = fork_gen;
list_head_init(&cv->waitq);
}

return cv;
}

Expand Down
32 changes: 23 additions & 9 deletions variable.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "ccan/list/list.h"
#include "id_table.h"
#include "debug_counter.h"
#include "vm_core.h"

struct rb_id_table *rb_global_tbl;
static ID autoload, classpath, tmp_classpath, classid;
Expand Down Expand Up @@ -1859,6 +1860,7 @@ struct autoload_data_i {
rb_const_flag_t flag;
VALUE value;
struct autoload_state *state; /* points to on-stack struct */
rb_serial_t fork_gen;
};

static void
Expand All @@ -1881,8 +1883,18 @@ static const rb_data_type_t autoload_data_i_type = {
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};

#define check_autoload_data(av) \
(struct autoload_data_i *)rb_check_typeddata((av), &autoload_data_i_type)
static struct autoload_data_i *
get_autoload_data(VALUE av)
{
struct autoload_data_i *ele = rb_check_typeddata(av, &autoload_data_i_type);

/* do not reach across stack for ->state after forking: */
if (ele && ele->state && ele->fork_gen != GET_VM()->fork_gen) {
ele->state = 0;
ele->fork_gen = 0;
}
return ele;
}

RUBY_FUNC_EXPORTED void
rb_autoload(VALUE mod, ID id, const char *file)
Expand Down Expand Up @@ -1982,7 +1994,7 @@ check_autoload_required(VALUE mod, ID id, const char **loadingpath)
const char *loading;
int safe;

if (!(load = autoload_data(mod, id)) || !(ele = check_autoload_data(load))) {
if (!(load = autoload_data(mod, id)) || !(ele = get_autoload_data(load))) {
return 0;
}
file = ele->feature;
Expand Down Expand Up @@ -2020,7 +2032,7 @@ rb_autoloading_value(VALUE mod, ID id, VALUE* value, rb_const_flag_t *flag)
VALUE load;
struct autoload_data_i *ele;

if (!(load = autoload_data(mod, id)) || !(ele = check_autoload_data(load))) {
if (!(load = autoload_data(mod, id)) || !(ele = get_autoload_data(load))) {
return 0;
}
if (ele->state && ele->state->thread == rb_thread_current()) {
Expand Down Expand Up @@ -2087,8 +2099,9 @@ autoload_reset(VALUE arg)
int need_wakeups = 0;

if (state->ele->state == state) {
need_wakeups = 1;
state->ele->state = 0;
need_wakeups = 1;
state->ele->state = 0;
state->ele->fork_gen = 0;
}

/* At the last, move a value defined in autoload to constant table */
Expand Down Expand Up @@ -2170,7 +2183,7 @@ rb_autoload_load(VALUE mod, ID id)
if (src && loading && strcmp(src, loading) == 0) return Qfalse;

/* set ele->state for a marker of autoloading thread */
if (!(ele = check_autoload_data(load))) {
if (!(ele = get_autoload_data(load))) {
return Qfalse;
}

Expand All @@ -2180,6 +2193,7 @@ rb_autoload_load(VALUE mod, ID id)
state.thread = rb_thread_current();
if (!ele->state) {
ele->state = &state;
ele->fork_gen = GET_VM()->fork_gen;

/*
* autoload_reset will wake up any threads added to this
Expand Down Expand Up @@ -2217,7 +2231,7 @@ rb_autoload_p(VALUE mod, ID id)
}
load = check_autoload_required(mod, id, 0);
if (!load) return Qnil;
return (ele = check_autoload_data(load)) ? ele->feature : Qnil;
return (ele = get_autoload_data(load)) ? ele->feature : Qnil;
}

void
Expand Down Expand Up @@ -2646,7 +2660,7 @@ current_autoload_data(VALUE mod, ID id)
struct autoload_data_i *ele;
VALUE load = autoload_data(mod, id);
if (!load) return 0;
ele = check_autoload_data(load);
ele = get_autoload_data(load);
if (!ele) return 0;
/* for autoloading thread, keep the defined value to autoloading storage */
if (ele->state && (ele->state->thread == rb_thread_current())) {
Expand Down
1 change: 1 addition & 0 deletions vm_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ typedef struct rb_vm_struct {
struct rb_thread_struct *main_thread;
struct rb_thread_struct *running_thread;

rb_serial_t fork_gen;
struct list_head waiting_fds; /* <=> struct waiting_fd */
struct list_head living_threads;
size_t living_thread_num;
Expand Down