diff --git a/test/config/process_detach_before_fork.rb b/test/config/process_detach_before_fork.rb new file mode 100644 index 0000000000..c61a453317 --- /dev/null +++ b/test/config/process_detach_before_fork.rb @@ -0,0 +1,11 @@ +worker_shutdown_timeout 0 + +before_fork do + pid = fork do + sleep 30 # This has to exceed the test timeout + end + + pid_filename = File.join(Dir.tmpdir, 'process_detach_test.pid') + File.write(pid_filename, pid) + Process.detach(pid) +end diff --git a/test/test_integration_cluster.rb b/test/test_integration_cluster.rb index 4403776a30..911ff1bb47 100644 --- a/test/test_integration_cluster.rb +++ b/test/test_integration_cluster.rb @@ -175,6 +175,35 @@ def test_stuck_external_term_spawn end end + # From Ruby 2.6 to 3.2, `Process.detach` can delay or prevent + # `Process.wait2(-1)` from detecting a terminated child: + # https://bugs.ruby-lang.org/issues/19837. However, + # `Process.wait2()` still works properly. This bug has + # been fixed in Ruby 3.3. + def test_workers_respawn_with_process_detach + skip_unless_signal_exist? :KILL + + config = 'test/config/process_detach_before_fork.rb' + + worker_respawn(0, workers, config) do |phase0_worker_pids| + last = phase0_worker_pids.last + phase0_worker_pids.each do |pid| + Process.kill :KILL, pid + end + end + + # `test/config/process_detach_before_fork.rb` forks and detaches a + # process. Since MiniTest attempts to join all threads before + # finishing, terminate the process so that the test can end quickly + # if it passes. + pid_filename = File.join(Dir.tmpdir, 'process_detach_test.pid') + if File.exist?(pid_filename) + pid = File.read(pid_filename).chomp.to_i + File.unlink(pid_filename) + Process.kill :TERM, pid if pid > 0 + end + end + # mimicking stuck workers, test restart def test_stuck_phased_restart skip_unless_signal_exist? :USR1 @@ -681,10 +710,10 @@ def usr1_all_respond(unix: false, config: '') end end - def worker_respawn(phase = 1, size = workers) + def worker_respawn(phase = 1, size = workers, config = 'test/config/worker_shutdown_timeout_2.rb') threads = [] - cli_server "-w #{workers} -t 1:1 -C test/config/worker_shutdown_timeout_2.rb test/rackup/sleep_pid.ru" + cli_server "-w #{workers} -t 1:1 -C #{config} test/rackup/sleep_pid.ru" # make sure two workers have booted phase0_worker_pids = get_worker_pids