Skip to content

Commit 7e33ecc

Browse files
Refactor IOCP::OverlappedOperation internalize handle and wait_for_completion (#14724)
* Add ivar `@handle` * Internalize `schedule_overlapped` as `wait_for_completion` * Add temporary special case for `overlapped_accept`
1 parent a1db18c commit 7e33ecc

File tree

2 files changed

+42
-36
lines changed

2 files changed

+42
-36
lines changed

src/crystal/system/win32/iocp.cr

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,19 @@ module Crystal::IOCP
7575
property previous : OverlappedOperation?
7676
@@canceled = Thread::LinkedList(OverlappedOperation).new
7777

78+
def initialize(@handle : LibC::HANDLE)
79+
end
80+
81+
def initialize(handle : LibC::SOCKET)
82+
@handle = LibC::HANDLE.new(handle)
83+
end
84+
7885
def self.run(handle, &)
79-
operation = OverlappedOperation.new
86+
operation = OverlappedOperation.new(handle)
8087
begin
8188
yield operation
8289
ensure
83-
operation.done(handle)
90+
operation.done
8491
end
8592
end
8693

@@ -93,9 +100,12 @@ module Crystal::IOCP
93100
pointerof(@overlapped)
94101
end
95102

96-
def result(handle, &)
103+
def wait_for_result(timeout, &)
104+
wait_for_completion(timeout)
105+
97106
raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started?
98-
result = LibC.GetOverlappedResult(handle, self, out bytes, 0)
107+
108+
result = LibC.GetOverlappedResult(@handle, self, out bytes, 0)
99109
if result.zero?
100110
error = WinError.value
101111
yield error
@@ -106,10 +116,15 @@ module Crystal::IOCP
106116
bytes
107117
end
108118

109-
def wsa_result(socket, &)
119+
def wait_for_wsa_result(timeout, &)
120+
wait_for_completion(timeout)
121+
wsa_result { |error| yield error }
122+
end
123+
124+
def wsa_result(&)
110125
raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started?
111126
flags = 0_u32
112-
result = LibC.WSAGetOverlappedResult(socket, self, out bytes, false, pointerof(flags))
127+
result = LibC.WSAGetOverlappedResult(LibC::SOCKET.new(@handle.address), self, out bytes, false, pointerof(flags))
113128
if result.zero?
114129
error = WinError.wsa_value
115130
yield error
@@ -132,15 +147,13 @@ module Crystal::IOCP
132147
end
133148
end
134149

135-
protected def done(handle)
150+
protected def done
136151
case @state
137152
when .started?
138-
handle = LibC::HANDLE.new(handle) if handle.is_a?(LibC::SOCKET)
139-
140153
# https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-cancelioex
141154
# > The application must not free or reuse the OVERLAPPED structure
142155
# associated with the canceled I/O operations until they have completed
143-
if LibC.CancelIoEx(handle, self) != 0
156+
if LibC.CancelIoEx(@handle, self) != 0
144157
@state = :cancelled
145158
@@canceled.push(self) # to increase lifetime
146159
end
@@ -150,24 +163,23 @@ module Crystal::IOCP
150163
def done!
151164
@state = :done
152165
end
153-
end
154166

155-
# Returns `false` if the operation timed out.
156-
def self.schedule_overlapped(timeout : Time::Span?, line = __LINE__) : Bool
157-
if timeout
158-
timeout_event = Crystal::IOCP::Event.new(Fiber.current)
159-
timeout_event.add(timeout)
160-
else
161-
timeout_event = Crystal::IOCP::Event.new(Fiber.current, Time::Span::MAX)
162-
end
163-
# memoize event loop to make sure that we still target the same instance
164-
# after wakeup (guaranteed by current MT model but let's be future proof)
165-
event_loop = Crystal::EventLoop.current
166-
event_loop.enqueue(timeout_event)
167+
def wait_for_completion(timeout)
168+
if timeout
169+
timeout_event = Crystal::IOCP::Event.new(Fiber.current)
170+
timeout_event.add(timeout)
171+
else
172+
timeout_event = Crystal::IOCP::Event.new(Fiber.current, Time::Span::MAX)
173+
end
174+
# memoize event loop to make sure that we still target the same instance
175+
# after wakeup (guaranteed by current MT model but let's be future proof)
176+
event_loop = Crystal::EventLoop.current
177+
event_loop.enqueue(timeout_event)
167178

168-
Fiber.suspend
179+
Fiber.suspend
169180

170-
event_loop.dequeue(timeout_event)
181+
event_loop.dequeue(timeout_event)
182+
end
171183
end
172184

173185
def self.overlapped_operation(target, handle, method, timeout, *, writing = false, &)
@@ -192,9 +204,7 @@ module Crystal::IOCP
192204
return value
193205
end
194206

195-
schedule_overlapped(timeout)
196-
197-
operation.result(handle) do |error|
207+
operation.wait_for_result(timeout) do |error|
198208
case error
199209
when .error_io_incomplete?
200210
raise IO::TimeoutError.new("#{method} timed out")
@@ -224,9 +234,7 @@ module Crystal::IOCP
224234
return value
225235
end
226236

227-
schedule_overlapped(timeout)
228-
229-
operation.wsa_result(socket) do |error|
237+
operation.wait_for_wsa_result(timeout) do |error|
230238
case error
231239
when .wsa_io_incomplete?
232240
raise IO::TimeoutError.new("#{method} timed out")

src/crystal/system/win32/socket.cr

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,7 @@ module Crystal::System::Socket
146146
return nil
147147
end
148148

149-
IOCP.schedule_overlapped(read_timeout || 1.seconds)
150-
151-
operation.wsa_result(socket) do |error|
149+
operation.wait_for_wsa_result(read_timeout || 1.seconds) do |error|
152150
case error
153151
when .wsa_io_incomplete?, .wsaeconnrefused?
154152
return ::Socket::ConnectError.from_os_error(method, error)
@@ -210,11 +208,11 @@ module Crystal::System::Socket
210208
return true
211209
end
212210

213-
unless IOCP.schedule_overlapped(read_timeout)
211+
unless operation.wait_for_completion(read_timeout)
214212
raise IO::TimeoutError.new("#{method} timed out")
215213
end
216214

217-
operation.wsa_result(socket) do |error|
215+
operation.wsa_result do |error|
218216
case error
219217
when .wsa_io_incomplete?, .wsaenotsock?
220218
return false

0 commit comments

Comments
 (0)