Project

General

Profile

Actions

Bug #18444

open

Trapped TSTP causes a locking deadlock in 3.0.3 onward

Bug #18444: Trapped TSTP causes a locking deadlock in 3.0.3 onward

Added by why-el (Mohamed Wael Khobalatte) over 4 years ago. Updated 3 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:106867]

Description

A curious case:

ruby -e 'Signal.trap("TSTP") { puts "Received a terminal stop signal, but i will sleep instead."; sleep 10 }; loop {puts 1}'

this fails with deadlock; recursive locking (ThreadError) when I send the SIGTSTP via my terminal. This is on Mac OS Monterey (M1). It only happens in 3.0.3 and onward (I tried 3.1.0-preview1 as well, fails there too), when I try 3.0.2, the signal is handled properly.


Files

18444.log (11.8 KB) 18444.log wanabe (_ wanabe), 08/13/2026 05:05 AM

Related issues 1 (1 open0 closed)

Related to Ruby - Bug #19557: Deadlock on STDOUT(ERR) lock on signal handlerOpenActions

Updated by jeremyevans0 (Jeremy Evans) over 4 years ago Actions #1 [ruby-core:107105]

I couldn't replicate this behavior in OpenBSD/amd64. On OpenBSD/amd64, sending TSTP prints the Received... string twice, 10 seconds part, followed by the loop printing 1. Windows doesn't support TSTP, so no reason to test there. Can anyone replicate this outside of M1 MacOS?

Updated by xtkoba (Tee KOBAYASHI) about 4 years ago Actions #2 [ruby-core:108719]

I tested this on several Linux (GNU/Linux and Android) environments. Sending Ctrl-Z from terminal sometimes results in "Received ..." and sometimes "deadlock" but not sticking to one result for each environment. I used the newest released versions of Ruby (2.7.6, 3.0.4, 3.1.2) but not sure what happens for older versions.

Tested environments:

$ ruby27 -v
ruby 2.7.6p219 (2022-04-12 revision c9c2245c0a) [x86_64-linux]

$ ruby30 -v
ruby 3.0.4p208 (2022-04-12 revision 3fa771dded) [x86_64-linux]

$ ruby31 -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]

$ uname -a
Linux localhost 5.4.188-gentoo #1 SMP Wed Mar 30 09:04:25 JST 2022 x86_64 Intel(R) Core(TM)2 CPU U7500 @ 1.06GHz GenuineIntel GNU/Linux
$ ruby -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm-linux-android]

$ uname -a
Linux localhost 3.10.49-g12ec2e6-00359-g4edd381 #1 SMP PREEMPT Tue Sep 26 20:27:28 CST 2017 armv7l
$ ruby -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [aarch64-linux-android]

$ uname -a
Linux localhost 4.9.82-perf+ #1 SMP PREEMPT Wed Feb 27 13:05:28 CST 2019 aarch64

Updated by wanabe (_ wanabe) 3 days ago Actions #3 [ruby-core:126383]

This issue can also be reproduced on the master branch. Since it is not specific to TSTP, it can be reproduced with any signal.

$ ./miniruby -ve 'Signal.trap(:TERM) { print "2\r"; sleep 0.01; exit(0) }; spawn("sleep 0.1; kill -TERM #{$$}"); loop {print "1\r"}'
ruby 4.1.0dev (2026-08-12T02:39:28Z origin/master c6e9858440) +PRISM [x86_64-linux]
-e:1:in 'IO#write': deadlock; recursive locking (ThreadError)
        from -e:1:in 'Kernel#print'
        from -e:1:in 'block in <main>'
        from -e:1:in 'IO#write'
        from -e:1:in 'Kernel#print'
        from -e:1:in 'block in <main>'
        from -e:1:in 'Kernel#loop'
        from -e:1:in '<main>'

I attached the backtrace captured with GDB.
Summary:

print "1\r" # print in loop{}
->
#44 0x00005555557005d5 in io_binwrite (ptr=0x55555606e810 "1\r", len=2, fptr=0x555555f26c40, nosync=0) at ../../io.c:1939
#43 0x000055555589676c in rb_mutex_synchronize (self=140737345534480, func=0x555555700320 <io_binwrite_string>, arg=140737488338256) at ../../thread_sync.c:629
#42 0x00005555556b408c in rb_ec_ensure (ec_arg=0x555555ebff58, b_proc=0x555555700320 <io_binwrite_string>, data1=140737488338256, 
    e_proc_arg=0x555555896318 <do_mutex_unlock_safe>, data2_arg=140737488338176) at ../../eval.c:1171
#40 0x000055555570030a in io_binwrite_string_internal (fptr=0x555555f26c40, ptr=0x55555606e810 "1\r", length=2) at ../../io.c:1817
#38 0x00005555556fdf28 in rb_io_blocking_region_wait (io=0x555555f26c40, function=0x5555556ff0db <internal_write_func>, argument=0x7fffffffbac0, events=RUBY_IO_WRITABLE)
    at ../../io.c:230
#35 0x000055555589d86e in rb_threadptr_execute_interrupts (th=0x555555ea0780 <_main_thread>, blocking_timing=1) at ../../thread.c:2820

Signal.trap(:TERM)
->
#34 0x000055555584688a in rb_signal_exec (th=0x555555ea0780 <_main_thread>, sig=15) at ../../signal.c:1143
#33 0x00005555558466ef in signal_exec (cmd=140737345533960, sig=15) at ../../signal.c:1081
#32 0x00005555559106d9 in rb_eval_cmd_call_kw (cmd=140737345533960, argc=1, argv=0x7fffffffb778, kw_splat=0) at ../../vm_eval.c:2196

print "2\r" # print in trap{}
->
#3  0x00005555557005d5 in io_binwrite (ptr=0x55555606e8c0 "2\r", len=2, fptr=0x555555f26c40, nosync=0) at ../../io.c:1939
#2  0x000055555589674a in rb_mutex_synchronize (self=140737345534480, func=0x555555700320 <io_binwrite_string>, arg=140737488328896) at ../../thread_sync.c:628
#1  0x0000555555895bf4 in do_mutex_lock (args=0x7fffffff9870, interruptible_p=1) at ../../thread_sync.c:296
#0  rb_raise (exc_class=93824993755556, fmt=0x7fffffff9710 "@\227\377\377\377\177") at ../../error.c:3930
  • Since rb_signal_exec is executed
    • while STDOUT is locked by the print statement in the main loop,
  • a problem occurs when attempting to use print within the trap block
  • because STDOUT is still locked.

Ideally, this issue could be resolved by ensuring that rb_signal_exec is executed only after the lock is released by the first print statement in the main loop.
But I am concerned that doing so might compromise compatibility.

Updated by wanabe (_ wanabe) 1 day ago Actions #4

  • Related to Bug #19557: Deadlock on STDOUT(ERR) lock on signal handler added
Actions

Also available in: PDF Atom