| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -81,6 +81,7 @@ function start() { | |||
| 81 | 81 | process.stdout.write(`${red}Failed running ${kCommandStr}${white}\n`); | |
| 82 | 82 | } | |
| 83 | 83 | }); | |
| 84 | + return child; | ||
| 84 | 85 | } | |
| 85 | 86 | ||
| 86 | 87 | async function killAndWait(signal = kKillSignal, force = false) { | |
@@ -113,34 +114,43 @@ function reportGracefulTermination() { | |||
| 113 | 114 | }; | |
| 114 | 115 | } | |
| 115 | 116 | ||
| 116 | - async function stop() { | ||
| 117 | + async function stop(child) { | ||
| 118 | + // Without this line, the child process is still able to receive IPC, but is unable to send additional messages | ||
| 119 | + watcher.destroyIPC(child); | ||
| 117 | 120 | watcher.clearFileFilters(); | |
| 118 | 121 | const clearGraceReport = reportGracefulTermination(); | |
| 119 | 122 | await killAndWait(); | |
| 120 | 123 | clearGraceReport(); | |
| 121 | 124 | } | |
| 122 | 125 | ||
| 123 | 126 | let restarting = false; | |
| 124 | - async function restart() { | ||
| 127 | + async function restart(child) { | ||
| 125 | 128 | if (restarting) return; | |
| 126 | 129 | restarting = true; | |
| 127 | 130 | try { | |
| 128 | 131 | if (!kPreserveOutput) process.stdout.write(clear); | |
| 129 | 132 | process.stdout.write(`${green}Restarting ${kCommandStr}${white}\n`); | |
| 130 | - await stop(); | ||
| 131 | - start(); | ||
| 133 | + await stop(child); | ||
| 134 | + return start(); | ||
| 132 | 135 | } finally { | |
| 133 | 136 | restarting = false; | |
| 134 | 137 | } | |
| 135 | 138 | } | |
| 136 | 139 | ||
| 137 | - start(); | ||
| 138 | - watcher | ||
| 139 | - .on('changed', restart) | ||
| 140 | - .on('error', (error) => { | ||
| 141 | - watcher.off('changed', restart); | ||
| 142 | - triggerUncaughtException(error, true /* fromPromise */); | ||
| 143 | - }); | ||
| 140 | + async function init() { | ||
| 141 | + let child = start(); | ||
| 142 | + const restartChild = async () => { | ||
| 143 | + child = await restart(child); | ||
| 144 | + }; | ||
| 145 | + watcher | ||
| 146 | + .on('changed', restartChild) | ||
| 147 | + .on('error', (error) => { | ||
| 148 | + watcher.off('changed', restartChild); | ||
| 149 | + triggerUncaughtException(error, true /* fromPromise */); | ||
| 150 | + }); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + init(); | ||
| 144 | 154 | ||
| 145 | 155 | // Exiting gracefully to avoid stdout/stderr getting written after | |
| 146 | 156 | // parent process is killed. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,8 +3,10 @@ | |||
| 3 | 3 | const { | |
| 4 | 4 | ArrayIsArray, | |
| 5 | 5 | ArrayPrototypeForEach, | |
| 6 | + Boolean, | ||
| 6 | 7 | SafeMap, | |
| 7 | 8 | SafeSet, | |
| 9 | + SafeWeakMap, | ||
| 8 | 10 | StringPrototypeStartsWith, | |
| 9 | 11 | } = primordials; | |
| 10 | 12 | ||
@@ -31,6 +33,8 @@ class FilesWatcher extends EventEmitter { | |||
| 31 | 33 | #debounce; | |
| 32 | 34 | #mode; | |
| 33 | 35 | #signal; | |
| 36 | + #passthroughIPC = false; | ||
| 37 | + #ipcHandlers = new SafeWeakMap(); | ||
| 34 | 38 | ||
| 35 | 39 | constructor({ debounce = 200, mode = 'filter', signal } = kEmptyObject) { | |
| 36 | 40 | super({ __proto__: null, captureRejections: true }); | |
@@ -40,6 +44,7 @@ class FilesWatcher extends EventEmitter { | |||
| 40 | 44 | this.#debounce = debounce; | |
| 41 | 45 | this.#mode = mode; | |
| 42 | 46 | this.#signal = signal; | |
| 47 | + this.#passthroughIPC = Boolean(process.send); | ||
| 43 | 48 | ||
| 44 | 49 | if (signal) { | |
| 45 | 50 | addAbortListener(signal, () => this.clear()); | |
@@ -128,7 +133,31 @@ class FilesWatcher extends EventEmitter { | |||
| 128 | 133 | this.#ownerDependencies.set(owner, dependencies); | |
| 129 | 134 | } | |
| 130 | 135 | } | |
| 136 | + | ||
| 137 | + | ||
| 138 | + #setupIPC(child) { | ||
| 139 | + const handlers = { | ||
| 140 | + __proto__: null, | ||
| 141 | + parentToChild: (message) => child.send(message), | ||
| 142 | + childToParent: (message) => process.send(message), | ||
| 143 | + }; | ||
| 144 | + this.#ipcHandlers.set(child, handlers); | ||
| 145 | + process.on('message', handlers.parentToChild); | ||
| 146 | + child.on('message', handlers.childToParent); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + destroyIPC(child) { | ||
| 150 | + const handlers = this.#ipcHandlers.get(child); | ||
| 151 | + if (this.#passthroughIPC && handlers !== undefined) { | ||
| 152 | + process.off('message', handlers.parentToChild); | ||
| 153 | + child.off('message', handlers.childToParent); | ||
| 154 | + } | ||
| 155 | + } | ||
| 156 | + | ||
| 131 | 157 | watchChildProcessModules(child, key = null) { | |
| 158 | + if (this.#passthroughIPC) { | ||
| 159 | + this.#setupIPC(child); | ||
| 160 | + } | ||
| 132 | 161 | if (this.#mode !== 'filter') { | |
| 133 | 162 | return; | |
| 134 | 163 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ import { spawn } from 'node:child_process'; | |||
| 8 | 8 | import { writeFileSync, readFileSync, mkdirSync } from 'node:fs'; | |
| 9 | 9 | import { inspect } from 'node:util'; | |
| 10 | 10 | import { pathToFileURL } from 'node:url'; | |
| 11 | + import { once } from 'node:events'; | ||
| 11 | 12 | import { createInterface } from 'node:readline'; | |
| 12 | 13 | ||
| 13 | 14 | if (common.isIBMi) | |
@@ -574,4 +575,84 @@ console.log(values.random); | |||
| 574 | 575 | `Completed running ${inspect(file)}`, | |
| 575 | 576 | ]); | |
| 576 | 577 | }); | |
| 578 | + | ||
| 579 | + it('should pass IPC messages from a spawning parent to the child and back', async () => { | ||
| 580 | + const file = createTmpFile(`console.log('running'); | ||
| 581 | + process.on('message', (message) => { | ||
| 582 | + if (message === 'exit') { | ||
| 583 | + process.exit(0); | ||
| 584 | + } else { | ||
| 585 | + console.log('Received:', message); | ||
| 586 | + process.send(message); | ||
| 587 | + } | ||
| 588 | + })`); | ||
| 589 | + | ||
| 590 | + const child = spawn( | ||
| 591 | + execPath, | ||
| 592 | + [ | ||
| 593 | + '--watch', | ||
| 594 | + '--no-warnings', | ||
| 595 | + file, | ||
| 596 | + ], | ||
| 597 | + { | ||
| 598 | + encoding: 'utf8', | ||
| 599 | + stdio: ['pipe', 'pipe', 'pipe', 'ipc'], | ||
| 600 | + }, | ||
| 601 | + ); | ||
| 602 | + | ||
| 603 | + let stderr = ''; | ||
| 604 | + let stdout = ''; | ||
| 605 | + | ||
| 606 | + child.stdout.on('data', (data) => stdout += data); | ||
| 607 | + child.stderr.on('data', (data) => stderr += data); | ||
| 608 | + async function waitForEcho(msg) { | ||
| 609 | + const receivedPromise = new Promise((resolve) => { | ||
| 610 | + const fn = (message) => { | ||
| 611 | + if (message === msg) { | ||
| 612 | + child.off('message', fn); | ||
| 613 | + resolve(); | ||
| 614 | + } | ||
| 615 | + }; | ||
| 616 | + child.on('message', fn); | ||
| 617 | + }); | ||
| 618 | + child.send(msg); | ||
| 619 | + await receivedPromise; | ||
| 620 | + } | ||
| 621 | + | ||
| 622 | + async function waitForText(text) { | ||
| 623 | + const seenPromise = new Promise((resolve) => { | ||
| 624 | + const fn = (data) => { | ||
| 625 | + if (data.toString().includes(text)) { | ||
| 626 | + resolve(); | ||
| 627 | + child.stdout.off('data', fn); | ||
| 628 | + } | ||
| 629 | + }; | ||
| 630 | + child.stdout.on('data', fn); | ||
| 631 | + }); | ||
| 632 | + await seenPromise; | ||
| 633 | + } | ||
| 634 | + | ||
| 635 | + await waitForText('running'); | ||
| 636 | + await waitForEcho('first message'); | ||
| 637 | + const stopRestarts = restart(file); | ||
| 638 | + await waitForText('running'); | ||
| 639 | + stopRestarts(); | ||
| 640 | + await waitForEcho('second message'); | ||
| 641 | + const exitedPromise = once(child, 'exit'); | ||
| 642 | + child.send('exit'); | ||
| 643 | + await waitForText('Completed'); | ||
| 644 | + child.disconnect(); | ||
| 645 | + child.kill(); | ||
| 646 | + await exitedPromise; | ||
| 647 | + assert.strictEqual(stderr, ''); | ||
| 648 | + const lines = stdout.split(/\r?\n/).filter(Boolean); | ||
| 649 | + assert.deepStrictEqual(lines, [ | ||
| 650 | + 'running', | ||
| 651 | + 'Received: first message', | ||
| 652 | + `Restarting ${inspect(file)}`, | ||
| 653 | + 'running', | ||
| 654 | + 'Received: second message', | ||
| 655 | + `Completed running ${inspect(file)}`, | ||
| 656 | + ]); | ||
| 657 | + }); | ||
| 577 | 658 | }); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments