Skip to content

Conversation

@grynspan
Copy link
Contributor

@grynspan grynspan commented Nov 5, 2025

Attempting to read to the end of a pipe will block if the writer hasn't finished writing bytes. This can lead to a deadlock (or, at least, pathologically bad read performance) if the writer is also communicating with the reader via another file descriptor or other IPC mechanism. It can also lead to difficult-to-describe issues when the pipe's kernel-side buffer is filled and the writer is blocked waiting for the reader to empty it.

This PR introduces a function on FileHandle that reads line-by-line which gives the reader a chance to process data as it comes in rather than waiting for EOF.

Checklist:

  • Code and documentation should follow the style of the Style Guide.
  • If public symbols are renamed or modified, DocC references should be updated.

Attempting to read to the end of a pipe will block if the writer hasn't finished
writing bytes. This can lead to a deadlock (or, at least, pathologically bad
read performance) if the writer is _also_ communicating with the reader via
another file descriptor or other IPC mechanism. It can also lead to
difficult-to-describe issues when the pipe's kernel-side buffer is filled and
the writer is blocked waiting for the reader to empty it.

This PR introduces a function on `FileHandle` that reads line-by-line which
gives the reader a chance to process data as it comes in rather than waiting for
EOF.
@grynspan grynspan added this to the Swift 6.x (main) milestone Nov 5, 2025
@grynspan grynspan self-assigned this Nov 5, 2025
@grynspan grynspan added the concurrency 🔀 Swift concurrency/sendability issues label Nov 5, 2025
@grynspan grynspan requested a review from jerryjrchen as a code owner November 5, 2025 22:19
@grynspan grynspan added performance 🏎️ Performance issues exit-tests ☠️ Work related to exit tests labels Nov 5, 2025
@grynspan grynspan marked this pull request as draft November 6, 2025 03:36
/// Use this function when calling C I/O interfaces such as `fputs()` on the
/// underlying C file handle.
borrowing func withUnsafeCFILEHandle<R>(_ body: (SWT_FILEHandle) throws -> R) rethrows -> R {
borrowing func withUnsafeCFILEHandle<R>(_ body: (SWT_FILEHandle) throws -> R) rethrows -> R where R: ~Copyable {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the rationale behind the R: ~Copyable in the where clause here? Is it to prevent copying of anything returned by body as a performance optimisation?

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 that's a remnant of an earlier iteration. I'll revert it.

if 0 != ferror(file) {
throw CError(rawValue: swt_errno())
}
} else if let byteRead = UInt8(exactly: byteRead) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is just verifying it really is a char if it's not EOF?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The value we get back is an int or CInt and needs to be cast to the correct type. In practice, the if let can't fail, so this code is equivalent to let byteRead = UInt8(byteRead).

/// thrown by `isDelimiter`.
func read(until isDelimiter: (UInt8) throws -> Bool) throws -> [UInt8] {
var line = [UInt8]()
line.reserveCapacity(1024)
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reasoning behind this capacity? Seems to me you could get much longer lines for a given delimiter, so is this just based off experience?

Copy link
Contributor

Choose a reason for hiding this comment

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

When I wrote my question I forgot this is for lines of the JSON event stream; now the limit makes more sense to me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Utterly arbitrary. Gotta pick something, no way to know what.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

concurrency 🔀 Swift concurrency/sendability issues exit-tests ☠️ Work related to exit tests performance 🏎️ Performance issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants