From e153e8ea0af002e4233557085c81b3277adbc28c Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 23 Jul 2023 08:50:10 +1000 Subject: [PATCH 01/28] pass details on interrupt --- src/crystal/system/process.cr | 9 +++++++++ src/crystal/system/unix/process.cr | 24 ++++++++++++++++++++++-- src/crystal/system/win32/process.cr | 15 +++++++++++++-- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index 387447c083c2..e6d1a05898b9 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -77,6 +77,15 @@ struct Crystal::System::Process # Changes the root directory for the current process. # def self.chroot(path : String) + + enum Interrupt + # sigint, ctrl-c or ctrl-break events + USER_SIGNALLED + # sighup or closed event + TERMINAL_DISCONNECTED + # sigterm, logoff or shutdown events + SESSION_ENDED + end end module Crystal::System diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index f07a91806857..240c97f54d3b 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -58,8 +58,28 @@ struct Crystal::System::Process raise RuntimeError.from_errno("kill") if ret < 0 end - def self.on_interrupt(&handler : ->) : Nil - ::Signal::INT.trap { |_signal| handler.call } + def self.on_interrupt(&handler : Interrupt ->) : Nil + sig_handler = Proc(::Signal, Nil).new do |signal| + int_type = case signal + when .int? + Interrupt::USER_SIGNALLED + when .hup? + Interrupt::TERMINAL_DISCONNECTED + when .term? + Interrupt::SESSION_ENDED + else + Interrupt::USER_SIGNALLED + end + handler.call int_type + + # ignore prevents system defaults and clears registered interrupts + # hence we need to re-register + signal.ignore + Process.on_interrupt &handler + end + ::Signal::INT.trap &sig_handler + ::Signal::HUP.trap &sig_handler + ::Signal::TERM.trap &sig_handler end def self.ignore_interrupts! : Nil diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 36878bc935bf..5af21a395963 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -14,6 +14,7 @@ struct Crystal::System::Process @@interrupt_count = Crystal::AtomicSemaphore.new @@win32_interrupt_handler : LibC::PHANDLER_ROUTINE? @@setup_interrupt_handler = Atomic::Flag.new + @@last_interrupt = Interrupt::USER_SIGNALLED def initialize(process_info) @pid = process_info.dwProcessId @@ -103,7 +104,16 @@ struct Crystal::System::Process def self.on_interrupt(&@@interrupt_handler : ->) : Nil restore_interrupts! @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| - next 0 unless event_type.in?(LibC::CTRL_C_EVENT, LibC::CTRL_BREAK_EVENT) + @@last_interrupt = case event_type + when LibC::CTRL_C_EVENT, LibC::CTRL_BREAK_EVENT + Interrupt::USER_SIGNALLED + when LibC::CTRL_CLOSE_EVENT + Interrupt::TERMINAL_DISCONNECTED + when LibC::CTRL_LOGOFF_EVENT, LibC::CTRL_SHUTDOWN_EVENT + Interrupt::SESSION_ENDED + else + next 0 + end @@interrupt_count.signal 1 end @@ -136,8 +146,9 @@ struct Crystal::System::Process if handler = @@interrupt_handler non_nil_handler = handler # if handler is closured it will also have the Nil type + int_type = @@last_interrupt spawn do - non_nil_handler.call + non_nil_handler.call int_type rescue ex ex.inspect_with_backtrace(STDERR) STDERR.puts("FATAL: uncaught exception while processing interrupt handler, exiting") From 2ad9331f017be14f2c3fd18bad9c65d6e81f7c65 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 23 Jul 2023 08:58:37 +1000 Subject: [PATCH 02/28] fix win32 handler def --- src/crystal/system/win32/process.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 5af21a395963..101d30f931d6 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -10,7 +10,7 @@ struct Crystal::System::Process @thread_id : LibC::DWORD @process_handle : LibC::HANDLE - @@interrupt_handler : Proc(Nil)? + @@interrupt_handler : Proc(Interrupt)? @@interrupt_count = Crystal::AtomicSemaphore.new @@win32_interrupt_handler : LibC::PHANDLER_ROUTINE? @@setup_interrupt_handler = Atomic::Flag.new @@ -101,7 +101,7 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end - def self.on_interrupt(&@@interrupt_handler : ->) : Nil + def self.on_interrupt(&@@interrupt_handler : Interrupt ->) : Nil restore_interrupts! @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| @@last_interrupt = case event_type From 006ce08402b436152449403d87cf448842651b0f Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 23 Jul 2023 09:11:59 +1000 Subject: [PATCH 03/28] update public interface --- src/process.cr | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/process.cr b/src/process.cr index 159b08b39eaa..b348c8c09426 100644 --- a/src/process.cr +++ b/src/process.cr @@ -50,15 +50,18 @@ class Process Crystal::System::Process.signal(pid, signal.value) end + alias Interrupt = Crystal::System::Process::Interrupt + # Installs *handler* as the new handler for interrupt requests. Removes any # previously set interrupt handler. # # The handler is executed on a fresh fiber every time an interrupt occurs. # - # * On Unix-like systems, this traps `SIGINT`. - # * On Windows, this captures Ctrl + C and - # Ctrl + Break signals sent to a console application. - def self.on_interrupt(&handler : ->) : Nil + # * On Unix-like systems, this traps `SIGINT`, `SIGHUP`, `SIGTERM`. + # * On Windows, this captures Ctrl + C, + # Ctrl + Break, terminal close, windows logoff + # and shutdown signals sent to a console application. + def self.on_interrupt(&handler : Interrupt ->) : Nil Crystal::System::Process.on_interrupt(&handler) end From 4ef574f6442043a9a4eb8a47d7985d14fac50612 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 23 Jul 2023 17:06:20 +1000 Subject: [PATCH 04/28] fix windows build --- src/crystal/system/win32/process.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 101d30f931d6..2fd9273a22af 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -10,7 +10,7 @@ struct Crystal::System::Process @thread_id : LibC::DWORD @process_handle : LibC::HANDLE - @@interrupt_handler : Proc(Interrupt)? + @@interrupt_handler : Proc(Interrupt, Nil)? @@interrupt_count = Crystal::AtomicSemaphore.new @@win32_interrupt_handler : LibC::PHANDLER_ROUTINE? @@setup_interrupt_handler = Atomic::Flag.new From 0fa95aacff3b1be9d03744500adb39177215e20b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 23 Jul 2023 17:46:44 +1000 Subject: [PATCH 05/28] add missed windows constants --- src/lib_c/x86_64-windows-msvc/c/consoleapi.cr | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr index 680e199be2ab..2938b544ddcc 100644 --- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr @@ -14,8 +14,11 @@ lib LibC fun GetConsoleCP : DWORD fun GetConsoleOutputCP : DWORD - CTRL_C_EVENT = 0 - CTRL_BREAK_EVENT = 1 + CTRL_C_EVENT = 0 + CTRL_BREAK_EVENT = 1 + CTRL_CLOSE_EVENT = 2 + CTRL_LOGOFF_EVENT = 5 + CTRL_SHUTDOWN_EVENT = 6 alias PHANDLER_ROUTINE = DWORD -> BOOL From 2044e94efd1bca5bdced85adf75b7a5cc8c01862 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 19 Sep 2023 09:49:44 +1000 Subject: [PATCH 06/28] use Process::ExitReason enum --- src/crystal/system/process.cr | 11 +---------- src/crystal/system/unix/process.cr | 10 +++++----- src/crystal/system/wasi/process.cr | 2 +- src/crystal/system/win32/process.cr | 8 ++++---- src/process.cr | 2 +- src/process/status.cr | 12 ++++++++++++ 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index e6d1a05898b9..c5eddaef8460 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -44,7 +44,7 @@ struct Crystal::System::Process # Installs *handler* as the new handler for interrupt requests. Removes any # previously set interrupt handler. - # def self.on_interrupt(&handler : ->) + # def self.on_interrupt(&handler : ::Process::ExitReason ->) # Ignores all interrupt requests. Removes any custom interrupt handler set # def self.ignore_interrupts! @@ -77,15 +77,6 @@ struct Crystal::System::Process # Changes the root directory for the current process. # def self.chroot(path : String) - - enum Interrupt - # sigint, ctrl-c or ctrl-break events - USER_SIGNALLED - # sighup or closed event - TERMINAL_DISCONNECTED - # sigterm, logoff or shutdown events - SESSION_ENDED - end end module Crystal::System diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 240c97f54d3b..3cee8bf17009 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -58,17 +58,17 @@ struct Crystal::System::Process raise RuntimeError.from_errno("kill") if ret < 0 end - def self.on_interrupt(&handler : Interrupt ->) : Nil + def self.on_interrupt(&handler : ::Process::ExitReason ->) : Nil sig_handler = Proc(::Signal, Nil).new do |signal| int_type = case signal when .int? - Interrupt::USER_SIGNALLED + ::Process::ExitReason::Interrupted when .hup? - Interrupt::TERMINAL_DISCONNECTED + ::Process::ExitReason::TerminalDisconnected when .term? - Interrupt::SESSION_ENDED + ::Process::ExitReason::SessionEnded else - Interrupt::USER_SIGNALLED + ::Process::ExitReason::Interrupted end handler.call int_type diff --git a/src/crystal/system/wasi/process.cr b/src/crystal/system/wasi/process.cr index a6f9d156c396..4d524b9b3e44 100644 --- a/src/crystal/system/wasi/process.cr +++ b/src/crystal/system/wasi/process.cr @@ -48,7 +48,7 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end - def self.on_interrupt(&handler : ->) : Nil + def self.on_interrupt(&handler : ::Process::ExitReason ->) : Nil raise NotImplementedError.new("Process.on_interrupt") end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 2940bfdd20d6..998f9af2a010 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -101,16 +101,16 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end - def self.on_interrupt(&@@interrupt_handler : Interrupt ->) : Nil + def self.on_interrupt(&@@interrupt_handler : ::Process::ExitReason ->) : Nil restore_interrupts! @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| @@last_interrupt = case event_type when LibC::CTRL_C_EVENT, LibC::CTRL_BREAK_EVENT - Interrupt::USER_SIGNALLED + ::Process::ExitReason::Interrupted when LibC::CTRL_CLOSE_EVENT - Interrupt::TERMINAL_DISCONNECTED + ::Process::ExitReason::TerminalDisconnected when LibC::CTRL_LOGOFF_EVENT, LibC::CTRL_SHUTDOWN_EVENT - Interrupt::SESSION_ENDED + ::Process::ExitReason::SessionEnded else next 0 end diff --git a/src/process.cr b/src/process.cr index b348c8c09426..5433f2c7463e 100644 --- a/src/process.cr +++ b/src/process.cr @@ -61,7 +61,7 @@ class Process # * On Windows, this captures Ctrl + C, # Ctrl + Break, terminal close, windows logoff # and shutdown signals sent to a console application. - def self.on_interrupt(&handler : Interrupt ->) : Nil + def self.on_interrupt(&handler : ::Process::ExitReason ->) : Nil Crystal::System::Process.on_interrupt(&handler) end diff --git a/src/process/status.cr b/src/process/status.cr index c7b78b1a4583..bfc5861a42b1 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -79,6 +79,18 @@ enum Process::ExitReason # A `Process::Status` that maps to `Unknown` may map to a different value if # new enum members are added to `ExitReason`. Unknown + + # The process exited due to the user closing the terminal window or ending an ssh session. + # + # * On Unix-like systems, this corresponds to `Signal::HUP` + # * On Windows, this corresponds to the `CTRL_CLOSE_EVENT` message + TerminalDisconnected + + # The process exited due to the user logging off or shutting down the OS. + # + # * On Unix-like systems, this corresponds to `Signal::TERM` + # * On Windows, this corresponds to the `CTRL_LOGOFF_EVENT`, `CTRL_SHUTDOWN_EVENT` messages + SessionEnded end # The status of a terminated process. Returned by `Process#wait`. From 360a3b54e0083ba8d18443f12ec1b92e932871f7 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 19 Sep 2023 09:51:40 +1000 Subject: [PATCH 07/28] remove alias --- src/process.cr | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/process.cr b/src/process.cr index 5433f2c7463e..d57ba07c2ff7 100644 --- a/src/process.cr +++ b/src/process.cr @@ -50,8 +50,6 @@ class Process Crystal::System::Process.signal(pid, signal.value) end - alias Interrupt = Crystal::System::Process::Interrupt - # Installs *handler* as the new handler for interrupt requests. Removes any # previously set interrupt handler. # From 2533fad0d37335df017ad280ed4a02ee15e52af7 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 19 Sep 2023 10:02:54 +1000 Subject: [PATCH 08/28] fix windows class vars --- src/crystal/system/win32/process.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 998f9af2a010..624ef7edf968 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -10,11 +10,11 @@ struct Crystal::System::Process @thread_id : LibC::DWORD @process_handle : LibC::HANDLE - @@interrupt_handler : Proc(Interrupt, Nil)? + @@interrupt_handler : Proc(::Process::ExitReason, Nil)? @@interrupt_count = Crystal::AtomicSemaphore.new @@win32_interrupt_handler : LibC::PHANDLER_ROUTINE? @@setup_interrupt_handler = Atomic::Flag.new - @@last_interrupt = Interrupt::USER_SIGNALLED + @@last_interrupt = ::Process::ExitReason::Interrupted def initialize(process_info) @pid = process_info.dwProcessId From 0ea6ecbf8af1a934f8edc93d76eb1de493e1dc7e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Nov 2023 16:26:34 +1100 Subject: [PATCH 09/28] win32 backwards compat --- src/crystal/system/win32/process.cr | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 1e4c7472f519..b94907ff4b34 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -13,7 +13,7 @@ struct Crystal::System::Process @job_object : LibC::HANDLE @completion_key = IO::Overlapped::CompletionKey.new - @@interrupt_handler : Proc(::Process::ExitReason, Nil)? + @@interrupt_handler : Proc(::Process::ExitReason, Nil) | Proc(Nil)? @@interrupt_count = Crystal::AtomicSemaphore.new @@win32_interrupt_handler : LibC::PHANDLER_ROUTINE? @@setup_interrupt_handler = Atomic::Flag.new @@ -151,7 +151,7 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end - def self.on_interrupt(&@@interrupt_handler : ::Process::ExitReason ->) : Nil + def self.on_interrupt(&@@interrupt_handler) : Nil restore_interrupts! @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| @@last_interrupt = case event_type @@ -198,7 +198,11 @@ struct Crystal::System::Process non_nil_handler = handler # if handler is closured it will also have the Nil type int_type = @@last_interrupt spawn do - non_nil_handler.call int_type + if non_nil_handler.arity == 0 + non_nil_handler.as(Proc(Nil)).call + else + non_nil_handler.as(Proc(::Process::ExitReason, Nil)).call int_type + end rescue ex ex.inspect_with_backtrace(STDERR) STDERR.puts("FATAL: uncaught exception while processing interrupt handler, exiting") From 118baffb08ea9b6658c61df36db578be5573b12b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Nov 2023 16:27:54 +1100 Subject: [PATCH 10/28] remove type requirements --- src/process.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/process.cr b/src/process.cr index d57ba07c2ff7..6ece2acb1544 100644 --- a/src/process.cr +++ b/src/process.cr @@ -59,7 +59,7 @@ class Process # * On Windows, this captures Ctrl + C, # Ctrl + Break, terminal close, windows logoff # and shutdown signals sent to a console application. - def self.on_interrupt(&handler : ::Process::ExitReason ->) : Nil + def self.on_interrupt(&handler) : Nil Crystal::System::Process.on_interrupt(&handler) end From 793f0a6085d6d86ac6fcb060937f83644ad8a18e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Nov 2023 16:29:40 +1100 Subject: [PATCH 11/28] fix wasi compatibility --- src/crystal/system/wasi/process.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crystal/system/wasi/process.cr b/src/crystal/system/wasi/process.cr index 4d524b9b3e44..8cbc36968751 100644 --- a/src/crystal/system/wasi/process.cr +++ b/src/crystal/system/wasi/process.cr @@ -48,7 +48,7 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end - def self.on_interrupt(&handler : ::Process::ExitReason ->) : Nil + def self.on_interrupt(&handler) : Nil raise NotImplementedError.new("Process.on_interrupt") end From 5765c73e339f836863b693703cf6c12745fdfada Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Nov 2023 16:48:07 +1100 Subject: [PATCH 12/28] unix with compatibility --- src/crystal/system/unix/process.cr | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 32a76e5c2d16..74c3c1adb99b 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -58,7 +58,7 @@ struct Crystal::System::Process raise RuntimeError.from_errno("kill") if ret < 0 end - def self.on_interrupt(&handler : ::Process::ExitReason ->) : Nil + def self.on_interrupt(&handler) : Nil sig_handler = Proc(::Signal, Nil).new do |signal| int_type = case signal when .int? @@ -70,7 +70,12 @@ struct Crystal::System::Process else ::Process::ExitReason::Interrupted end - handler.call int_type + # maintain backwards compatibility + if handler.arity == 0 + handler.as(Proc(Nil)).call + else + handler.as(Proc(::Process::ExitReason, Nil)).call int_type + end # ignore prevents system defaults and clears registered interrupts # hence we need to re-register From 23520656d152927957afc20f1e45cf77bc30652e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Nov 2023 16:51:12 +1100 Subject: [PATCH 13/28] fix compilation --- src/crystal/system/unix/process.cr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 74c3c1adb99b..68103f258635 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -70,11 +70,12 @@ struct Crystal::System::Process else ::Process::ExitReason::Interrupted end + # maintain backwards compatibility - if handler.arity == 0 - handler.as(Proc(Nil)).call + if handler.is_a? Proc(Nil) + handler.call else - handler.as(Proc(::Process::ExitReason, Nil)).call int_type + handler.call int_type end # ignore prevents system defaults and clears registered interrupts From 33c9ac6e342d701237530a38fde87f95917f2e62 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 16 Nov 2023 18:14:11 +1100 Subject: [PATCH 14/28] improve readability of win32 proc type --- src/crystal/system/win32/process.cr | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index b94907ff4b34..2a6237a491cc 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -198,10 +198,11 @@ struct Crystal::System::Process non_nil_handler = handler # if handler is closured it will also have the Nil type int_type = @@last_interrupt spawn do - if non_nil_handler.arity == 0 - non_nil_handler.as(Proc(Nil)).call - else - non_nil_handler.as(Proc(::Process::ExitReason, Nil)).call int_type + case non_nil_handler + in Proc(Nil) + non_nil_handler.call + in Proc(::Process::ExitReason, Nil) + non_nil_handler.call int_type end rescue ex ex.inspect_with_backtrace(STDERR) From 0a11eb5bfd0e16069af134499732ced3b09cbc5a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 17 Nov 2023 08:24:46 +1100 Subject: [PATCH 15/28] Update src/process.cr Co-authored-by: Devonte W --- src/process.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/process.cr b/src/process.cr index 6ece2acb1544..00302c437fe4 100644 --- a/src/process.cr +++ b/src/process.cr @@ -55,7 +55,7 @@ class Process # # The handler is executed on a fresh fiber every time an interrupt occurs. # - # * On Unix-like systems, this traps `SIGINT`, `SIGHUP`, `SIGTERM`. + # * On Unix-like systems, this traps `SIGINT`, `SIGHUP` and `SIGTERM`. # * On Windows, this captures Ctrl + C, # Ctrl + Break, terminal close, windows logoff # and shutdown signals sent to a console application. From bdc854153c681fb3aa5ed595f34bdbc5ec84d028 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 17 Nov 2023 08:24:56 +1100 Subject: [PATCH 16/28] Update src/process/status.cr Co-authored-by: Devonte W --- src/process/status.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process/status.cr b/src/process/status.cr index bfc5861a42b1..0952f50437a5 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -82,8 +82,8 @@ enum Process::ExitReason # The process exited due to the user closing the terminal window or ending an ssh session. # - # * On Unix-like systems, this corresponds to `Signal::HUP` - # * On Windows, this corresponds to the `CTRL_CLOSE_EVENT` message + # * On Unix-like systems, this corresponds to `Signal::HUP`. + # * On Windows, this corresponds to the `CTRL_CLOSE_EVENT` message. TerminalDisconnected # The process exited due to the user logging off or shutting down the OS. From a2bc368b1486dae9105fad9cadee154847bd2dc2 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 17 Nov 2023 08:25:06 +1100 Subject: [PATCH 17/28] Update src/process/status.cr Co-authored-by: Devonte W --- src/process/status.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process/status.cr b/src/process/status.cr index 0952f50437a5..52386ba68833 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -88,8 +88,8 @@ enum Process::ExitReason # The process exited due to the user logging off or shutting down the OS. # - # * On Unix-like systems, this corresponds to `Signal::TERM` - # * On Windows, this corresponds to the `CTRL_LOGOFF_EVENT`, `CTRL_SHUTDOWN_EVENT` messages + # * On Unix-like systems, this corresponds to `Signal::TERM`. + # * On Windows, this corresponds to the `CTRL_LOGOFF_EVENT` and `CTRL_SHUTDOWN_EVENT` messages. SessionEnded end From 6bcf16608ee2bf91d3b16e4f4da1071a2f467a14 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 17 Nov 2023 08:29:47 +1100 Subject: [PATCH 18/28] fix win32 compilation --- src/crystal/system/win32/process.cr | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 2a6237a491cc..b94907ff4b34 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -198,11 +198,10 @@ struct Crystal::System::Process non_nil_handler = handler # if handler is closured it will also have the Nil type int_type = @@last_interrupt spawn do - case non_nil_handler - in Proc(Nil) - non_nil_handler.call - in Proc(::Process::ExitReason, Nil) - non_nil_handler.call int_type + if non_nil_handler.arity == 0 + non_nil_handler.as(Proc(Nil)).call + else + non_nil_handler.as(Proc(::Process::ExitReason, Nil)).call int_type end rescue ex ex.inspect_with_backtrace(STDERR) From d497f08809c759731962ab542ff62f291715c38d Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Fri, 17 Nov 2023 08:32:08 +1100 Subject: [PATCH 19/28] minor performance improvement --- src/crystal/system/unix/process.cr | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 68103f258635..00d44884b7e4 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -60,21 +60,20 @@ struct Crystal::System::Process def self.on_interrupt(&handler) : Nil sig_handler = Proc(::Signal, Nil).new do |signal| - int_type = case signal - when .int? - ::Process::ExitReason::Interrupted - when .hup? - ::Process::ExitReason::TerminalDisconnected - when .term? - ::Process::ExitReason::SessionEnded - else - ::Process::ExitReason::Interrupted - end - # maintain backwards compatibility if handler.is_a? Proc(Nil) handler.call else + int_type = case signal + when .int? + ::Process::ExitReason::Interrupted + when .hup? + ::Process::ExitReason::TerminalDisconnected + when .term? + ::Process::ExitReason::SessionEnded + else + ::Process::ExitReason::Interrupted + end handler.call int_type end From 78e80473961a0670014abc561eb6f3d7e36aa384 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 23 Jan 2024 11:21:34 +1100 Subject: [PATCH 20/28] deprecate on_interrupt and use on_terminate --- spec/std/process_spec.cr | 8 +++++ src/crystal/system/process.cr | 6 +++- src/crystal/system/unix/process.cr | 36 +++++++++---------- src/crystal/system/wasi/process.cr | 7 +++- src/crystal/system/win32/process.cr | 11 ++++-- src/lib_c/x86_64-windows-msvc/c/consoleapi.cr | 4 +-- src/process.cr | 15 +++++++- 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 9734ec5ea99c..f303cdb5862f 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -348,6 +348,14 @@ describe Process do end end + describe ".on_terminate" do + it "compiles" do + typeof(Process.on_terminate { }) + typeof(Process.ignore_interrupts!) + typeof(Process.restore_interrupts!) + end + end + {% unless flag?(:win32) %} describe "#signal(Signal::KILL)" do it "kills a process" do diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index c5eddaef8460..fcc08adbbec3 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -44,7 +44,11 @@ struct Crystal::System::Process # Installs *handler* as the new handler for interrupt requests. Removes any # previously set interrupt handler. - # def self.on_interrupt(&handler : ::Process::ExitReason ->) + # def self.on_interrupt(&handler : ->) + + # Installs *handler* as the new handler for termination signals. Removes any + # previously set handler. + # def self.on_terminate(&handler : ::Process::ExitReason ->) # Ignores all interrupt requests. Removes any custom interrupt handler set # def self.ignore_interrupts! diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 00d44884b7e4..4fd2b658cd59 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -58,29 +58,29 @@ struct Crystal::System::Process raise RuntimeError.from_errno("kill") if ret < 0 end - def self.on_interrupt(&handler) : Nil + @[Deprecated("Use `#on_terminate` instead")] + def self.on_interrupt(&handler : ->) : Nil + ::Signal::INT.trap { |_signal| handler.call } + end + + def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil sig_handler = Proc(::Signal, Nil).new do |signal| - # maintain backwards compatibility - if handler.is_a? Proc(Nil) - handler.call - else - int_type = case signal - when .int? - ::Process::ExitReason::Interrupted - when .hup? - ::Process::ExitReason::TerminalDisconnected - when .term? - ::Process::ExitReason::SessionEnded - else - ::Process::ExitReason::Interrupted - end - handler.call int_type - end + int_type = case signal + when .int? + ::Process::ExitReason::Interrupted + when .hup? + ::Process::ExitReason::TerminalDisconnected + when .term? + ::Process::ExitReason::SessionEnded + else + ::Process::ExitReason::Interrupted + end + handler.call int_type # ignore prevents system defaults and clears registered interrupts # hence we need to re-register signal.ignore - Process.on_interrupt &handler + Process.on_terminate &handler end ::Signal::INT.trap &sig_handler ::Signal::HUP.trap &sig_handler diff --git a/src/crystal/system/wasi/process.cr b/src/crystal/system/wasi/process.cr index 8cbc36968751..cae182f1ac50 100644 --- a/src/crystal/system/wasi/process.cr +++ b/src/crystal/system/wasi/process.cr @@ -48,10 +48,15 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end - def self.on_interrupt(&handler) : Nil + @[Deprecated("Use `#on_terminate` instead")] + def self.on_interrupt(&handler : ->) : Nil raise NotImplementedError.new("Process.on_interrupt") end + def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil + raise NotImplementedError.new("Process.on_terminate") + end + def self.ignore_interrupts! : Nil raise NotImplementedError.new("Process.ignore_interrupts!") end diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index b94907ff4b34..d0a27caf5e17 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -13,7 +13,7 @@ struct Crystal::System::Process @job_object : LibC::HANDLE @completion_key = IO::Overlapped::CompletionKey.new - @@interrupt_handler : Proc(::Process::ExitReason, Nil) | Proc(Nil)? + @@interrupt_handler : Proc(::Process::ExitReason, Nil)? @@interrupt_count = Crystal::AtomicSemaphore.new @@win32_interrupt_handler : LibC::PHANDLER_ROUTINE? @@setup_interrupt_handler = Atomic::Flag.new @@ -151,7 +151,14 @@ struct Crystal::System::Process raise NotImplementedError.new("Process.signal") end - def self.on_interrupt(&@@interrupt_handler) : Nil + @[Deprecated("Use `#on_terminate` instead")] + def self.on_interrupt(&handler : ->) : Nil + on_terminate do |reason| + handler.call if reason.interrupted? + end + end + + def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil restore_interrupts! @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| @@last_interrupt = case event_type diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr index 7a90e7d4bdd5..fe2fbe381d03 100644 --- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr @@ -22,8 +22,8 @@ lib LibC pInputControl : Void* ) : BOOL - CTRL_C_EVENT = 0 - CTRL_BREAK_EVENT = 1 + CTRL_C_EVENT = 0 + CTRL_BREAK_EVENT = 1 CTRL_CLOSE_EVENT = 2 CTRL_LOGOFF_EVENT = 5 CTRL_SHUTDOWN_EVENT = 6 diff --git a/src/process.cr b/src/process.cr index fc51deec1de4..aa9dda9973f8 100644 --- a/src/process.cr +++ b/src/process.cr @@ -50,6 +50,19 @@ class Process Crystal::System::Process.signal(pid, signal.value) end + # Installs *handler* as the new handler for interrupt requests. Removes any + # previously set interrupt handler. + # + # The handler is executed on a fresh fiber every time an interrupt occurs. + # + # * On Unix-like systems, this traps `SIGINT`. + # * On Windows, this captures Ctrl + C and + # Ctrl + Break signals sent to a console application. + @[Deprecated("Use `#on_terminate` instead")] + def self.on_interrupt(&handler : ->) : Nil + Crystal::System::Process.on_interrupt(&handler) + end + # Installs *handler* as the new handler for interrupt requests. Removes any # previously set interrupt handler. # @@ -59,7 +72,7 @@ class Process # * On Windows, this captures Ctrl + C, # Ctrl + Break, terminal close, windows logoff # and shutdown signals sent to a console application. - def self.on_interrupt(&handler) : Nil + def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil Crystal::System::Process.on_interrupt(&handler) end From d70ee3389f697977ae5a3b951fa44775e3dae790 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 23 Jan 2024 11:26:18 +1100 Subject: [PATCH 21/28] fix call to on_terminate --- src/process.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process.cr b/src/process.cr index aa9dda9973f8..c96ff83d49dc 100644 --- a/src/process.cr +++ b/src/process.cr @@ -73,11 +73,11 @@ class Process # Ctrl + Break, terminal close, windows logoff # and shutdown signals sent to a console application. def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil - Crystal::System::Process.on_interrupt(&handler) + Crystal::System::Process.on_terminate(&handler) end # Ignores all interrupt requests. Removes any custom interrupt handler set - # with `#on_interrupt`. + # with `#on_terminate`. # # * On Windows, interrupts generated by Ctrl + Break # cannot be ignored in this way. From f460d5c7624da5d367365a55566f311e34698215 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 23 Jan 2024 11:57:53 +1100 Subject: [PATCH 22/28] fix win32 --- src/crystal/system/win32/process.cr | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index d0a27caf5e17..eaa91ad7eb5a 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -160,6 +160,7 @@ struct Crystal::System::Process def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil restore_interrupts! + @@interrupt_handler = handler @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| @@last_interrupt = case event_type when LibC::CTRL_C_EVENT, LibC::CTRL_BREAK_EVENT @@ -205,11 +206,7 @@ struct Crystal::System::Process non_nil_handler = handler # if handler is closured it will also have the Nil type int_type = @@last_interrupt spawn do - if non_nil_handler.arity == 0 - non_nil_handler.as(Proc(Nil)).call - else - non_nil_handler.as(Proc(::Process::ExitReason, Nil)).call int_type - end + non_nil_handler.call int_type rescue ex ex.inspect_with_backtrace(STDERR) STDERR.puts("FATAL: uncaught exception while processing interrupt handler, exiting") From a740d4a75fde68b326d648774900fa8c7675051e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 23 Jan 2024 11:59:44 +1100 Subject: [PATCH 23/28] improve win32 on_terminate --- src/crystal/system/win32/process.cr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index eaa91ad7eb5a..a8a89f442fab 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -158,9 +158,8 @@ struct Crystal::System::Process end end - def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil + def self.on_terminate(&@@interrupt_handler : ::Process::ExitReason ->) : Nil restore_interrupts! - @@interrupt_handler = handler @@win32_interrupt_handler = handler = LibC::PHANDLER_ROUTINE.new do |event_type| @@last_interrupt = case event_type when LibC::CTRL_C_EVENT, LibC::CTRL_BREAK_EVENT From 757079b42cfef9c9b9d871748b8a5684da99d94e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 23 Jan 2024 12:34:01 +1100 Subject: [PATCH 24/28] update docs --- src/signal.cr | 6 +++--- src/spec.cr | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/signal.cr b/src/signal.cr index 2e085b92311e..50360b73c511 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -40,8 +40,8 @@ require "crystal/system/signal" # The standard library provides several platform-agnostic APIs to achieve tasks # that are typically solved with signals on POSIX systems: # -# * The portable API for responding to an interrupt signal (`INT.trap`) is -# `Process.on_interrupt`. +# * The portable API for responding to a termination request is +# `Process.on_terminate`. # * The portable API for sending a `TERM` or `KILL` signal to a process is # `Process#terminate`. # * The portable API for retrieving the exit signal of a process @@ -105,7 +105,7 @@ enum Signal : Int32 # check child processes using `Process.exists?`. Trying to use waitpid with a # zero or negative value won't work. # - # NOTE: `Process.on_interrupt` is preferred over `Signal::INT.trap` as a + # NOTE: `Process.on_terminate` is preferred over `Signal::INT.trap` as a # portable alternative which also works on Windows. def trap(&handler : Signal ->) : Nil {% if @type.has_constant?("CHLD") %} diff --git a/src/spec.cr b/src/spec.cr index c51ee8831de4..474aa7c5a0dc 100644 --- a/src/spec.cr +++ b/src/spec.cr @@ -129,8 +129,8 @@ module Spec add_split_filter ENV["SPEC_SPLIT"]? {% unless flag?(:wasm32) %} - # TODO(wasm): Enable this once `Process.on_interrupt` is implemented - Process.on_interrupt { abort! } + # TODO(wasm): Enable this once `Process.on_terminate` is implemented + Process.on_terminate { abort! } {% end %} run From 79cbc17434cfef53bfc3f966320a7730528aa829 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 24 Jan 2024 13:12:38 +1100 Subject: [PATCH 25/28] change wording in description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/process.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process.cr b/src/process.cr index c96ff83d49dc..6f324ae5fd85 100644 --- a/src/process.cr +++ b/src/process.cr @@ -63,8 +63,8 @@ class Process Crystal::System::Process.on_interrupt(&handler) end - # Installs *handler* as the new handler for interrupt requests. Removes any - # previously set interrupt handler. + # Installs *handler* as the new handler for termination requests. Removes any + # previously set termination handler. # # The handler is executed on a fresh fiber every time an interrupt occurs. # From bc66bdbec2b0c98399d3476ee7ea583d9deb843b Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 24 Jan 2024 13:13:07 +1100 Subject: [PATCH 26/28] add example code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/process.cr | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/process.cr b/src/process.cr index 6f324ae5fd85..d17fc55884d1 100644 --- a/src/process.cr +++ b/src/process.cr @@ -72,6 +72,26 @@ class Process # * On Windows, this captures Ctrl + C, # Ctrl + Break, terminal close, windows logoff # and shutdown signals sent to a console application. + # + # ``` + # wait_channel = Channel(Nil).new + # + # Process.on_terminate do |reason| + # case reason + # when .interrupted? + # puts "terminating gracefully" + # wait_channel.close + # when .terminal_disconnected? + # puts "reloading configuration" + # when .session_ended? + # puts "terminating forcefully" + # Process.exit + # end + # end + # + # wait_channel.receive + # puts "bye" + # ``` def self.on_terminate(&handler : ::Process::ExitReason ->) : Nil Crystal::System::Process.on_terminate(&handler) end From 2b91b7d6be294e26290ab88362d5d546673f6abc Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 24 Jan 2024 13:17:15 +1100 Subject: [PATCH 27/28] crystal tool format --- src/process.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/process.cr b/src/process.cr index d17fc55884d1..a1b827d73754 100644 --- a/src/process.cr +++ b/src/process.cr @@ -72,7 +72,7 @@ class Process # * On Windows, this captures Ctrl + C, # Ctrl + Break, terminal close, windows logoff # and shutdown signals sent to a console application. - # + # # ``` # wait_channel = Channel(Nil).new # From 962d6546852cc9e151299529e433ea4350728add Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Sun, 4 Feb 2024 10:35:29 +1100 Subject: [PATCH 28/28] fix comments and specs for new enum values --- spec/std/process/status_spec.cr | 10 ++++++++-- src/compiler/crystal/command.cr | 2 +- src/process/status.cr | 10 +++++++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 45f60aba4c06..470a0a1a34d9 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -149,10 +149,16 @@ describe Process::Status do it "returns Aborted" do Process::Status.new(Signal::ABRT.value).exit_reason.aborted?.should be_true - Process::Status.new(Signal::HUP.value).exit_reason.aborted?.should be_true Process::Status.new(Signal::KILL.value).exit_reason.aborted?.should be_true Process::Status.new(Signal::QUIT.value).exit_reason.aborted?.should be_true - Process::Status.new(Signal::TERM.value).exit_reason.aborted?.should be_true + end + + it "returns TerminalDisconnected" do + Process::Status.new(Signal::HUP.value).exit_reason.terminal_disconnected?.should be_true + end + + it "returns SessionEnded" do + Process::Status.new(Signal::TERM.value).exit_reason.session_ended?.should be_true end it "returns Interrupted" do diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index b51f9405c4e6..431c9114a6b7 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -312,7 +312,7 @@ class Crystal::Command private def exit_message(status) case status.exit_reason - when .aborted? + when .aborted?, .session_ended?, .terminal_disconnected? if status.signal_exit? signal = status.exit_signal if signal.kill? diff --git a/src/process/status.cr b/src/process/status.cr index 52386ba68833..de29351ff12f 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -16,8 +16,8 @@ enum Process::ExitReason # The process terminated abnormally. # - # * On Unix-like systems, this corresponds to `Signal::ABRT`, `Signal::HUP`, - # `Signal::KILL`, `Signal::QUIT`, and `Signal::TERM`. + # * On Unix-like systems, this corresponds to `Signal::ABRT`, `Signal::KILL`, + # and `Signal::QUIT`. # * On Windows, this corresponds to the `NTSTATUS` value # `STATUS_FATAL_APP_EXIT`. Aborted @@ -141,8 +141,12 @@ class Process::Status case Signal.from_value?(signal_code) when Nil ExitReason::Signal - when .abrt?, .hup?, .kill?, .quit?, .term? + when .abrt?, .kill?, .quit? ExitReason::Aborted + when .hup? + ExitReason::TerminalDisconnected + when .term? + ExitReason::SessionEnded when .int? ExitReason::Interrupted when .trap?