Skip to content

Create platform specific AsyncIO #64

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

iCharlesHu
Copy link
Contributor

This PR introduces platform specific AsyncIO:

  • Darwin: based on DispatchIO
  • Linux: based on epoll
  • (TODO) Windows: based on IOCP with OVERLAPPED

The epoll based AsyncIO replaces DispatchIO on Linux.

Resolves: #47

@iCharlesHu iCharlesHu requested review from parkera and itingliu June 10, 2025 01:22
#if os(Windows)
// Compiler generated conformances
#else
#if canImport(Darwin)
Copy link
Contributor

Choose a reason for hiding this comment

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

I know these aren't not new changes, but why do we have custom implementation only for Darwin?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

DispatchData is unfortunately not Hashable

) async throws -> [UInt8]? {
// If we are reading until EOF, start with readBufferSize
// and gradually increase buffer size
let bufferLength = maxLength == .max ? readBufferSize : maxLength
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just respect the passed-in maxLength as-is and not impose this silent cap?
This feels deceiving from callsites perspective and I imagine it'd make it difficult to figure out what's going on

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we need to preallocate buffer of a certain length. We can't just pass in .max to allocate infinite buffer so we have to grow it manually similar like an array (all this is so we can avoid copying and write into the buffer directly from read()).

)
}

self.setupError = maybeSetupError
Copy link
Contributor

Choose a reason for hiding this comment

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

Why can't we throw this error right here in init?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AsyncIO is designed to be a singleton static let so we only spin up one additional thread for monitoring. Unfortunately this also means we can't have failable initializer...

@jakepetroules
Copy link
Contributor

What's the strategy for other Unix platforms like FreeBSD?

[PlatformFileDescriptor : SignalStream.Continuation]
> = Mutex([:])

final class AsyncIO: Sendable {
Copy link
Contributor

Choose a reason for hiding this comment

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

On Darwin, class AsyncIO doesn't have any stored variables. That means AsyncIO.shared.read() can also be a static function, and you would just write AsyncIO.read(...)

I think you left it this way because on linux it has stored variables so they can't be static funcs, but we still want to share the same source at callsites? If so would it make more sense to move the #if platform guards into the shared functions? So instead of

#if linux
class AsyncIO {
    func read(...)
}
#elif mac 
class AsyncIO {
    func read(...)
}
#endif

Can we have

class AsyncIO {
    func read(...) {
        #if linux
         ...
        #elif mac 
         ...
        #endif
    }
}

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 decided to go with the first approach because the implementation for each platform is vastly different. So having #if #else inside of each function makes this file overall not readable. This is especially true now I'm adding Windows-specific implementation on top of this. It's much more maintainable if each platform has its own "section" of code so the overall flow is more clear.

@iCharlesHu
Copy link
Contributor Author

What's the strategy for other Unix platforms like FreeBSD?

We don't currently have a plan to support FreeBSD, however, we are definitely open to it. Although I'm not sure logistically how this would work because in order to officially support a platform, we'd have to have CI for it. Since FreeBSD isn't an officially supported platform for Swift, I'm not sure if we can do that just yet (@shahmishal please correct me if I'm wrong).

- Darwin: based on DispatchIO
- Linux: based on epoll
- Windows (not included in this commit): based on IOCP with OVERLAPPED
@iCharlesHu iCharlesHu force-pushed the charles/linux-async-io branch from 893f4ed to 17afb54 Compare June 16, 2025 19:17
@iCharlesHu
Copy link
Contributor Author

Rebased

@iCharlesHu iCharlesHu requested a review from cthielen June 17, 2025 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use epoll and non-blocking read/write instead of DispatchIO on Linux
3 participants