From a81fb93e0b24a059dcce160f28706d93ee2f1e5d Mon Sep 17 00:00:00 2001
From: Eric Wong <e@80x24.org>
Date: Fri, 17 Jul 2015 09:59:48 +0000
Subject: [PATCH] make Process.kill(:STOP, $$) resumable

Self-inflicted signals are delivered immediately.  This is fine
for most signals which are catchable, but SIGSTOP and SIGKILL
are special and cannot be caught by a userspace process.

SIGKILL is easy, the process will die immediately and we won't
care for it.  However, SIGSTOP is tricky because we cannot know
when it is delivered.

Thus, we must rely on sighandler->timer_thread to signal
th->interrupt_cond when SIGCONT resumes the process.

* signal.c (Init_signal): install sighandler for SIGCONT
* test/ruby/test_process.rb (test_stop_self_resumable): new test
---
 signal.c                  |  3 +++
 test/ruby/test_process.rb | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/signal.c b/signal.c
index 40caaa6..bc3ab4d 100644
--- a/signal.c
+++ b/signal.c
@@ -1456,6 +1456,9 @@ Init_signal(void)
 #ifdef SIGUSR2
     install_sighandler(SIGUSR2, sighandler);
 #endif
+#ifdef SIGCONT
+    install_sighandler(SIGCONT, sighandler);
+#endif
 
     if (!ruby_enable_coredump) {
 #ifdef SIGBUS
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index 00bbf9e..d42a096 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -1341,6 +1341,43 @@ class TestProcess < Test::Unit::TestCase
     end
   end
 
+  def test_stop_self_resumable
+    skip 'kill not supported' unless Process.respond_to?(:kill)
+    skip 'fork not supported' unless Process.respond_to?(:fork)
+    skip 'SIGSTOP not supported' unless Signal.list.include?('STOP')
+    skip 'WUNTRACED not defined' unless Process.const_defined?(:WUNTRACED)
+
+    (0..1).each do |n|
+      begin
+        pid = nil
+        Timeout.timeout(30) do
+          pid = fork do
+            # 2nd time we run this test, try with an explicit SIGCONT handler
+            case n
+            when 1
+              trap(:CONT) { n = 0 }
+            when 2
+              n = 0
+              trap(:CONT, 'IGNORE')
+            end
+
+            Process.kill(:STOP, $$)
+            exit(42 + n)
+          end
+          _, s = Process.waitpid2(pid, Process::WUNTRACED)
+          assert_predicate s, :stopped?
+          Process.kill(:CONT, pid)
+          _, s = Process.waitpid2(pid)
+          assert_predicate s, :exited?
+          assert_equal 42, s.exitstatus
+        end
+      rescue Timeout::Error
+        Process.kill(:KILL, pid) if pid
+        raise
+      end
+    end
+  end
+
   def test_wait_without_arg
     with_tmpchdir do
       write_file("foo", "sleep 0.1")
-- 
EW

