From bfe216c8acd06975a18b208ca7c1f3ad58cfba1f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 May 2019 04:09:44 -0600 Subject: [PATCH 1/4] Revert "Merge pull request #19345 from atom/ns/notify-retreat" This reverts commit 3d37651ac13789cc546de2ad97683741ed38df1a, reversing changes made to e1a5d520e0d4bcd26692e3d811cb63ca92152506. --- src/config-schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-schema.js b/src/config-schema.js index 345e6dd78..1a8c9503e 100644 --- a/src/config-schema.js +++ b/src/config-schema.js @@ -331,7 +331,7 @@ const configSchema = { fileSystemWatcher: { description: 'Choose the underlying implementation used to watch for filesystem changes. Emulating changes will miss any events caused by applications other than Atom, but may help prevent crashes or freezes. Polling may be useful for network drives, but will be more costly in terms of CPU overhead.
This setting will require a relaunch of Atom to take effect.', type: 'string', - default: 'native', + default: 'experimental', enum: [ { value: 'native', From 145ebb1a631fed7e512a38831b7144cd876ce9fa Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 May 2019 04:09:52 -0600 Subject: [PATCH 2/4] Revert "Merge pull request #19331 from atom/ns-as/notify-snapshot-exclude" This reverts commit 1e08ad8470c5780c9e0bb804e8b562bc7927a771, reversing changes made to 0994d8ae8011363b3b39c70f35d23493d00f4c49. --- package-lock.json | 6 +++--- package.json | 2 +- script/lib/generate-startup-snapshot.js | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a8510da0..f8b3b7fa7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -456,9 +456,9 @@ } }, "@atom/notify": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@atom/notify/-/notify-1.3.3.tgz", - "integrity": "sha512-AExln6/wUVEo4mpkMmJu1n37RxQ1qKlwvGKaMncL5UkIEXYzKtjs303OS5T9WZ0Ks/YDUOn7B9Np3t4BIYbiYg==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@atom/notify/-/notify-1.3.2.tgz", + "integrity": "sha512-EGCDK33j4mstsdbpHXmSVOsi27uzFGWv+9CFctiMQy8rPD5DVOWuLLkES+74nsiFgzlkEbR1ahgQghpnjPzB1Q==" }, "@atom/nsfw": { "version": "1.0.22", diff --git a/package.json b/package.json index bdde15347..577b44472 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "license": "MIT", "electronVersion": "2.0.18", "dependencies": { - "@atom/notify": "1.3.3", + "@atom/notify": "1.3.2", "@atom/nsfw": "1.0.22", "@atom/source-map-support": "^0.3.4", "@atom/watcher": "1.3.1", diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 5581e8bc7..c8cf02022 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -69,7 +69,6 @@ module.exports = function (packagedAppPath) { requiredModuleRelativePath === path.join('..', 'node_modules', 'yauzl', 'index.js') || requiredModuleRelativePath === path.join('..', 'node_modules', 'winreg', 'lib', 'registry.js') || requiredModuleRelativePath === path.join('..', 'node_modules', '@atom', 'fuzzy-native', 'lib', 'main.js') || - requiredModuleRelativePath === path.join('..', 'node_modules', '@atom', 'notify', 'lib', 'bin-path.js') || // The startup-time script is used by both the renderer and the main process and having it in the // snapshot causes issues. requiredModuleRelativePath === path.join('..', 'src', 'startup-time.js') From 4f08da2a988dc28f09004cba16bcf48ae25d363e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 May 2019 04:10:10 -0600 Subject: [PATCH 3/4] Revert "Merge pull request #19325 from atom/ns/notify-asar-exclude" This reverts commit da8b1a1a421f38d50aa7d37f5d1a97a400b9a192, reversing changes made to 1edf94a24032a8a0e555c3db7aa7de01e5b00633. --- package-lock.json | 6 +++--- package.json | 2 +- script/lib/package-application.js | 1 - src/path-watcher.js | 3 +-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8b3b7fa7..4deeb1d89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -456,9 +456,9 @@ } }, "@atom/notify": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@atom/notify/-/notify-1.3.2.tgz", - "integrity": "sha512-EGCDK33j4mstsdbpHXmSVOsi27uzFGWv+9CFctiMQy8rPD5DVOWuLLkES+74nsiFgzlkEbR1ahgQghpnjPzB1Q==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@atom/notify/-/notify-1.2.1.tgz", + "integrity": "sha512-d1mZlBmPrYbk/SS1q0+gq/I9lG58a+PZ5y9vKBNuWzbgVaDPhpYBJyiO4glr80UbTxCQ/KW8AAD+rY517P8TfA==" }, "@atom/nsfw": { "version": "1.0.22", diff --git a/package.json b/package.json index 577b44472..80336f196 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "license": "MIT", "electronVersion": "2.0.18", "dependencies": { - "@atom/notify": "1.3.2", + "@atom/notify": "1.2.1", "@atom/nsfw": "1.0.22", "@atom/source-map-support": "^0.3.4", "@atom/watcher": "1.3.1", diff --git a/script/lib/package-application.js b/script/lib/package-application.js index 5dbf5552c..d8f7e94eb 100644 --- a/script/lib/package-application.js +++ b/script/lib/package-application.js @@ -112,7 +112,6 @@ function buildAsarUnpackGlobExpression () { path.join('**', 'node_modules', 'dugite', 'git', '**'), path.join('**', 'node_modules', 'github', 'bin', '**'), path.join('**', 'node_modules', 'vscode-ripgrep', 'bin', '**'), - path.join('**', 'node_modules', '@atom', 'notify', 'bin', '**'), path.join('**', 'resources', 'atom.png') ] diff --git a/src/path-watcher.js b/src/path-watcher.js index bd6da5ff9..cb690299d 100644 --- a/src/path-watcher.js +++ b/src/path-watcher.js @@ -586,8 +586,7 @@ class PathWatcherManager { if (this.useExperimentalWatcher()) { if (!this.notifyWatcher) { const options = { - transformBinPath: (binPath) => binPath.replace(/\bapp\.asar\b/, 'app.asar.unpacked'), - onError: error => { + onError: (error) => { throw new Error(`Error watching file system: ${error}`) } } From 770b8fdcf8bd8bbc314faf36783909ea3d4d599e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 May 2019 04:10:17 -0600 Subject: [PATCH 4/4] Revert "Merge pull request #19244 from atom/ns/notify" This reverts commit b2ecabd527e59340f3acdc307541f773e8dcc83e, reversing changes made to c3bf95194a1da07f22b30247440e3076d9a0c3da. --- package-lock.json | 5 - package.json | 1 - script/test | 1 - script/vsts/platforms/windows.yml | 5 +- spec/path-watcher-spec.js | 201 +++++++++++++-------------- spec/project-spec.js | 53 +++---- src/config-schema.js | 13 +- src/main-process/atom-application.js | 1 - src/path-watcher.js | 169 +++++++++++++++------- 9 files changed, 250 insertions(+), 199 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4deeb1d89..a4c519a52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -455,11 +455,6 @@ } } }, - "@atom/notify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@atom/notify/-/notify-1.2.1.tgz", - "integrity": "sha512-d1mZlBmPrYbk/SS1q0+gq/I9lG58a+PZ5y9vKBNuWzbgVaDPhpYBJyiO4glr80UbTxCQ/KW8AAD+rY517P8TfA==" - }, "@atom/nsfw": { "version": "1.0.22", "resolved": "https://registry.npmjs.org/@atom/nsfw/-/nsfw-1.0.22.tgz", diff --git a/package.json b/package.json index 80336f196..0696ba29c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "license": "MIT", "electronVersion": "2.0.18", "dependencies": { - "@atom/notify": "1.2.1", "@atom/nsfw": "1.0.22", "@atom/source-map-support": "^0.3.4", "@atom/watcher": "1.3.1", diff --git a/script/test b/script/test index 3330e7145..adb8a5123 100755 --- a/script/test +++ b/script/test @@ -48,7 +48,6 @@ const resourcePath = CONFIG.repositoryRootPath let executablePath if (process.platform === 'darwin') { const executablePaths = glob.sync(path.join(CONFIG.buildOutputPath, '*.app')) - assert(executablePaths.length > 0, `No application found in ${CONFIG.buildOutputPath} to run tests against`) assert(executablePaths.length === 1, `More than one application to run tests against was found. ${executablePaths.join(',')}`) executablePath = path.join(executablePaths[0], 'Contents', 'MacOS', path.basename(executablePaths[0], '.app')) } else if (process.platform === 'linux') { diff --git a/script/vsts/platforms/windows.yml b/script/vsts/platforms/windows.yml index 4b2898d99..de8247335 100644 --- a/script/vsts/platforms/windows.yml +++ b/script/vsts/platforms/windows.yml @@ -78,10 +78,7 @@ jobs: IS_SIGNED_ZIP_BRANCH: $(IsSignedZipBranch) displayName: Build Atom - - powershell: | - # Normalize %TEMP% as a long (non 8.3) path to avoid assertion failures comparing paths in temp folders - $env:TEMP = (Get-Item -LiteralPath $env:TEMP).FullName - node script\vsts\windows-run.js script\test.cmd + - script: node script\vsts\windows-run.js script\test.cmd env: CI: true CI_PROVIDER: VSTS diff --git a/spec/path-watcher-spec.js b/spec/path-watcher-spec.js index 27ab6dea9..ec1993f96 100644 --- a/spec/path-watcher-spec.js +++ b/spec/path-watcher-spec.js @@ -6,7 +6,7 @@ import path from 'path' import { promisify } from 'util' import { CompositeDisposable } from 'event-kit' -import { PathWatcherManager } from '../src/path-watcher' +import { watchPath, stopAllWatchers } from '../src/path-watcher' temp.track() @@ -17,7 +17,7 @@ const realpath = promisify(fs.realpath) const tempMkdir = promisify(temp.mkdir) -describe('PathWatcherManager', function () { +describe('watchPath', function () { let subs beforeEach(function () { @@ -26,6 +26,7 @@ describe('PathWatcherManager', function () { afterEach(async function () { subs.dispose() + await stopAllWatchers() }) function waitForChanges (watcher, ...fileNames) { @@ -50,136 +51,124 @@ describe('PathWatcherManager', function () { }) } - describe('in "native" mode', () => { - let manager + describe('watchPath()', function () { + it('resolves the returned promise when the watcher begins listening', async function () { + const rootDir = await tempMkdir('atom-fsmanager-test-') - beforeEach(function () { - manager = new PathWatcherManager('native') + const watcher = await watchPath(rootDir, {}, () => {}) + expect(watcher.constructor.name).toBe('PathWatcher') }) - afterEach(async function () { - await manager.stopAllWatchers() + it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function () { + const rootDir = await tempMkdir('atom-fsmanager-test-') + + const watcher0 = await watchPath(rootDir, {}, () => {}) + const watcher1 = await watchPath(rootDir, {}, () => {}) + + expect(watcher0.native).toBe(watcher1.native) }) - describe('watchPath()', function () { - it('resolves the returned promise when the watcher begins listening', async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-') + it("reuses existing native watchers even while they're still starting", async function () { + const rootDir = await tempMkdir('atom-fsmanager-test-') - const watcher = await manager.watchPath(rootDir, {}, () => {}) - expect(watcher.constructor.name).toBe('PathWatcher') - }) + const [watcher0, watcher1] = await Promise.all([ + watchPath(rootDir, {}, () => {}), + watchPath(rootDir, {}, () => {}) + ]) + expect(watcher0.native).toBe(watcher1.native) + }) - it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-') + it("doesn't attach new watchers to a native watcher that's stopping", async function () { + const rootDir = await tempMkdir('atom-fsmanager-test-') - const watcher0 = await manager.watchPath(rootDir, {}, () => {}) - const watcher1 = await manager.watchPath(rootDir, {}, () => {}) + const watcher0 = await watchPath(rootDir, {}, () => {}) + const native0 = watcher0.native - expect(watcher0.native).toBe(watcher1.native) - }) + watcher0.dispose() + const watcher1 = await watchPath(rootDir, {}, () => {}) - it("reuses existing native watchers even while they're still starting", async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-') + expect(watcher1.native).not.toBe(native0) + }) - const [watcher0, watcher1] = await Promise.all([ - manager.watchPath(rootDir, {}, () => {}), - manager.watchPath(rootDir, {}, () => {}) - ]) - expect(watcher0.native).toBe(watcher1.native) - }) + it('reuses an existing native watcher on a parent directory and filters events', async function () { + const rootDir = await tempMkdir('atom-fsmanager-test-').then(realpath) + const rootFile = path.join(rootDir, 'rootfile.txt') + const subDir = path.join(rootDir, 'subdir') + const subFile = path.join(subDir, 'subfile.txt') - it("doesn't attach new watchers to a native watcher that's stopping", async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-') + await mkdir(subDir) - const watcher0 = await manager.watchPath(rootDir, {}, () => {}) - const native0 = watcher0.native + // Keep the watchers alive with an undisposed subscription + const rootWatcher = await watchPath(rootDir, {}, () => {}) + const childWatcher = await watchPath(subDir, {}, () => {}) - watcher0.dispose() - const watcher1 = await manager.watchPath(rootDir, {}, () => {}) + expect(rootWatcher.native).toBe(childWatcher.native) + expect(rootWatcher.native.isRunning()).toBe(true) - expect(watcher1.native).not.toBe(native0) - }) + const firstChanges = Promise.all([ + waitForChanges(rootWatcher, subFile), + waitForChanges(childWatcher, subFile) + ]) + await writeFile(subFile, 'subfile\n', { encoding: 'utf8' }) + await firstChanges - it('reuses an existing native watcher on a parent directory and filters events', async function () { - const rootDir = await tempMkdir('atom-fsmanager-test-').then(realpath) - const rootFile = path.join(rootDir, 'rootfile.txt') - const subDir = path.join(rootDir, 'subdir') - const subFile = path.join(subDir, 'subfile.txt') + const nextRootEvent = waitForChanges(rootWatcher, rootFile) + await writeFile(rootFile, 'rootfile\n', { encoding: 'utf8' }) + await nextRootEvent + }) - await mkdir(subDir) + it('adopts existing child watchers and filters events appropriately to them', async function () { + const parentDir = await tempMkdir('atom-fsmanager-test-') + .then(realpath) - // Keep the watchers alive with an undisposed subscription - const rootWatcher = await manager.watchPath(rootDir, {}, () => {}) - const childWatcher = await manager.watchPath(subDir, {}, () => {}) + // Create the directory tree + const rootFile = path.join(parentDir, 'rootfile.txt') + const subDir0 = path.join(parentDir, 'subdir0') + const subFile0 = path.join(subDir0, 'subfile0.txt') + const subDir1 = path.join(parentDir, 'subdir1') + const subFile1 = path.join(subDir1, 'subfile1.txt') - expect(rootWatcher.native).toBe(childWatcher.native) - expect(rootWatcher.native.isRunning()).toBe(true) + await mkdir(subDir0) + await mkdir(subDir1) + await Promise.all([ + writeFile(rootFile, 'rootfile\n', { encoding: 'utf8' }), + writeFile(subFile0, 'subfile 0\n', { encoding: 'utf8' }), + writeFile(subFile1, 'subfile 1\n', { encoding: 'utf8' }) + ]) - const firstChanges = Promise.all([ - waitForChanges(rootWatcher, subFile), - waitForChanges(childWatcher, subFile) - ]) - await writeFile(subFile, 'subfile\n', { encoding: 'utf8' }) - await firstChanges + // Begin the child watchers and keep them alive + const subWatcher0 = await watchPath(subDir0, {}, () => {}) + const subWatcherChanges0 = waitForChanges(subWatcher0, subFile0) - const nextRootEvent = waitForChanges(rootWatcher, rootFile) - await writeFile(rootFile, 'rootfile\n', { encoding: 'utf8' }) - await nextRootEvent - }) + const subWatcher1 = await watchPath(subDir1, {}, () => {}) + const subWatcherChanges1 = waitForChanges(subWatcher1, subFile1) - it('adopts existing child watchers and filters events appropriately to them', async function () { - const parentDir = await tempMkdir('atom-fsmanager-test-') - .then(realpath) + expect(subWatcher0.native).not.toBe(subWatcher1.native) - // Create the directory tree - const rootFile = path.join(parentDir, 'rootfile.txt') - const subDir0 = path.join(parentDir, 'subdir0') - const subFile0 = path.join(subDir0, 'subfile0.txt') - const subDir1 = path.join(parentDir, 'subdir1') - const subFile1 = path.join(subDir1, 'subfile1.txt') + // Create the parent watcher + const parentWatcher = await watchPath(parentDir, {}, () => {}) + const parentWatcherChanges = waitForChanges( + parentWatcher, + rootFile, + subFile0, + subFile1 + ) - await mkdir(subDir0) - await mkdir(subDir1) - await Promise.all([ - writeFile(rootFile, 'rootfile\n', { encoding: 'utf8' }), - writeFile(subFile0, 'subfile 0\n', { encoding: 'utf8' }), - writeFile(subFile1, 'subfile 1\n', { encoding: 'utf8' }) - ]) + expect(subWatcher0.native).toBe(parentWatcher.native) + expect(subWatcher1.native).toBe(parentWatcher.native) - // Begin the child watchers and keep them alive - const subWatcher0 = await manager.watchPath(subDir0, {}, () => {}) - const subWatcherChanges0 = waitForChanges(subWatcher0, subFile0) + // Ensure events are filtered correctly + await Promise.all([ + appendFile(rootFile, 'change\n', { encoding: 'utf8' }), + appendFile(subFile0, 'change\n', { encoding: 'utf8' }), + appendFile(subFile1, 'change\n', { encoding: 'utf8' }) + ]) - const subWatcher1 = await manager.watchPath(subDir1, {}, () => {}) - const subWatcherChanges1 = waitForChanges(subWatcher1, subFile1) - - expect(subWatcher0.native).not.toBe(subWatcher1.native) - - // Create the parent watcher - const parentWatcher = await manager.watchPath(parentDir, {}, () => {}) - const parentWatcherChanges = waitForChanges( - parentWatcher, - rootFile, - subFile0, - subFile1 - ) - - expect(subWatcher0.native).toBe(parentWatcher.native) - expect(subWatcher1.native).toBe(parentWatcher.native) - - // Ensure events are filtered correctly - await Promise.all([ - appendFile(rootFile, 'change\n', { encoding: 'utf8' }), - appendFile(subFile0, 'change\n', { encoding: 'utf8' }), - appendFile(subFile1, 'change\n', { encoding: 'utf8' }) - ]) - - await Promise.all([ - subWatcherChanges0, - subWatcherChanges1, - parentWatcherChanges - ]) - }) + await Promise.all([ + subWatcherChanges0, + subWatcherChanges1, + parentWatcherChanges + ]) }) }) }) diff --git a/spec/project-spec.js b/spec/project-spec.js index 4a364632e..2025cae71 100644 --- a/spec/project-spec.js +++ b/spec/project-spec.js @@ -59,7 +59,7 @@ describe('Project', () => { }) }) - it('does not deserialize paths that are now files', async () => { + it('does not deserialize paths that are now files', () => { const childPath = path.join(temp.mkdirSync('atom-spec-project'), 'child') fs.mkdirSync(childPath) @@ -70,21 +70,22 @@ describe('Project', () => { grammarRegistry: atom.grammars }) atom.project.setPaths([childPath]) - await stopAllWatchers() const state = atom.project.serialize() fs.rmdirSync(childPath) fs.writeFileSync(childPath, 'surprise!\n') let err = null - try { - await deserializedProject.deserialize(state, atom.deserializers) - } catch (e) { - err = e - } + waitsForPromise(() => + deserializedProject.deserialize(state, atom.deserializers).catch(e => { + err = e + }) + ) - expect(deserializedProject.getPaths()).toEqual([]) - expect(err.missingProjectPaths).toEqual([childPath]) + runs(() => { + expect(deserializedProject.getPaths()).toEqual([]) + expect(err.missingProjectPaths).toEqual([childPath]) + }) }) it('does not include unretained buffers in the serialized state', () => { @@ -1055,20 +1056,18 @@ describe('Project', () => { afterEach(() => sub.dispose()) const waitForEvents = paths => { - const remaining = new Set(paths.map(path => fs.realpathSync(path))) + const remaining = new Set(paths.map(p => fs.realpathSync(p))) return new Promise((resolve, reject) => { checkCallback = () => { for (let event of events) { remaining.delete(event.path) } if (remaining.size === 0) { - clearTimeout(timeout) resolve() } } const expire = () => { - clearTimeout(interval) checkCallback = () => {} console.error('Paths not seen:', remaining) reject( @@ -1076,33 +1075,37 @@ describe('Project', () => { ) } - const interval = setInterval(checkCallback, 100) - const timeout = setTimeout(expire, 2000) + checkCallback() + setTimeout(expire, 2000) }) } - it('reports filesystem changes within project paths', async () => { + it('reports filesystem changes within project paths', () => { const dirOne = temp.mkdirSync('atom-spec-project-one') const fileOne = path.join(dirOne, 'file-one.txt') const fileTwo = path.join(dirOne, 'file-two.txt') const dirTwo = temp.mkdirSync('atom-spec-project-two') const fileThree = path.join(dirTwo, 'file-three.txt') - await stopAllWatchers() + // Ensure that all preexisting watchers are stopped + waitsForPromise(() => stopAllWatchers()) - atom.project.setPaths([dirOne]) + runs(() => atom.project.setPaths([dirOne])) + waitsForPromise(() => atom.project.getWatcherPromise(dirOne)) - await atom.project.getWatcherPromise(dirOne) + runs(() => { + expect(atom.project.watcherPromisesByPath[dirTwo]).toEqual(undefined) - expect(atom.project.watcherPromisesByPath[dirTwo]).toEqual(undefined) + fs.writeFileSync(fileThree, 'three\n') + fs.writeFileSync(fileTwo, 'two\n') + fs.writeFileSync(fileOne, 'one\n') + }) - fs.writeFileSync(fileThree, 'three\n') - fs.writeFileSync(fileTwo, 'two\n') - fs.writeFileSync(fileOne, 'one\n') + waitsForPromise(() => waitForEvents([fileOne, fileTwo])) - await waitForEvents([fileOne, fileTwo]) - - expect(events.some(event => event.path === fileThree)).toBeFalsy() + runs(() => + expect(events.some(event => event.path === fileThree)).toBeFalsy() + ) }) }) diff --git a/src/config-schema.js b/src/config-schema.js index 1a8c9503e..39831b2b6 100644 --- a/src/config-schema.js +++ b/src/config-schema.js @@ -329,17 +329,17 @@ const configSchema = { default: 40 }, fileSystemWatcher: { - description: 'Choose the underlying implementation used to watch for filesystem changes. Emulating changes will miss any events caused by applications other than Atom, but may help prevent crashes or freezes. Polling may be useful for network drives, but will be more costly in terms of CPU overhead.
This setting will require a relaunch of Atom to take effect.', + description: 'Choose the underlying implementation used to watch for filesystem changes. Emulating changes will miss any events caused by applications other than Atom, but may help prevent crashes or freezes.', type: 'string', - default: 'experimental', + default: 'native', enum: [ { value: 'native', - description: 'Native operating system APIs (@atom/nsfw)' + description: 'Native operating system APIs' }, { value: 'experimental', - description: 'Experimental (@atom/notify)' + description: 'Experimental filesystem watching library' }, { value: 'poll', @@ -351,11 +351,6 @@ const configSchema = { } ] }, - fileSystemWatcherPollInterval: { - description: "If the 'Polling' option is selected for the file system watcher, this will be the interval between polls.", - type: 'number', - default: 1000 - }, useTreeSitterParsers: { type: 'boolean', default: true, diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 2cda58d0f..8a7be9c84 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -282,7 +282,6 @@ class AtomApplication extends EventEmitter { } this.config.onDidChange('core.titleBar', () => this.promptForRestart()) this.config.onDidChange('core.colorProfile', () => this.promptForRestart()) - this.config.onDidChange('core.fileSystemWatcher', () => this.promptForRestart()) } let optionsForWindowsToOpen = [] diff --git a/src/path-watcher.js b/src/path-watcher.js index cb690299d..6693489ef 100644 --- a/src/path-watcher.js +++ b/src/path-watcher.js @@ -2,9 +2,8 @@ const fs = require('fs') const path = require('path') const {Emitter, Disposable, CompositeDisposable} = require('event-kit') - -const NotifyWatcher = require('@atom/notify') const nsfw = require('@atom/nsfw') +const watcher = require('@atom/watcher') const {NativeWatcherRegistry} = require('./native-watcher-registry') // Private: Associate native watcher action flags with descriptive String equivalents. @@ -385,7 +384,12 @@ class PathWatcher { return this.normalizedPathPromise } - // Private: Return a {Promise} that will resolve when the underlying native watcher is ready to begin sending events. + // Private: Return a {Promise} that will resolve the first time that this watcher is attached to a native watcher. + getAttachedPromise () { + return this.attachedPromise + } + + // Extended: Return a {Promise} that will resolve when the underlying native watcher is ready to begin sending events. // When testing filesystem watchers, it's important to await this promise before making filesystem changes that you // intend to assert about because there will be a delay between the instantiation of the watcher and the activation // of the underlying OS resources that feed its events. @@ -540,18 +544,50 @@ class PathWatcherManager { // Private: Access the currently active manager instance, creating one if necessary. static active () { if (!this.activeManager) { - this.activeManager = new PathWatcherManager( - atom.config.get('core.fileSystemWatcher'), - atom.config.get('core.fileSystemWatcherPollInterval') - ) + this.activeManager = new PathWatcherManager(atom.config.get('core.fileSystemWatcher')) + this.sub = atom.config.onDidChange('core.fileSystemWatcher', ({newValue}) => { this.transitionTo(newValue) }) } return this.activeManager } + // Private: Replace the active {PathWatcherManager} with a new one that creates [NativeWatchers]{NativeWatcher} + // based on the value of `setting`. + static async transitionTo (setting) { + const current = this.active() + + if (this.transitionPromise) { + await this.transitionPromise + } + + if (current.setting === setting) { + return + } + current.isShuttingDown = true + + let resolveTransitionPromise = () => {} + this.transitionPromise = new Promise(resolve => { + resolveTransitionPromise = resolve + }) + + const replacement = new PathWatcherManager(setting) + this.activeManager = replacement + + await Promise.all( + Array.from(current.live, async ([root, native]) => { + const w = await replacement.createWatcher(root, {}, () => {}) + native.reattachTo(w.native, root, w.native.options || {}) + }) + ) + + current.stopAllWatchers() + + resolveTransitionPromise() + this.transitionPromise = null + } + // Private: Initialize global {PathWatcher} state. - constructor (setting, pollInterval) { + constructor (setting) { this.setting = setting - this.pollInterval = pollInterval this.live = new Map() const initLocal = NativeConstructor => { @@ -572,9 +608,15 @@ class PathWatcherManager { if (setting === 'atom') { initLocal(AtomNativeWatcher) - } else if (setting === 'native') { + } else if (setting === 'experimental') { + // + } else if (setting === 'poll') { + // + } else { initLocal(NSFWNativeWatcher) } + + this.isShuttingDown = false } useExperimentalWatcher () { @@ -582,53 +624,66 @@ class PathWatcherManager { } // Private: Create a {PathWatcher} tied to this global state. See {watchPath} for detailed arguments. - async watchPath (rootPath, options, eventCallback) { + async createWatcher (rootPath, options, eventCallback) { + if (this.isShuttingDown) { + await this.constructor.transitionPromise + return PathWatcherManager.active().createWatcher(rootPath, options, eventCallback) + } + if (this.useExperimentalWatcher()) { - if (!this.notifyWatcher) { - const options = { - onError: (error) => { - throw new Error(`Error watching file system: ${error}`) - } - } - if (this.setting === 'poll') { - options.pollInterval = this.pollInterval - } - this.notifyWatcher = new NotifyWatcher(options) + if (this.setting === 'poll') { + options.poll = true } - const watch = await this.notifyWatcher.watchPath(rootPath, event => { - if (event.action === 'error') { - watch.emitter.emit('error', event.description) - throw new Error(`Error watching file system at "${event.path}": ${event.description}`) - } else { - eventCallback(event) - } - }) - watch.emitter = new Emitter() - watch.onDidError = function (handler) { - return this.emitter.on('error', handler) - } - return watch - } else { - const w = new PathWatcher(this.nativeRegistry, rootPath, options) - w.onDidChange(eventCallback) - await w.getStartPromise() + const w = await watcher.watchPath(rootPath, options, eventCallback) + this.live.set(rootPath, w.native) return w } + + const w = new PathWatcher(this.nativeRegistry, rootPath, options) + w.onDidChange(eventCallback) + await w.getStartPromise() + return w + } + + // Private: Directly access the {NativeWatcherRegistry}. + getRegistry () { + if (this.useExperimentalWatcher()) { + return watcher.getRegistry() + } + + return this.nativeRegistry + } + + // Private: Sample watcher usage statistics. Only available for experimental watchers. + status () { + if (this.useExperimentalWatcher()) { + return watcher.status() + } + + return {} + } + + // Private: Return a {String} depicting the currently active native watchers. + print () { + if (this.useExperimentalWatcher()) { + return watcher.printWatchers() + } + + return this.nativeRegistry.print() } // Private: Stop all living watchers. // // Returns a {Promise} that resolves when all native watcher resources are disposed. - async stopAllWatchers () { + stopAllWatchers () { if (this.useExperimentalWatcher()) { - await this.notifyWatcher.kill() - this.notifyWatcher = null - } else { - await Promise.all( - Array.from(this.live, ([, w]) => w.stop()) - ) + return watcher.stopAllWatchers() } + + return Promise.all( + Array.from(this.live, ([, w]) => w.stop()) + ) } } @@ -672,7 +727,7 @@ class PathWatcherManager { // ``` // function watchPath (rootPath, options, eventCallback) { - return PathWatcherManager.active().watchPath(rootPath, options, eventCallback) + return PathWatcherManager.active().createWatcher(rootPath, options, eventCallback) } // Private: Return a Promise that resolves when all {NativeWatcher} instances associated with a FileSystemManager @@ -681,4 +736,24 @@ function stopAllWatchers () { return PathWatcherManager.active().stopAllWatchers() } -module.exports = {watchPath, stopAllWatchers, PathWatcherManager} +// Private: Show the currently active native watchers in a formatted {String}. +watchPath.printWatchers = function () { + return PathWatcherManager.active().print() +} + +// Private: Access the active {NativeWatcherRegistry}. +watchPath.getRegistry = function () { + return PathWatcherManager.active().getRegistry() +} + +// Private: Sample usage statistics for the active watcher. +watchPath.status = function () { + return PathWatcherManager.active().status() +} + +// Private: Configure @atom/watcher ("experimental") directly. +watchPath.configure = function (...args) { + return watcher.configure(...args) +} + +module.exports = {watchPath, stopAllWatchers}