-
-
Notifications
You must be signed in to change notification settings - Fork 229
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
Subprocess should emit error if executable path doesn't exist in Windows #469
Comments
Hi @lynxtaa, Thanks for reaching out. This is an example with running your example on my machine on Windows, with latest A potential issue you might be experiencing is if you forgot to add a Aside from this, |
@ehmicky I'm running Node@16.3.0 and execa@5.1.1.
I've tried running $ node --unhandled-rejections=warn t.js
Error: spawn wrong-path ENOENT
at Process.ChildProcess._handle.onexit (node:internal/child_process:282:19)
at onErrorNT (node:internal/child_process:480:16)
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
errno: -4058,
code: 'ENOENT',
syscall: 'spawn wrong-path',
path: 'wrong-path',
spawnargs: []
} |
Does |
No, that's all. I've reproduced this problem on three different Windows 10 machines running Node 16.3.0 and Node 14.17.0. In WSL in works okay. To be sure it has time to handle an error, I'm running this code: const execa = require('execa')
const p = execa('wrong-path')
p.on('error', err => {
console.log(err) // never called in Windows, but called in Linux
})
setTimeout(() => {}, 1000) Still no luck |
Would it be possible to create a GitHub repository to reproduce this behavior, with a GitHub action running on Windows? |
Thanks a lot @lynxtaa, that's perfect 👍 |
I'm using |
After further investigation I think the issue is with Line 83 in 9216ec8
{
file: 'C:\\WINDOWS\\system32\\cmd.exe',
args: [ '/q', '/d', '/s', '/c', '"wrong-path"' ],
options: {
maxBuffer: 100000000,
buffer: true,
stripFinalNewline: true,
extendEnv: true,
preferLocal: false, Running on Linux: {
file: 'wrong-path',
args: [],
options: {
maxBuffer: 100000000,
buffer: true,
stripFinalNewline: true,
extendEnv: true,
preferLocal: false, |
Related moxystudio/node-cross-spawn#16 |
Thanks for digging into this @lynxtaa! The actual issue seems to be the following: Node.js detects
|
@ehmicky Thanks you for this detailed explanation. There are cases when we are running a subprocess for a very long time and awaiting a promise is undesirable. To detect a startup failure with await Promise.race([
subprocess,
new Promise(resolve => setTimeout(resolve, 1000))
]) But I still think some kind of error should be emitted when using |
I'm realizing your main problem might be: you want to handle process startup failures separately from post-startup failures. Am I correct to assume this? If so, this problem would cover not only non-existing files but other types of startup failures as well. I actually have had that same problem myself when using Execa in other projects. The approach you're describing works, but it relies on timeouts, i.e. it makes the code time-dependent. Also, it temporarily stops the code execution. I agree with you that there should ideally be a better way to handle startup failures. For clarity, we should dissociate the following three types of startup failures. First typeThe first type of startup failures are triggered synchronously with import { spawn } from 'child_process'
try {
const subprocess = spawn('example', { cwd: 1 })
return subprocess
} catch (error) {
console.log(error)
} Since Execa always returns a // `util.promisify(setTimeout)` can be used instead for Node <15.0.0
import { setTimeout } from 'timers/promises'
const subprocess = execa('example', { cwd: 1 })
try {
await Promise.race([subprocess, setTimeout(0)])
return subprocess
} catch (error) {
console.log(error)
} Second typeThe second type of startup failures are triggered with the import { spawn } from 'child_process'
import pEvent from 'p-event'
const subprocess = spawn('does_not_exist')
try {
await pEvent(subprocess, { rejectionEvents: ['error'] })
} catch (error) {
console.log(error)
} Execa handles the const subprocess = execa('does_not_exist')
try {
await Promise.race([subprocess, waitTwice()])
return subprocess
} catch (error) {
console.log(error)
}
const waitTwice = async function() {
await setTimeout(0)
await setTimeout(0)
} Third typeThe third type of startup failures are happening inside the process as it starts executing. This includes spawning a Node.js process which tries to Those are time-dependent since they are only triggered after the process has fully spawned and has started to execute. Based on this, it is difficult to separate those errors from post-startup errors. A timeout could be used. You could also detect specific errors using the exit code, terminal signal, error code or error message. However, both of those solutions are brittle. A more reliable way could be to use some IPC mechanism (including but not limited to Problem on WindowsMost of the time, non-existing files fall into the second type of startup failures. However, on Windows, under specific circumstances, Execa (via |
I'm very grateful for this detailed write-up. Thank you, @ehmicky ❤️ ! Yeah, I've wanted to use Previously I've used |
Summary: I thought we could depend on execa to manage returning `ENOENT` for invalid command names, but apprently this doesn't always happen on windows. By making this test work on windows, we show a more accurate "sl is not installed correctly" rather than "this is an invalid repository". See this discussion for more information: sindresorhus/execa#469 (comment) Example of a user on windows not triggering ENOENT: #282 (comment) Instead, we should detect invalid commands on windows with exit code 1 (but only on windows). The use of this detection is isolated to findRoot, which is where we determine if the command being used is valid. If some other issue triggered this code path, showing "this is not a valid sl command" is also a reasonable error message. Plus we still log the error for debugging purposes. This may also help with #282. Reviewed By: bolinfest Differential Revision: D41595211 fbshipit-source-id: 7cff8eafd58f6da14bb85f4289eb25ddbcf5273c
Returned from
execa
subprocess should emit 'error' event if executable path doesn't exist in Windows. Just likespawn
andexecFile
inchild_process
Consider this code:
The text was updated successfully, but these errors were encountered: