From cfcc2007b3843127329d6a7307a62a927a714327 Mon Sep 17 00:00:00 2001 From: Daijiro Fukuda Date: Mon, 26 Feb 2024 17:50:56 +0900 Subject: [PATCH] daemon: Fix slow start with Ruby 3 This does not change any specification. This just changes to use a Ruby thread instead of `CreateThread`. It is very very fast to use a Ruby thread from the beginning. Service startup is noticeably slower with Ruby 3. (Please see https://github.com/chef/win32-service/issues/84). Windows service needs to finish `StartServiceCtrlDispatcher` in 30 seconds by default. With Ruby 3 or later, this problem can cause timeout errors. The more CPUs, the slower it tends to be. Maybe some implementation about threading has changed with Ruby 3 series. The current implementation switches between multiple Ruby threads and non-Ruby threads. This complex process may be unsuitable for Ruby 3. `CreateThread` makes a non-Ruby thread, and it calls Ruby Proc, and it makes a Ruby thread, and ... Signed-off-by: Daijiro Fukuda --- lib/win32/daemon.rb | 48 ++++++++++++---------------------- lib/win32/windows/functions.rb | 2 -- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/lib/win32/daemon.rb b/lib/win32/daemon.rb index b2ff825..4e405c9 100644 --- a/lib/win32/daemon.rb +++ b/lib/win32/daemon.rb @@ -186,25 +186,6 @@ class Daemon end end - ThreadProc = FFI::Function.new(:ulong, [:pointer]) do |lpParameter| - ste = FFI::MemoryPointer.new(SERVICE_TABLE_ENTRY, 2) - - s = SERVICE_TABLE_ENTRY.new(ste[0]) - s[:lpServiceName] = FFI::MemoryPointer.from_string("") - s[:lpServiceProc] = lpParameter - - s = SERVICE_TABLE_ENTRY.new(ste[1]) - s[:lpServiceName] = nil - s[:lpServiceProc] = nil - - # No service to step, no service handle, no ruby exceptions, just terminate the thread.. - unless StartServiceCtrlDispatcher(ste) - return 1 - end - - return 0 - end - # This is a shortcut for Daemon.new + Daemon#mainloop. # def self.mainloop @@ -254,26 +235,29 @@ def mainloop raise SystemCallError.new("CreateEvent", FFI.errno) end - hThread = CreateThread(nil, 0, ThreadProc, Service_Main, 0, nil) + hThread = Thread.new(Service_Main) do |lp_proc| + ste = FFI::MemoryPointer.new(SERVICE_TABLE_ENTRY, 2) - if hThread == 0 - raise SystemCallError.new("CreateThread", FFI.errno) - end + s = SERVICE_TABLE_ENTRY.new(ste[0]) + s[:lpServiceName] = FFI::MemoryPointer.from_string("") + s[:lpServiceProc] = lp_proc - events = FFI::MemoryPointer.new(:pointer, 2) - events.put_pointer(0, FFI::Pointer.new(hThread)) - events.put_pointer(FFI::Pointer.size, FFI::Pointer.new(@@hStartEvent)) + s = SERVICE_TABLE_ENTRY.new(ste[1]) + s[:lpServiceName] = nil + s[:lpServiceProc] = nil - while (index = WaitForMultipleObjects(2, events, 0, 1000)) == WAIT_TIMEOUT + # When returning 'false', there is no service to stop, no service handle, + # no ruby exceptions, just terminate the thread. + StartServiceCtrlDispatcher(ste) end - if index == WAIT_FAILED - raise SystemCallError.new("WaitForMultipleObjects", FFI.errno) + while (index = WaitForSingleObject(@@hStartEvent, 1000)) == WAIT_TIMEOUT + # The thread exited, so the show is off. + raise "Service_Main thread exited abnormally" unless hThread.alive? end - # The thread exited, so the show is off. - if index == WAIT_OBJECT_0 - raise "Service_Main thread exited abnormally" + if index == WAIT_FAILED + raise SystemCallError.new("WaitForSingleObject", FFI.errno) end thr = Thread.new do diff --git a/lib/win32/windows/functions.rb b/lib/win32/windows/functions.rb index 85568bd..96a98f9 100644 --- a/lib/win32/windows/functions.rb +++ b/lib/win32/windows/functions.rb @@ -23,7 +23,6 @@ def attach_pfunc(*args) attach_pfunc :CloseHandle, [:handle], :bool attach_pfunc :CreateEvent, :CreateEventA, %i{ptr int int str}, :handle - attach_pfunc :CreateThread, %i{ptr size_t ptr ptr dword ptr}, :handle, blocking: true attach_pfunc :EnterCriticalSection, [:ptr], :void attach_pfunc :FormatMessage, :FormatMessageA, %i{ulong ptr ulong ulong str ulong ptr}, :ulong attach_pfunc :GetCurrentProcess, [], :handle @@ -31,7 +30,6 @@ def attach_pfunc(*args) attach_pfunc :LeaveCriticalSection, [:ptr], :void attach_pfunc :SetEvent, [:handle], :bool attach_pfunc :WaitForSingleObject, %i{handle dword}, :dword, blocking: true - attach_pfunc :WaitForMultipleObjects, %i{dword ptr int dword}, :dword ffi_lib :advapi32