AsyncBytes and AsyncLineSequence not available on Linux

I’ve seen a few issues related to this, and it seems like this is just “we haven’t gotten to it yet” rather than something fundamental, but I wanted to make sure.

Is there some complexity delaying the implementation of these APIs? Is there a recommended alternative other than “roll your own” or “use Swift NIO”?

One difficulty with AsyncBytes specifically is that the current implementation is poorly suited to server-side use cases. It's very much aimed at the "small device, regular files, low parallelism, low installed memory, priority donation is very important" iOS use profile.

A good way to fix this would be to reimplement it on top of io_uring on Linux (I did carefully leave room in the implementation to swap out the "back end"), but that's a somewhat nontrivial project.

2 Likes

You should be able to use the NIOFileSystem module to iterate over the data in files. This package supports both Linux and Darwin. Under the hood it uses a separate thread pool and standard sys calls for writing/reading data. It is capable of achieving really high performance and fast throughput.

@David_Smith already mentioned that this isn't ideal for Darwin platforms since it breaks priority propagation and escalation. One idea that we had that might solve this to adopt task executors and task executor preference.

2 Likes

Have just been tripped up by the lack of this.

On the performance side of things, I'd just say this: linux-swift > server-side-swift!

My use case is a tool, not a server, and whilst performance would be nice, it's not essential. If there's an existing non-optimal implementation that could be ported easily, that would be a good start.

Perfect is the enemy of good, etc, etc... :slight_smile:

As pointed out by Franz, NIOFileSystem not only is optimal and porting it to other platforms is relatively easy, but it is already supported on both Darwin and Linux.

Exactly. NIOFileSystem is not perfect, but it certainly is good enough for a lot of use cases.

As pointed out by Franz, NIOFileSystem not only is optimal and can be ported easily to other platforms, but is already supported on both Darwin and Linux. For now I'd recommend that module as a cross-platform substitute.

I might be misunderstanding, but NIOFileSystem is for processing files, not network requests, which is where I've run into this issue. The Async http client in NIO has some support here, but you really do want to be using the system transport on Darwin platforms. While I haven't implemented NIO support yet, I did roll my own abstraction to support switching out the networking layer between NIO and URL session: SwiftClaude/Sources/Client/Transport.swift at 65192ee97c30016d77b740af36bb50ebe3274f9a · GeorgeLyon/SwiftClaude · GitHub

My use case is a tool, not a server, and whilst performance would be nice, it's not essential. If there's an existing non-optimal implementation that could be ported easily, that would be a good start.

If it helps at all, I rolled my own AsyncLines for SwiftClaude: SwiftClaude/Sources/Client/Server-Sent Events.swift at 65192ee97c30016d77b740af36bb50ebe3274f9a · GeorgeLyon/SwiftClaude · GitHub

And my own data task delegate to mimic AsyncBytes:

Is there a reason you're not using swift-nio-transport-services then?

Ah interestring, I hadn't seen that project. Yes something like that and async-http-client could work well.

Potentially a useful reminder: for anyone doing their own AsyncBytes-like byte streams, you can use AsyncBufferedByteIterator to wrap anything that asynchronously gives you "chunks of bytes", and it'll take care of the slightly subtle inlining needed to get good performance.

It remains up to you to make sure the stream consumer also has good performance though (we've seen cases where people were calling malloc for every single byte in the stream and then wondered why it was slow).

In the future we hope to have some additional abstractions that help address a) performance across specialization boundaries, and b) dealing with data where you want random access to a subset of the stream.

1 Like

NIO is an additional dependency however.

I am unlikely to want to add NIO as a dependency for my use case, which is a tiny package.