GeezerVison is showing github.com — esm: propagate `process.exit` from the loader thread to the main thread · nodejs/node@39fbce7
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/hooks.js +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -501,6 +501,7 @@ class HooksProxy { | 501 | 501 | }, | 502 | 502 | }); | 503 | 503 | this.#worker.unref(); // ! Allows the process to eventually exit. | | 504 | + this.#worker.on('exit', process.exit); | 504 | 505 | } | 505 | 506 |
| 506 | 507 | #waitForWorker() { | @@ -510,6 +511,7 @@ class HooksProxy { | 510 | 511 | debug('wait for signal from worker'); | 511 | 512 | AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 0); | 512 | 513 | const response = this.#worker.receiveMessageSync(); | | 514 | + if (response.message.status === 'exit') { return; } | 513 | 515 | const { preloadScripts } = this.#unwrapMessage(response); | 514 | 516 | this.#executePreloadScripts(preloadScripts); | 515 | 517 | } | @@ -590,6 +592,8 @@ class HooksProxy { | 590 | 592 | debug('got sync response from worker', { method, args }); | 591 | 593 | if (response.message.status === 'never-settle') { | 592 | 594 | process.exit(13); | | 595 | + } else if (response.message.status === 'exit') { | | 596 | + process.exit(response.message.body); | 593 | 597 | } | 594 | 598 | return this.#unwrapMessage(response); | 595 | 599 | } | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/worker.js +13Lines changed: 13 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -66,6 +66,19 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { | 66 | 66 | let hooks, preloadScripts, initializationError; | 67 | 67 | let hasInitializationError = false; | 68 | 68 |
| | 69 | + { | | 70 | + // If a custom hook is calling `process.exit`, we should wake up the main thread | | 71 | + // so it can detect the exit event. | | 72 | + const { exit } = process; | | 73 | + process.exit = function(code) { | | 74 | + syncCommPort.postMessage(wrapMessage('exit', code ?? process.exitCode)); | | 75 | + AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); | | 76 | + AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); | | 77 | + return ReflectApply(exit, this, arguments); | | 78 | + }; | | 79 | + } | | 80 | + | | 81 | + | 69 | 82 | try { | 70 | 83 | initializeESM(); | 71 | 84 | const initResult = await initializeHooks(); | |
Collapse file Copy file name to clipboardExpand all lines: test/es-module/test-esm-loader-hooks.mjs +48Lines changed: 48 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -143,4 +143,52 @@ describe('Loader hooks', { concurrency: true }, () => { | 143 | 143 | assert.strictEqual(code, 0); | 144 | 144 | assert.strictEqual(signal, null); | 145 | 145 | }); | | 146 | + | | 147 | + it('should be fine to call `process.exit` from a custom async hook', async () => { | | 148 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | | 149 | + '--no-warnings', | | 150 | + '--experimental-import-meta-resolve', | | 151 | + '--experimental-loader', | | 152 | + 'data:text/javascript,export function load(a,b,next){if(a==="data:exit")process.exit(42);return next(a,b)}', | | 153 | + '--input-type=module', | | 154 | + '--eval', | | 155 | + 'import "data:exit"', | | 156 | + ]); | | 157 | + | | 158 | + assert.strictEqual(stderr, ''); | | 159 | + assert.strictEqual(stdout, ''); | | 160 | + assert.strictEqual(code, 42); | | 161 | + assert.strictEqual(signal, null); | | 162 | + }); | | 163 | + | | 164 | + it('should be fine to call `process.exit` from a custom sync hook', async () => { | | 165 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | | 166 | + '--no-warnings', | | 167 | + '--experimental-import-meta-resolve', | | 168 | + '--experimental-loader', | | 169 | + 'data:text/javascript,export function resolve(a,b,next){if(a==="exit:")process.exit(42);return next(a,b)}', | | 170 | + '--input-type=module', | | 171 | + '--eval', | | 172 | + 'import "data:text/javascript,import.meta.resolve(%22exit:%22)"', | | 173 | + ]); | | 174 | + | | 175 | + assert.strictEqual(stderr, ''); | | 176 | + assert.strictEqual(stdout, ''); | | 177 | + assert.strictEqual(code, 42); | | 178 | + assert.strictEqual(signal, null); | | 179 | + }); | | 180 | + | | 181 | + it('should be fine to call `process.exit` from the loader thread top-level', async () => { | | 182 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | | 183 | + '--no-warnings', | | 184 | + '--experimental-loader', | | 185 | + 'data:text/javascript,process.exit(42)', | | 186 | + fixtures.path('empty.js'), | | 187 | + ]); | | 188 | + | | 189 | + assert.strictEqual(stderr, ''); | | 190 | + assert.strictEqual(stdout, ''); | | 191 | + assert.strictEqual(code, 42); | | 192 | + assert.strictEqual(signal, null); | | 193 | + }); | 146 | 194 | }); |
|