Skip to content

Commit

Permalink
Add methods to Crystal::EventLoop (#14977)
Browse files Browse the repository at this point in the history
Add `#after_fork_before_exec` to allow an evloop to do some cleanup before exec (UNIX only).
Add `#remove(io)` to allow an evloop to free resources when the IO is closed in a GC finalizer.
  • Loading branch information
ysbaddaden authored Sep 9, 2024
1 parent cdd9ccf commit bdddae7
Show file tree
Hide file tree
Showing 12 changed files with 61 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/crystal/scheduler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class Crystal::Scheduler
Thread.current.scheduler.@event_loop
end

def self.event_loop?
if scheduler = Thread.current?.try(&.scheduler?)
scheduler.@event_loop
end
end

def self.enqueue(fiber : Fiber) : Nil
Crystal.trace :sched, "enqueue", fiber: fiber do
thread = Thread.current
Expand Down
5 changes: 5 additions & 0 deletions src/crystal/system/event_loop.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ abstract class Crystal::EventLoop
Crystal::Scheduler.event_loop
end

@[AlwaysInline]
def self.current? : self?
Crystal::Scheduler.event_loop?
end

# Runs the loop.
#
# Returns immediately if events are activable. Set `blocking` to false to
Expand Down
8 changes: 8 additions & 0 deletions src/crystal/system/event_loop/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,13 @@ abstract class Crystal::EventLoop

# Closes the file descriptor resource.
abstract def close(file_descriptor : Crystal::System::FileDescriptor) : Nil

# Removes the file descriptor from the event loop. Can be used to free up
# memory resources associated with the file descriptor, as well as removing
# the file descriptor from kernel data structures.
#
# Called by `::IO::FileDescriptor#finalize` before closing the file
# descriptor. Errors shall be silently ignored.
abstract def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
end
end
8 changes: 8 additions & 0 deletions src/crystal/system/event_loop/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,13 @@ abstract class Crystal::EventLoop

# Closes the socket.
abstract def close(socket : ::Socket) : Nil

# Removes the socket from the event loop. Can be used to free up memory
# resources associated with the socket, as well as removing the socket from
# kernel data structures.
#
# Called by `::Socket#finalize` before closing the socket. Errors shall be
# silently ignored.
abstract def remove(socket : ::Socket) : Nil
end
end
4 changes: 4 additions & 0 deletions src/crystal/system/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ module Crystal::System::FileDescriptor
event_loop.write(self, slice)
end

private def event_loop? : Crystal::EventLoop::FileDescriptor?
Crystal::EventLoop.current?
end

private def event_loop : Crystal::EventLoop::FileDescriptor
Crystal::EventLoop.current
end
Expand Down
4 changes: 4 additions & 0 deletions src/crystal/system/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ module Crystal::System::Socket
# Also used in `Socket#finalize`
# def socket_close

private def event_loop? : Crystal::EventLoop::Socket?
Crystal::EventLoop.current?
end

private def event_loop : Crystal::EventLoop::Socket
Crystal::EventLoop.current
end
Expand Down
9 changes: 9 additions & 0 deletions src/crystal/system/unix/event_loop_libevent.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ require "./event_libevent"
class Crystal::LibEvent::EventLoop < Crystal::EventLoop
private getter(event_base) { Crystal::LibEvent::Event::Base.new }

def after_fork_before_exec : Nil
end

{% unless flag?(:preview_mt) %}
# Reinitializes the event loop after a fork.
def after_fork : Nil
Expand Down Expand Up @@ -93,6 +96,9 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
file_descriptor.evented_close
end

def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
end

def read(socket : ::Socket, slice : Bytes) : Int32
evented_read(socket, "Error reading socket") do
LibC.recv(socket.fd, slice, slice.size, 0).to_i32
Expand Down Expand Up @@ -186,6 +192,9 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
socket.evented_close
end

def remove(socket : ::Socket) : Nil
end

def evented_read(target, errno_msg : String, &) : Int32
loop do
bytes_read = yield
Expand Down
3 changes: 3 additions & 0 deletions src/crystal/system/unix/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ struct Crystal::System::Process
# child:
pid = nil
if will_exec
# notify event loop
Crystal::EventLoop.current.after_fork_before_exec

# reset signal handlers, then sigmask (inherited on exec):
Crystal::System::Signal.after_fork_before_exec
LibC.sigemptyset(pointerof(newmask))
Expand Down
6 changes: 6 additions & 0 deletions src/crystal/system/wasi/event_loop.cr
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
file_descriptor.evented_close
end

def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
end

def read(socket : ::Socket, slice : Bytes) : Int32
evented_read(socket, "Error reading socket") do
LibC.recv(socket.fd, slice, slice.size, 0).to_i32
Expand Down Expand Up @@ -85,6 +88,9 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
socket.evented_close
end

def remove(socket : ::Socket) : Nil
end

def evented_read(target, errno_msg : String, &) : Int32
loop do
bytes_read = yield
Expand Down
6 changes: 6 additions & 0 deletions src/crystal/system/win32/event_loop_iocp.cr
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop
LibC.CancelIoEx(file_descriptor.windows_handle, nil) unless file_descriptor.system_blocking?
end

def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
end

private def wsa_buffer(bytes)
wsabuf = LibC::WSABUF.new
wsabuf.len = bytes.size
Expand Down Expand Up @@ -271,6 +274,9 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop

def close(socket : ::Socket) : Nil
end

def remove(socket : ::Socket) : Nil
end
end

class Crystal::IOCP::Event
Expand Down
1 change: 1 addition & 0 deletions src/io/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ class IO::FileDescriptor < IO
def finalize
return if closed? || !close_on_finalize?

event_loop?.try(&.remove(self))
file_descriptor_close { } # ignore error
end

Expand Down
1 change: 1 addition & 0 deletions src/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ class Socket < IO
def finalize
return if closed?

event_loop?.try(&.remove(self))
socket_close { } # ignore error
end

Expand Down

0 comments on commit bdddae7

Please sign in to comment.