You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Promise readline question results in unsettled promise on abortion #53497
Hi, just ran across this; funny thing is, rl.question() accepts an AbortSignal in the second argument, but when I tried installing a SIGINT handler and aborting the question manually, I got exactly the same result as before - meaning my signal handler wasn't even called. I hope I'm not overlooking something, but it looks as though the rl.question() method somehow hijacks the signal..?
@DanielVenable sure thing, this is a minimal example which demonstrates what happens:
import{createInterface}from'node:readline/promises';import{stdin,stdout}from'node:process';constrl=createInterface({input: stdin,output: stdout});constctrl=newAbortController();process.on('SIGINT',functionhandleSignal(){console.log(`This should be printed when you press Ctrl+C, but it won't be`);process.off('SIGINT',handleSignal);ctrl.abort();});try{constresponse=awaitrl.question('Press Ctrl+C now!',{signal: ctrl.signal});console.log('This is never reached if you press Ctrl+C');}catch{console.log('And neither is this');}console.log('Nor this');rl.close();
shove that into a test.mjs file and run it with node test.mjs; after pressing Ctrl+C, only the "Warning: Detected unsettled top-level await" is printed and the script is immediately terminated
@jahudka It is not rl.question but createInterface that hijacks the SIGINT signal. Once createInterface is called, the first SIGINT closes the readline interface. In your example that causes the process to exit because there is nothing left keeping it open. If there was something keeping it open, a second SIGINT would then call handleSignal. To make your example work as expected, replace process.on('SIGINT', function handleSignal() { with rl.on('SIGINT', function handleSignal() {.
@DanielVenable Oh, wow, okay, makes sense. Never would've guessed that Readline would mess with signal handling like that.. But seeing the docs now and the fact that it handles SIGTSTP and SIGCONT it makes sense why it would do that. Still, I'd say it's somewhat unexpected and that it would be perhaps better to have those signal handlers be opt-in; or even just to have an option to opt out, which I don't see in the docs.
Anyway, I've just confirmed that if you install a SIGINT handler on the readline interface rather than on the process itself, you can abort the .question() call gracefully using an AbortController as shown in my example code and the warning will not be shown (although an AbortError will be thrown - but that can easily be caught).
So as far as I'm concerned, this could maybe do with a big fat warning in the docs, but otherwise I'd say it's working as intended. Thanks for helping me debug!
Activity
imanhpr commentedon Jun 18, 2024
More Additional Information
According to the Nodejs documentation related to exit codes:
In the example below I expect to get
13as an exit code inbeforeExitandexitevents but it doesn't.Result :
It's good to mention when I check the previous process exit code with
echo $?it's13and it's fine.DanielVenable commentedon Jun 24, 2024
I could try to work on this. Can someone assign me?
avivkeller commentedon Jun 24, 2024
Hi! If you have a solution, feel free to submit a PR!
jahudka commentedon Jan 23, 2025
Hi, just ran across this; funny thing is,
rl.question()accepts anAbortSignalin the second argument, but when I tried installing aSIGINThandler and aborting the question manually, I got exactly the same result as before - meaning my signal handler wasn't even called. I hope I'm not overlooking something, but it looks as though therl.question()method somehow hijacks the signal..?DanielVenable commentedon Jan 24, 2025
@jahudka Can you share a code snippet?
jahudka commentedon Jan 25, 2025
@DanielVenable sure thing, this is a minimal example which demonstrates what happens:
shove that into a
test.mjsfile and run it withnode test.mjs; after pressing Ctrl+C, only the "Warning: Detected unsettled top-level await" is printed and the script is immediately terminatednode -v:v23.3.0DanielVenable commentedon Jan 25, 2025
@jahudka It is not
rl.questionbutcreateInterfacethat hijacks the SIGINT signal. OncecreateInterfaceis called, the first SIGINT closes the readline interface. In your example that causes the process to exit because there is nothing left keeping it open. If there was something keeping it open, a second SIGINT would then callhandleSignal. To make your example work as expected, replaceprocess.on('SIGINT', function handleSignal() {withrl.on('SIGINT', function handleSignal() {.jahudka commentedon Jan 25, 2025
@DanielVenable Oh, wow, okay, makes sense. Never would've guessed that Readline would mess with signal handling like that.. But seeing the docs now and the fact that it handles
SIGTSTPandSIGCONTit makes sense why it would do that. Still, I'd say it's somewhat unexpected and that it would be perhaps better to have those signal handlers be opt-in; or even just to have an option to opt out, which I don't see in the docs.Anyway, I've just confirmed that if you install a
SIGINThandler on the readline interface rather than on the process itself, you can abort the.question()call gracefully using an AbortController as shown in my example code and the warning will not be shown (although an AbortError will be thrown - but that can easily be caught).So as far as I'm concerned, this could maybe do with a big fat warning in the docs, but otherwise I'd say it's working as intended. Thanks for helping me debug!
readline: fix unresolved promise on abortion
readline: fix unresolved promise on abortion
readline: fix unresolved promise on abortion
readline: fix unresolved promise on abortion
8 remaining items
fix: suppress readline close errors in enquirer adapter for Node.js 22+
fix: skip enquirer cancel callback tests on Node.js 24+
fix: suppress readline close errors in enquirer adapter for Node.js 22+
fix: skip enquirer cancel callback tests on Node.js 24+
perf(deps): update major dependencies (#754)
chore(release): 10.0.0 [skip ci]
chore(release): 4.0.0 [skip ci]
chore(release): 4.0.0 [skip ci]
chore(release): 4.0.0 [skip ci]
Update dependency listr2 to v10 (#2277)
Epic AQJ95G: default namespace .project/ — resolver, setup scaffold, …
feat(cli): styled prompts, single-letter choice(), clean Ctrl+C
ci: run each test file in its own node process
ci: run each test file in its own node process (#3)
fix(create-mono-stack): stop the unsettled top-level await warning on…