Skip to content

There should be dedicated API to collect merged stdout+stderr as a single stream (stdout and stderr going to the same underlying fd) #59

Open
@jakepetroules

Description

@jakepetroules
Contributor

The API only allows collecting stdout and stderr separately, not as a single stream. This makes it impossible to build something like a Terminal app where the stdout+stderr of a process should be interleaved in the order of emission.

Activity

FranzBusch

FranzBusch commented on Jun 4, 2025

@FranzBusch
Member

Can't you use the merge algorithm of swift-async-algorithms to achieve this?

jakepetroules

jakepetroules commented on Jun 4, 2025

@jakepetroules
ContributorAuthor

How could that possibly produce the same result w.r.t. ordering?

iCharlesHu

iCharlesHu commented on Jun 5, 2025

@iCharlesHu
Contributor

Ditto to @FranzBusch 's answer. The current design uses AsyncSequence specifically so you can utilize the rest of async-algorithm for things like this. @jakepetroules can you elaborate what's the issue you are seeing with merge?

FranzBusch

FranzBusch commented on Jun 6, 2025

@FranzBusch
Member

@jakepetroules is right that the ordering with merge is not guaranteed to be the same as the subprocess that produced it since it is up to the executors scheduling. If we want to achieve the same ordering as the subprocess then we need to pass probably the same FD to as stdout and stderr and then offer a single async sequence for it.

jakepetroules

jakepetroules commented on Jun 6, 2025

@jakepetroules
ContributorAuthor

Actually, this approach works with the current API surface:

let (readEnd, writeEnd) = try FileDescriptor.pipe()
return try await readEnd.closeAfter {
    // Direct both stdout and stderr to the same fd. Only set `closeAfterSpawningProcess` on one of the outputs so it isn't double-closed (similarly avoid using closeAfter for the same reason).
    let result = try await Subprocess.run(
        .path(FilePath(url.filePath.str)),
        arguments: .init(arguments),
        environment: environment.map { .custom(.init($0)) } ?? .inherit,
        workingDirectory: (currentDirectoryURL?.filePath.str).map { FilePath($0) } ?? nil,
        output: .fileDescriptor(writeEnd, closeAfterSpawningProcess: true),
        error: .fileDescriptor(writeEnd, closeAfterSpawningProcess: false))
    // read from `readEnd` here...
}
jakepetroules

jakepetroules commented on Jun 6, 2025

@jakepetroules
ContributorAuthor

Actually I'll leave this open to allow consideration of a higher level API for this (which I think would be a reasonable addition). That said, there is a workaround for now, it's just a little lower level than I'd prefer, especially since the client has to provide their own mechanism to read from the fd in an async-friendly manner.

changed the title [-]There doesn't seem to be a way to collect merged stdout+stderr[/-] [+]There should be dedicated API to collect merged stdout+stderr as a single stream (stdout and stderr going to the same underlying fd)[/+] on Jun 6, 2025
added
enhancementNew feature or request
and removed
bugSomething isn't working
on Jun 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @jakepetroules@FranzBusch@iCharlesHu

        Issue actions

          There should be dedicated API to collect merged stdout+stderr as a single stream (stdout and stderr going to the same underlying fd) · Issue #59 · swiftlang/swift-subprocess