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

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
jakepetroules opened this issue Jun 4, 2025 · 6 comments
Labels
API Change enhancement New feature or request

Comments

@jakepetroules
Copy link
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.

@jakepetroules jakepetroules added the bug Something isn't working label Jun 4, 2025
@FranzBusch
Copy link
Member

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

@jakepetroules
Copy link
Contributor Author

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

@iCharlesHu
Copy link
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
Copy link
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
Copy link
Contributor Author

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
Copy link
Contributor Author

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.

@jakepetroules jakepetroules reopened this Jun 6, 2025
@jakepetroules jakepetroules 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) Jun 6, 2025
@jakepetroules jakepetroules added enhancement New feature or request and removed bug Something isn't working labels Jun 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Change enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants