-
-
Notifications
You must be signed in to change notification settings - Fork 221
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
Fix mixing stream and promise consumption #351
Conversation
It seems like a lot of our problems are caused by having |
I agree that mixing promises and streams means that child processes streams are being consumed by competing resources:
I think it would be better to not allow both at the same time. But it would be a major breaking change, not sure if that's a good idea. |
fb4e785
to
852a4c1
Compare
Even if we decide it's not worth the breaking change in the end, it would still be productive to discuss how it would like like to split them up. |
Core Node.js actually split them up: A possible approach would be for us to do the same:
What do you think? |
What are the advantages of mixing them besides having a nicer syntax? I have a few modules where I do this as well, but I've yet to see any real use cases where
I think having a separate |
This trips me up all the time:
I need to call This seems to work, but I can't really put my finger on why: const bootstrap = (name: string) => { // eslint-disable-line @typescript-eslint/promise-function-async
const service = execa.node(`${name}.js`, {
buffer: false,
cwd: 'build/test/fixtures/bootstrapped-services',
env: {
HEALTHCHECK_PORT: '0',
LOG_LEVEL: 'debug',
},
stderr: 'inherit',
}) as any as ExecaChildProcess<NodeJS.ReadableStream>
const promise = service.then(result => result)
if (service.all !== undefined) service.all.on('data', () => {})
return {
...service,
then: promise.then.bind(promise),
catch: promise.catch.bind(promise),
finally: promise.finally.bind(promise),
}
} Apologies for dumping this here rather than in a new issue, but it seems relevant to the discussion. |
@sindresorhus what do you think? |
After giving this PR some thoughts, I think this should be closed. The original problem is probably not one. If a process output is consumed (here the user with When it comes to the second problem raised in this PR (mixing different consumption patterns), I do think it would be better if those were separate. However I think the benefits of having them separate is not strong enough to justify such a breaking change. I also think this is a separate issue from this PR. I am going to close this PR, but feel free to re-open it (if you disagree) and to comment. Thanks! |
Now that this is resolved can we expect v3 soonish? (referencing #353 (comment)) |
We can make a major release but maybe we should first review/merge #375. @sindresorhus what do you think? |
Now that #375 has been merged, I guess we can make that major release? |
Awesome work @ehmicky <3 |
I think this should be revisited. The fact that there is currently no way to return the result of a call to async function execThing (args, asStream) {
const cmd = await getCmd()
const proc = execa(cmd, args)
if (asStream) {
delete proc.then
}
return proc
} I think you will probably agree this is unfortunate. I think there are two reasonable api's which would help in my use case: execa(cmd, args, { stream: true })
execa(cmd, args).stream() Based on this conversation @sindresorhus said we should explore this, but I don't really see that exploration or conversation here. I just see the decision by @ehmicky to hold off since it is a rather significant breaking change. I think that both approaches listed could be done in a non-breaking way. Make the default behavior still mix the promise and child process, but when |
I would be ok with this suggestion. @sindresorhus What are your thoughts? |
Sounds good. I would do |
Opened an issue at #414 to track this. |
Fixes #322
At the moment, we delay child process streams being consumed by
get-stream
untilexecaPromise.then()
is called. However this does not work for the following reason:execaPromise.then()
is called, its streams will be closed whenget-stream
starts reading them.execaResult.stdout
andexecaResult.stderr
will be empty.This PR solves this problem by not delaying
get-stream
consumption.The drawback (and breaking change) is that users would now need to pass the
buffer: false
option when not consumingexecaPromise
as a promise. Otherwise:get-stream
would buffer streams into memoryexecaPromise.stdout|stderr.pipe()
will only work if done in the same tick asexeca()
The core issue is that we are consuming child process streams in two ways:
a. with
get-stream
(promise consumption)b. we let users consume it themselves
Mixing those two creates several issues since streams can be piped to several streams at once, but not consumed twice.
Some refactoring can be done and some dependencies can be removed following this PR, but we can do this in a separate PR.
@tiagonapoli @sindresorhus do you have some feedback on this?