Skip to content

Refactor Socket blocking mode#15804

Merged
straight-shoota merged 7 commits intocrystal-lang:masterfrom
ysbaddaden:refactor/socket-nonblocking
May 26, 2025
Merged

Refactor Socket blocking mode#15804
straight-shoota merged 7 commits intocrystal-lang:masterfrom
ysbaddaden:refactor/socket-nonblocking

Conversation

@ysbaddaden
Copy link
Collaborator

@ysbaddaden ysbaddaden commented May 20, 2025

Refactors the handling of the Socket blocking mode.

Overall the stdlib now asks the event loop to create the sockets, and the event loop (opinionated) might use the general helpers from Crystal::System::Socket to create the sockets, then further refine it.

The event loop also reports whether the blocking mode was set, or not, because win32 doesn't have a mean to ask whether the blocking mode has been set and we must record it (WSAIsBlocking is deprecated and only available in older Winsock 1.1 DLL).

  1. Changes the blocking arg from false to nil by default, which means that each event loop now decides to set the mode to non-blocking or not (breaking change of undocumented, but expected, behavior).

  2. Moves the fd/handle creation to the event loops, so they can decide to set the overlapped flag (win32) or blocking mode (unix) as they need.

  3. Improves the win32 IOCP event loop to not set the blocking mode because it only needs the overlapped flag; it also moves the creation of the port completion to the event loop.

  4. Reworks the *Socket initializers to not set the blocking arg for internal constructors (already set).

  5. The public *Socket initializers from a raw fd/handle still set the blocking mode, when nil it asks the event loop.

    FIXME: We probably want to keep their blocking arg to false by default? We should ask the event loop when it's nil.

NOTES:

  • The blocking arg defaulting to nil isn't an immediate breaking change on UNIX: the libevent, epoll and kqueue event loops will keep setting the non-blocking mode. However, on win32, we won't set the socket to non-blocking anymore (no need, we use overlapped), and the io_uring event loop will keep the socket in blocking mode; these are behavior changes.

  • We should deprecate/remove support for the blocking arg. Only Socket and TCPSocket actually have it, neither of TCPServer, UNIXSocket or UNIXServer do, for unknown reasons.

    If we need to set the mode, for example to interact with an external C library, we can manually call #blocking=.

    I tried to use the deprecated annotation, but we can't because the blocking arg comes after args with default values, and we can't use the usual distinct methods trick (one with/out the arg) 😭

Extracted from #15685.

@ysbaddaden ysbaddaden self-assigned this May 20, 2025
@ysbaddaden ysbaddaden added kind:refactor breaking-change May have breaking effect in some edge cases. Mostly negligible, but still noteworthy. topic:stdlib:networking labels May 20, 2025
sock = UNIXSocket.new(handle: fd, type: type, blocking: blocking)
sock.sync = true
sock
end
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: we don't currently need the Crystal::EventLoop#socketpair to return the blocking arg, as it's only supported on UNIX where we can query the blocking mode, and only win32 needs to save the info as an ivar.

@ysbaddaden ysbaddaden marked this pull request as ready for review May 20, 2025 17:25
@straight-shoota
Copy link
Member

I think it would be helpful to rebase the commit history and squash some commits (only this once 😅).

Overall it looks fine, but the history is hard to follow with all the back-and-forth and fixups. We'll have it easier now for review and whenever we want to make sense of what happened here later on, if the commits are more comprehensible.

The libevent, epoll and kqueue event loops don't carry over the blocking
mode from the server socket to the client socket. They always set the
client socket into non-blocking mode.

The `TCPServer#accept?` and `UNIXServer#accept?` already behaved that
way, but `Socket#accept?` used to carry the blocking mode.
@ysbaddaden ysbaddaden force-pushed the refactor/socket-nonblocking branch from 88da2f3 to 7b86f75 Compare May 22, 2025 16:42
@ysbaddaden
Copy link
Collaborator Author

Here comes the rebase, with focused commits 😂

@straight-shoota straight-shoota added this to the 1.17.0 milestone May 22, 2025
ysbaddaden added a commit to ysbaddaden/crystal that referenced this pull request May 24, 2025
@straight-shoota straight-shoota merged commit ef2b8fe into crystal-lang:master May 26, 2025
45 of 48 checks passed
@ysbaddaden ysbaddaden deleted the refactor/socket-nonblocking branch May 26, 2025 10:25
@straight-shoota straight-shoota added kind:breaking Intentional breaking change with significant impact. Shows up on top of the changelog. and removed kind:refactor breaking-change May have breaking effect in some edge cases. Mostly negligible, but still noteworthy. labels Jun 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind:breaking Intentional breaking change with significant impact. Shows up on top of the changelog. topic:stdlib:networking

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants