Skip to content

Docs: Socket and IO::FileDescriptor fd ownership & soft deprecate their blocking args #15924

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

ysbaddaden
Copy link
Contributor

@ysbaddaden ysbaddaden commented Jun 24, 2025

Improves the documentation around the system file descriptors and handles in IO::FileDescriptor and Socket in relation to "adopting" the fd/handle into the IO system, or accessing it. I hope it starts to clarify the intent.

The documentation also soft deprecates the blocking argument of their constructors.

Sadly, I can't use the @[Deprecated] annotation and the usual two methods trick (one without the arg, and a deprecated one with the arg) because the blocking arg is positional and comes after default arguments.

My doc-fu ain't the best. If you're more capable and find better phrasing, feel free to edit the PR 🙇

Follow up to #15804, #15754, #15753 and #15750.

@ysbaddaden ysbaddaden self-assigned this Jun 24, 2025
@ysbaddaden ysbaddaden marked this pull request as ready for review June 24, 2025 17:22
@ysbaddaden ysbaddaden changed the title Fix/docs for socket and file fd ownership+soft deprecate blocking args Docs: fd/handle ownership Socket and IO::FileDescriptor + soft deprecate their blocking args Jun 24, 2025
@ysbaddaden ysbaddaden changed the title Docs: fd/handle ownership Socket and IO::FileDescriptor + soft deprecate their blocking args Docs: Socket and IO::FileDescriptor fd ownership & soft deprecate their blocking args Jun 24, 2025
Comment on lines 90 to 105
# WARNING: changing the blocking mode can break the IO system requirements and
# cause the event loop to misbehave, for example block the program when a
# fiber tries to read from this file descriptor.
Copy link
Member

Choose a reason for hiding this comment

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

thought: This sounds like it might be quite a foot gun. Perhaps we should not make it so easy to break the event loop...
So I'm wondering, what are legitimate use cases for setting blocking to something other than what the event loop chose?
There's reconfiguring the adopted file descriptors, which may not have been originally created by the event loop. But apart from that I figure it might be related to using raw file descriptors outside the stdlib API. And for that use case, we should not need such an easily accessible method in the top-level API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's acceptable as long as we properly document the behavior.

We can for example create an IO in Crystal, make sure it's (non-)blocking then take the fd/handle and give it to an external library that expects the given blocking mode.

Copy link
Contributor Author

@ysbaddaden ysbaddaden Jun 26, 2025

Choose a reason for hiding this comment

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

More problematic is the documentation for IO::FileDescriptor#blocking that states:

Returns whether I/O operations on this file descriptor block the current thread. If false, operations might opt to suspend the current fiber instead.

Because on WINDOWS a STDIO might emulate non-blocking, something that might be dropped when we have #15871.

Copy link
Member

Choose a reason for hiding this comment

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

We can for example create an IO in Crystal, make sure it's (non-)blocking then take the fd/handle and give it to an external library that expects the given blocking mode.

That use case goes beyond the scope of the stdlib API. And it's clear that passing the fd/handle around directly needs special attention.
But #blocking in the stdlib API seems inconspicuous It might be too much exposed for its limited (remaining) relevancy. It's an invitation for a footgun.

Copy link
Contributor Author

@ysbaddaden ysbaddaden Jun 26, 2025

Choose a reason for hiding this comment

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

Then we can consider to deprecate the #blocking and #blocking= methods.

It would still be nice to have a mean to have a target abstract method to change the blocking mode of a system socket, but it doesn't have to be an instance method 👍

Copy link
Member

Choose a reason for hiding this comment

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

Exactly. It could be something like IO::FileDescriptor.set_blocking(fd : Handle, blocking : Bool).

#
# NOTE: On Windows the handle should have been created with `FILE_FLAG_OVERLAPPED`.
# NOTE: The *blocking* arg is deprecated since Crystal 1.17. Use `#blocking=`
# to change the blocking mode instead.
def self.new(fd : Handle, blocking = nil, *, close_on_finalize = true)
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: I think we should be able to duplicate this def with a @[Deprecated] annotation. There are no parameters with default value before blocking. There are a couple other similar defs like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh my, thanks to the deprecated annotation, I just noticed that the event loops call IO::FileDescriptor with a blocking arg 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same for ORIGINAL_STDIN/OUT/ERR.

- The `blocking` args of File constructors now defaults to `nil`.
- The polling evloops now set the fd to non blocking by default.
- The IOCP evloop uses OVERLAPPED IO by default.
Explicits that the fd/handle will be configured to match the event loop
requirements when adopted in the Socket types (its configuration will be
changed).

Explicits that `#fd` returns the fd/handle configured for the current
event loop, and no specific configuration must be expected.

Explicits that changing a socket's blocking mode can break the event
loop, and as such must be treated carefully.
Explicits that the fd/handle will be configured to match the event loop
requirements when adopted in the Socket types (its configuration will be
changed).

Explicits that `#fd` returns the fd/handle configured for the current
event loop, and no specific configuration must be expected.

Explicits that changing a socket's blocking mode can break the event
loop, and as such must be treated carefully.
@ysbaddaden
Copy link
Contributor Author

Back to draft. Waiting for #15930.

@ysbaddaden ysbaddaden marked this pull request as draft June 26, 2025 16:17
@ysbaddaden ysbaddaden force-pushed the fix/docs-for-socket-and-file-fd-ownership+soft-deprecate-blocking-args branch from 3c44c1d to cc31269 Compare June 26, 2025 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants