thread_pthread.c: main thread always gets hit by signals

We need to ensure Signal.trap handlers can function if the main
thread is sleeping after a subthread has grabbed sigwait_fd,
but later exited.

Consider the following timeline:

main_thread             sub-thread
-----------------------------------------
Signal.trap() { ... }
                        get sigwait_fd
                        ppoll on sigwait_fd
native_cond_sleep
(via pthread_cond_wait)
                        ppoll times-out
			put sigwait_fd
                        sub-thread exits

only thread alive
SIGNAL HITS

The problem is pthread_cond_wait cannot return EINTR,
so we can never run the Signal.trap handler.  So we
will avoid using native_cond_sleep in the main thread
and always use ppoll to sleep when in the main thread.
This can guarantee the main thread remains aware of
signals; even if it cannot safely read off sigwait_fd

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64538 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
normal 2018-08-25 21:59:30 +00:00
Родитель 49309733e2
Коммит 70a8a6d3eb
2 изменённых файлов: 61 добавлений и 1 удалений

Просмотреть файл

@ -369,4 +369,24 @@ class TestSignal < Test::Unit::TestCase
ensure
trap(:CHLD, old) if Signal.list['CHLD']
end
def test_sigwait_fd_unused
t = EnvUtil.apply_timeout_scale(0.1)
assert_separately([], <<-End)
tgt = $$
trap(:TERM) { exit(0) }
e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})"
term = [ '#{EnvUtil.rubybin}', '--disable=gems', '-e', e ]
t2 = Thread.new { sleep } # grab sigwait_fd
Thread.pass until t2.stop?
Thread.new do
sleep #{t}
t2.kill
t2.join
end
Process.spawn(*term)
# last thread remaining, ensure it can react to SIGTERM
loop { sleep }
End
end if Process.respond_to?(:kill) && Process.respond_to?(:daemon)
end

Просмотреть файл

@ -1911,7 +1911,7 @@ rb_sigwait_fd_get(const rb_thread_t *th)
return signal_self_pipe.normal[0];
}
}
return -1; /* avoid thundering herd */
return -1; /* avoid thundering herd and work stealing/starvation */
}
void
@ -1996,6 +1996,43 @@ rb_sigwait_sleep(rb_thread_t *th, int sigwait_fd, const struct timespec *ts)
}
}
/*
* This function does not exclusively acquire sigwait_fd, so it
* cannot safely read from it. However, it can be woken up in
* 4 ways:
*
* 1) ubf_select (from another thread)
* 2) rb_thread_wakeup_timer_thread (from signal handler)
* 3) any unmasked signal hitting the process
* 4) periodic ubf timer wakeups (after 3)
*/
static void
native_ppoll_sleep(rb_thread_t *th, rb_hrtime_t *rel)
{
rb_native_mutex_lock(&th->interrupt_lock);
th->unblock.func = ubf_select;
th->unblock.arg = th;
rb_native_mutex_unlock(&th->interrupt_lock);
GVL_UNLOCK_BEGIN(th);
if (!RUBY_VM_INTERRUPTED(th->ec)) {
struct pollfd pfd;
struct timespec ts;
pfd.fd = signal_self_pipe.normal[0]; /* sigwait_fd */
pfd.events = POLLIN;
(void)ppoll(&pfd, 1, rb_hrtime2timespec(&ts, rel), 0);
/*
* do not read the fd, here, let uplevel callers or other threads
* that, otherwise we may steal and starve other threads
*/
}
unblock_function_clear(th);
unregister_ubf_list(th);
GVL_UNLOCK_END(th);
}
static void
native_sleep(rb_thread_t *th, rb_hrtime_t *rel)
{
@ -2020,6 +2057,9 @@ native_sleep(rb_thread_t *th, rb_hrtime_t *rel)
rb_sigwait_fd_put(th, sigwait_fd);
rb_sigwait_fd_migrate(th->vm);
}
else if (th == th->vm->main_thread) { /* always able to handle signals */
native_ppoll_sleep(th, rel);
}
else {
native_cond_sleep(th, rel);
}