| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e3c4852 commit e158281
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -87,10 +87,13 @@ ArrayPrototypeForEach(kWatchedPaths, (p) => watcher.watchPath(p)); | |||
| 87 | 87 | ||
| 88 | 88 | let graceTimer; | |
| 89 | 89 | let child; | |
| 90 | + let childExitPromise; | ||
| 90 | 91 | let exited; | |
| 92 | + let stopping; | ||
| 91 | 93 | ||
| 92 | 94 | function start() { | |
| 93 | 95 | exited = false; | |
| 96 | + stopping = false; | ||
| 94 | 97 | const stdio = kShouldFilterModules ? ['inherit', 'inherit', 'inherit', 'ipc'] : 'inherit'; | |
| 95 | 98 | child = spawn(process.execPath, argsWithoutWatchOptions, { | |
| 96 | 99 | stdio, | |
@@ -107,29 +110,36 @@ function start() { | |||
| 107 | 110 | ArrayPrototypeForEach(kOptionalEnvFiles, | |
| 108 | 111 | (file) => watcher.filterFile(resolve(file), undefined, { allowMissing: true })); | |
| 109 | 112 | } | |
| 110 | - child.once('exit', (code) => { | ||
| 113 | + childExitPromise = once(child, 'exit').then(({ 0: code }) => { | ||
| 111 | 114 | exited = true; | |
| 115 | + if (stopping) { | ||
| 116 | + return code; | ||
| 117 | + } | ||
| 112 | 118 | const waitingForChanges = 'Waiting for file changes before restarting...'; | |
| 113 | 119 | if (code === 0) { | |
| 114 | 120 | process.stdout.write(`${blue}Completed running ${kCommandStr}. ${waitingForChanges}${white}\n`); | |
| 115 | 121 | } else { | |
| 116 | 122 | process.stdout.write(`${red}Failed running ${kCommandStr}. ${waitingForChanges}${white}\n`); | |
| 117 | 123 | } | |
| 124 | + return code; | ||
| 118 | 125 | }); | |
| 119 | 126 | return child; | |
| 120 | 127 | } | |
| 121 | 128 | ||
| 122 | 129 | async function killAndWait(signal = kKillSignal, force = false) { | |
| 123 | - child?.removeAllListeners(); | ||
| 124 | - if (!child) { | ||
| 130 | + const processToKill = child; | ||
| 131 | + const onExit = childExitPromise; | ||
| 132 | + if (!processToKill) { | ||
| 125 | 133 | return; | |
| 126 | 134 | } | |
| 127 | - if ((child.killed || exited) && !force) { | ||
| 135 | + if ((processToKill.killed || exited) && !force) { | ||
| 128 | 136 | return; | |
| 129 | 137 | } | |
| 130 | - const onExit = once(child, 'exit'); | ||
| 131 | - child.kill(signal); | ||
| 132 | - const { 0: exitCode } = await onExit; | ||
| 138 | + stopping = true; | ||
| 139 | + if (!exited && processToKill.exitCode === null && processToKill.signalCode === null) { | ||
| 140 | + processToKill.kill(signal); | ||
| 141 | + } | ||
| 142 | + const exitCode = await onExit; | ||
| 133 | 143 | return exitCode; | |
| 134 | 144 | } | |
| 135 | 145 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -204,6 +204,9 @@ class FilesWatcher extends EventEmitter { | |||
| 204 | 204 | this.#filteredFiles.clear(); | |
| 205 | 205 | } | |
| 206 | 206 | clear() { | |
| 207 | + clearTimeout(this.#debounceTimer); | ||
| 208 | + this.#debounceTimer = null; | ||
| 209 | + this.#debounceOwners.clear(); | ||
| 207 | 210 | this.#watchers.forEach(this.#unwatch); | |
| 208 | 211 | this.#watchers.clear(); | |
| 209 | 212 | this.#filteredFiles.clear(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,69 @@ | |||
| 1 | + // Flags: --expose-internals | ||
| 2 | + import * as common from '../common/index.mjs'; | ||
| 3 | + import tmpdir from '../common/tmpdir.js'; | ||
| 4 | + import assert from 'node:assert'; | ||
| 5 | + import { writeFileSync } from 'node:fs'; | ||
| 6 | + import { createRequire } from 'node:module'; | ||
| 7 | + | ||
| 8 | + if (common.isIBMi) | ||
| 9 | + common.skip('IBMi does not support `fs.watch()`'); | ||
| 10 | + | ||
| 11 | + const require = createRequire(import.meta.url); | ||
| 12 | + const timers = require('node:timers'); | ||
| 13 | + const originalSetTimeout = timers.setTimeout; | ||
| 14 | + const originalClearTimeout = timers.clearTimeout; | ||
| 15 | + const { promise, resolve } = Promise.withResolvers(); | ||
| 16 | + const debounce = 1000; | ||
| 17 | + let debounceTimer; | ||
| 18 | + let debounceTimerCallback; | ||
| 19 | + let debounceTimerCleared = false; | ||
| 20 | + | ||
| 21 | + timers.setTimeout = function(fn, delay, ...args) { | ||
| 22 | + // Only intercept the FilesWatcher debounce timer configured below. | ||
| 23 | + if (delay === debounce) { | ||
| 24 | + const timer = { | ||
| 25 | + __proto__: null, | ||
| 26 | + ref() { return this; }, | ||
| 27 | + unref() { return this; }, | ||
| 28 | + }; | ||
| 29 | + debounceTimer = timer; | ||
| 30 | + debounceTimerCallback = () => { | ||
| 31 | + if (!debounceTimerCleared) { | ||
| 32 | + fn(...args); | ||
| 33 | + } | ||
| 34 | + }; | ||
| 35 | + resolve(); | ||
| 36 | + return timer; | ||
| 37 | + } | ||
| 38 | + return originalSetTimeout(fn, delay, ...args); | ||
| 39 | + }; | ||
| 40 | + | ||
| 41 | + timers.clearTimeout = function(timer) { | ||
| 42 | + if (timer === debounceTimer) { | ||
| 43 | + debounceTimerCleared = true; | ||
| 44 | + } | ||
| 45 | + return originalClearTimeout(timer); | ||
| 46 | + }; | ||
| 47 | + | ||
| 48 | + try { | ||
| 49 | + const { FilesWatcher } = require('internal/watch_mode/files_watcher'); | ||
| 50 | + | ||
| 51 | + tmpdir.refresh(); | ||
| 52 | + const file = tmpdir.resolve('watcher-clear.js'); | ||
| 53 | + writeFileSync(file, '0'); | ||
| 54 | + | ||
| 55 | + const watcher = new FilesWatcher({ debounce, mode: 'all' }); | ||
| 56 | + watcher.on('changed', common.mustNotCall()); | ||
| 57 | + watcher.watchPath(file, false); | ||
| 58 | + | ||
| 59 | + const interval = setInterval(() => writeFileSync(file, `${Date.now()}`), 50); | ||
| 60 | + await promise; | ||
| 61 | + clearInterval(interval); | ||
| 62 | + | ||
| 63 | + watcher.clear(); | ||
| 64 | + assert.strictEqual(debounceTimerCleared, true); | ||
| 65 | + debounceTimerCallback(); | ||
| 66 | + } finally { | ||
| 67 | + timers.setTimeout = originalSetTimeout; | ||
| 68 | + timers.clearTimeout = originalClearTimeout; | ||
| 69 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -171,6 +171,23 @@ async function failWriteSucceed({ file, watchedFile }) { | |||
| 171 | 171 | tmpdir.refresh(); | |
| 172 | 172 | ||
| 173 | 173 | describe('watch mode', { concurrency: !process.env.TEST_PARALLEL, timeout: 60_000 }, () => { | |
| 174 | + it('should exit when terminated after the watched process has completed', async () => { | ||
| 175 | + const file = createTmpFile(); | ||
| 176 | + const child = spawn(execPath, ['--watch', '--no-warnings', file], { | ||
| 177 | + encoding: 'utf8', | ||
| 178 | + stdio: 'pipe', | ||
| 179 | + }); | ||
| 180 | + | ||
| 181 | + for await (const line of createInterface({ input: child.stdout })) { | ||
| 182 | + if (line.includes('Completed running')) { | ||
| 183 | + break; | ||
| 184 | + } | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + child.kill(); | ||
| 188 | + await once(child, 'exit'); | ||
| 189 | + }); | ||
| 190 | + | ||
| 174 | 191 | it('should watch changes to a file', async () => { | |
| 175 | 192 | const file = createTmpFile(); | |
| 176 | 193 | const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: file, watchFlag: '--watch=true', options: { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments