Skip to content

Commit

Permalink
daemon: Fix slow start with Ruby 3
Browse files Browse the repository at this point in the history
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 chef#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 <fukuda@clear-code.com>
  • Loading branch information
daipom committed Feb 26, 2024
1 parent 67a0f7a commit cfcc200
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 34 deletions.
48 changes: 16 additions & 32 deletions lib/win32/daemon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions lib/win32/windows/functions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@ 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
attach_pfunc :InitializeCriticalSection, [:ptr], :void
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

Expand Down

0 comments on commit cfcc200

Please sign in to comment.