Skip to content

Commit

Permalink
Make Crystal::IOCP::OverlappedOperation abstract
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil committed Sep 7, 2024
1 parent 025f3e0 commit c092dbc
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 66 deletions.
4 changes: 2 additions & 2 deletions src/crystal/system/win32/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ module Crystal::System::FileDescriptor
end

private def lock_file(handle, flags)
IOCP::OverlappedOperation.run(handle) do |operation|
IOCP::IOOverlappedOperation.run(handle) do |operation|
result = LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation)

if result == 0
Expand All @@ -260,7 +260,7 @@ module Crystal::System::FileDescriptor
end

private def unlock_file(handle)
IOCP::OverlappedOperation.run(handle) do |operation|
IOCP::IOOverlappedOperation.run(handle) do |operation|
result = LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation)

if result == 0
Expand Down
125 changes: 65 additions & 60 deletions src/crystal/system/win32/iocp.cr
Original file line number Diff line number Diff line change
Expand Up @@ -78,78 +78,60 @@ module Crystal::IOCP
end
end

class OverlappedOperation
abstract class OverlappedOperation
enum State
STARTED
DONE
end

abstract def wait_for_result(timeout, & : WinError ->)

@overlapped = LibC::OVERLAPPED.new
@fiber = Fiber.current
@state : State = :started

def initialize(@handle : LibC::HANDLE)
end

def initialize(handle : LibC::SOCKET)
@handle = LibC::HANDLE.new(handle)
end

def self.run(handle, &)
operation_storage = uninitialized ReferenceStorage(OverlappedOperation)
operation = OverlappedOperation.unsafe_construct(pointerof(operation_storage), handle)
def self.run(*args, **opts, &)
operation_storage = uninitialized ReferenceStorage(self)
operation = unsafe_construct(pointerof(operation_storage), *args, **opts)
yield operation
end

def self.unbox(overlapped : LibC::OVERLAPPED*)
start = overlapped.as(Pointer(UInt8)) - offsetof(OverlappedOperation, @overlapped)
Box(OverlappedOperation).unbox(start.as(Pointer(Void)))
def self.unbox(overlapped : LibC::OVERLAPPED*) : self
start = overlapped.as(Pointer(UInt8)) - offsetof(self, @overlapped)
Box(self).unbox(start.as(Pointer(Void)))
end

def to_unsafe
pointerof(@overlapped)
end

def wait_for_result(timeout, &)
wait_for_completion(timeout)

result = LibC.GetOverlappedResult(@handle, self, out bytes, 0)
if result.zero?
error = WinError.value
yield error

raise IO::Error.from_os_error("GetOverlappedResult", error)
end

bytes
end

def wait_for_wsa_result(timeout, &)
wait_for_completion(timeout)

flags = 0_u32
result = LibC.WSAGetOverlappedResult(LibC::SOCKET.new(@handle.address), self, out bytes, false, pointerof(flags))
if result.zero?
error = WinError.wsa_value
yield error

raise IO::Error.from_os_error("WSAGetOverlappedResult", error)
end

bytes
end

protected def schedule(&)
done!
yield @fiber
end

def done!
private def done!
@fiber.cancel_timeout
@state = :done
end

def try_cancel : Bool
private def wait_for_completion(timeout)
if timeout
sleep timeout
else
Fiber.suspend
end

unless @state.done?
if try_cancel
# Wait for cancellation to complete. We must not free the operation
# until it's completed.
Fiber.suspend
end
end
end

private def try_cancel : Bool
# Microsoft documentation:
# The application must not free or reuse the OVERLAPPED structure
# associated with the canceled I/O operations until they have completed
Expand All @@ -162,34 +144,57 @@ module Crystal::IOCP
# Operation has already completed, do nothing
return false
else
raise RuntimeError.from_os_error("CancelIOEx", os_error: error)
raise RuntimeError.from_os_error("CancelIoEx", os_error: error)
end
end
true
end
end

def wait_for_completion(timeout)
if timeout
sleep timeout
else
Fiber.suspend
class IOOverlappedOperation < OverlappedOperation
def initialize(@handle : LibC::HANDLE)
end

def wait_for_result(timeout, & : WinError ->)
wait_for_completion(timeout)

result = LibC.GetOverlappedResult(@handle, self, out bytes, 0)
if result.zero?
error = WinError.value
yield error

raise IO::Error.from_os_error("GetOverlappedResult", error)
end

unless @state.done?
if try_cancel
# Wait for cancellation to complete. We must not free the operation
# until it's completed.
Fiber.suspend
end
bytes
end
end

class WSAOverlappedOperation < OverlappedOperation
def initialize(@handle : LibC::SOCKET)
end

def wait_for_result(timeout, & : WinError ->)
wait_for_completion(timeout)

flags = 0_u32
result = LibC.WSAGetOverlappedResult(@handle, self, out bytes, false, pointerof(flags))
if result.zero?
error = WinError.wsa_value
yield error

raise IO::Error.from_os_error("WSAGetOverlappedResult", error)
end

bytes
end
end

def self.overlapped_operation(file_descriptor, method, timeout, *, offset = nil, writing = false, &)
handle = file_descriptor.windows_handle
seekable = LibC.SetFilePointerEx(handle, 0, out original_offset, IO::Seek::Current) != 0

OverlappedOperation.run(handle) do |operation|
IOOverlappedOperation.run(handle) do |operation|
overlapped = operation.to_unsafe
if seekable
start_offset = offset || original_offset
Expand Down Expand Up @@ -243,7 +248,7 @@ module Crystal::IOCP
end

def self.wsa_overlapped_operation(target, socket, method, timeout, connreset_is_error = true, &)
OverlappedOperation.run(socket) do |operation|
WSAOverlappedOperation.run(socket) do |operation|
result, value = yield operation

if result == LibC::SOCKET_ERROR
Expand All @@ -257,7 +262,7 @@ module Crystal::IOCP
return value
end

operation.wait_for_wsa_result(timeout) do |error|
operation.wait_for_result(timeout) do |error|
case error
when .wsa_io_incomplete?, .error_operation_aborted?
raise IO::TimeoutError.new("#{method} timed out")
Expand Down
8 changes: 4 additions & 4 deletions src/crystal/system/win32/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ module Crystal::System::Socket

# :nodoc:
def overlapped_connect(socket, method, timeout, &)
IOCP::OverlappedOperation.run(socket) do |operation|
IOCP::WSAOverlappedOperation.run(socket) do |operation|
result = yield operation

if result == 0
Expand All @@ -145,7 +145,7 @@ module Crystal::System::Socket
return nil
end

operation.wait_for_wsa_result(timeout) do |error|
operation.wait_for_result(timeout) do |error|
case error
when .wsa_io_incomplete?, .wsaeconnrefused?
return ::Socket::ConnectError.from_os_error(method, error)
Expand Down Expand Up @@ -192,7 +192,7 @@ module Crystal::System::Socket
end

def overlapped_accept(socket, method, &)
IOCP::OverlappedOperation.run(socket) do |operation|
IOCP::WSAOverlappedOperation.run(socket) do |operation|
result = yield operation

if result == 0
Expand All @@ -206,7 +206,7 @@ module Crystal::System::Socket
return true
end

operation.wait_for_wsa_result(read_timeout) do |error|
operation.wait_for_result(read_timeout) do |error|
case error
when .wsa_io_incomplete?, .wsaenotsock?
return false
Expand Down

0 comments on commit c092dbc

Please sign in to comment.