Skip to content

Commit 523793e

Browse files
authored
Merge pull request #208 from webpack/perf/update-watchers
improve watcher update performance
2 parents 90d93ba + dec2905 commit 523793e

2 files changed

Lines changed: 131 additions & 62 deletions

File tree

lib/watchpack.js

Lines changed: 125 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ let EXISTANCE_ONLY_TIME_ENTRY; // lazy required
1515
const EMPTY_ARRAY = [];
1616
const EMPTY_OPTIONS = {};
1717

18+
function addWatchpackWatchersToSet(watchers, set) {
19+
for (const ww of watchers) {
20+
const w = ww.watcher;
21+
if (!set.has(w.directoryWatcher)) {
22+
set.add(w.directoryWatcher);
23+
addWatchersToSet(w.directoryWatcher.directories.values(), set);
24+
}
25+
}
26+
}
27+
1828
function addWatchersToSet(watchers, set) {
1929
for (const w of watchers) {
2030
if (w !== true && !set.has(w.directoryWatcher)) {
@@ -62,6 +72,85 @@ const cachedNormalizeOptions = options => {
6272
return normalized;
6373
};
6474

75+
class WatchpackFileWatcher {
76+
constructor(watchpack, watcher, files) {
77+
if (!watcher) throw new Error();
78+
this.files = Array.isArray(files) ? files : [files];
79+
this.watcher = watcher;
80+
watcher.on("initial-missing", type => {
81+
for (const file of this.files) {
82+
if (!watchpack._missing.has(file))
83+
watchpack._onRemove(file, file, type);
84+
}
85+
});
86+
watcher.on("change", (mtime, type) => {
87+
for (const file of this.files) {
88+
watchpack._onChange(file, mtime, file, type);
89+
}
90+
});
91+
watcher.on("remove", type => {
92+
for (const file of this.files) {
93+
watchpack._onRemove(file, file, type);
94+
}
95+
});
96+
}
97+
98+
update(files) {
99+
if (!Array.isArray(files)) {
100+
if (this.files.length !== 1) {
101+
this.files = [files];
102+
} else if (this.files[0] !== files) {
103+
this.files[0] = files;
104+
}
105+
} else {
106+
this.files = files;
107+
}
108+
}
109+
110+
close() {
111+
this.watcher.close();
112+
}
113+
}
114+
115+
class WatchpackDirectoryWatcher {
116+
constructor(watchpack, watcher, directories) {
117+
if (!watcher) throw new Error();
118+
this.directories = Array.isArray(directories) ? directories : [directories];
119+
this.watcher = watcher;
120+
watcher.on("initial-missing", type => {
121+
for (const item of this.directories) {
122+
watchpack._onRemove(item, item, type);
123+
}
124+
});
125+
watcher.on("change", (file, mtime, type) => {
126+
for (const item of this.directories) {
127+
watchpack._onChange(item, mtime, file, type);
128+
}
129+
});
130+
watcher.on("remove", type => {
131+
for (const item of this.directories) {
132+
watchpack._onRemove(item, item, type);
133+
}
134+
});
135+
}
136+
137+
update(directories) {
138+
if (!Array.isArray(directories)) {
139+
if (this.directories.length !== 1) {
140+
this.directories = [directories];
141+
} else if (this.directories[0] !== directories) {
142+
this.directories[0] = directories;
143+
}
144+
} else {
145+
this.directories = directories;
146+
}
147+
}
148+
149+
close() {
150+
this.watcher.close();
151+
}
152+
}
153+
65154
class Watchpack extends EventEmitter {
66155
constructor(options) {
67156
super();
@@ -75,6 +164,7 @@ class Watchpack extends EventEmitter {
75164
this.watcherManager = getWatcherManager(this.watcherOptions);
76165
this.fileWatchers = new Map();
77166
this.directoryWatchers = new Map();
167+
this._missing = new Set();
78168
this.startTime = undefined;
79169
this.paused = false;
80170
this.aggregatedChanges = new Set();
@@ -99,18 +189,20 @@ class Watchpack extends EventEmitter {
99189
startTime = arg3;
100190
}
101191
this.paused = false;
102-
const oldFileWatchers = this.fileWatchers;
103-
const oldDirectoryWatchers = this.directoryWatchers;
192+
const fileWatchers = this.fileWatchers;
193+
const directoryWatchers = this.directoryWatchers;
104194
const ignored = this.watcherOptions.ignored;
105195
const filter = ignored
106196
? path => !ignored.test(path.replace(/\\/g, "/"))
107197
: () => true;
108198
const addToMap = (map, key, item) => {
109199
const list = map.get(key);
110200
if (list === undefined) {
111-
map.set(key, [item]);
112-
} else {
201+
map.set(key, item);
202+
} else if (Array.isArray(list)) {
113203
list.push(item);
204+
} else {
205+
map.set(key, [list, item]);
114206
}
115207
};
116208
const fileWatchersNeeded = new Map();
@@ -170,82 +262,47 @@ class Watchpack extends EventEmitter {
170262
}
171263
}
172264
}
173-
const newFileWatchers = new Map();
174-
const newDirectoryWatchers = new Map();
175-
const setupFileWatcher = (watcher, key, files) => {
176-
watcher.on("initial-missing", type => {
177-
for (const file of files) {
178-
if (!missingFiles.has(file)) this._onRemove(file, file, type);
179-
}
180-
});
181-
watcher.on("change", (mtime, type) => {
182-
for (const file of files) {
183-
this._onChange(file, mtime, file, type);
184-
}
185-
});
186-
watcher.on("remove", type => {
187-
for (const file of files) {
188-
this._onRemove(file, file, type);
189-
}
190-
});
191-
newFileWatchers.set(key, watcher);
192-
};
193-
const setupDirectoryWatcher = (watcher, key, directories) => {
194-
watcher.on("initial-missing", type => {
195-
for (const item of directories) {
196-
this._onRemove(item, item, type);
197-
}
198-
});
199-
watcher.on("change", (file, mtime, type) => {
200-
for (const item of directories) {
201-
this._onChange(item, mtime, file, type);
202-
}
203-
});
204-
watcher.on("remove", type => {
205-
for (const item of directories) {
206-
this._onRemove(item, item, type);
207-
}
208-
});
209-
newDirectoryWatchers.set(key, watcher);
210-
};
211265
// Close unneeded old watchers
212-
const fileWatchersToClose = [];
213-
const directoryWatchersToClose = [];
214-
for (const [key, w] of oldFileWatchers) {
215-
if (!fileWatchersNeeded.has(key)) {
266+
// and update existing watchers
267+
for (const [key, w] of fileWatchers) {
268+
const needed = fileWatchersNeeded.get(key);
269+
if (needed === undefined) {
216270
w.close();
271+
fileWatchers.delete(key);
217272
} else {
218-
fileWatchersToClose.push(w);
273+
w.update(needed);
274+
fileWatchersNeeded.delete(key);
219275
}
220276
}
221-
for (const [key, w] of oldDirectoryWatchers) {
222-
if (!directoryWatchersNeeded.has(key)) {
277+
for (const [key, w] of directoryWatchers) {
278+
const needed = directoryWatchersNeeded.get(key);
279+
if (needed === undefined) {
223280
w.close();
281+
directoryWatchers.delete(key);
224282
} else {
225-
directoryWatchersToClose.push(w);
283+
w.update(needed);
284+
directoryWatchersNeeded.delete(key);
226285
}
227286
}
228287
// Create new watchers and install handlers on these watchers
229288
watchEventSource.batch(() => {
230289
for (const [key, files] of fileWatchersNeeded) {
231290
const watcher = this.watcherManager.watchFile(key, startTime);
232291
if (watcher) {
233-
setupFileWatcher(watcher, key, files);
292+
fileWatchers.set(key, new WatchpackFileWatcher(this, watcher, files));
234293
}
235294
}
236295
for (const [key, directories] of directoryWatchersNeeded) {
237296
const watcher = this.watcherManager.watchDirectory(key, startTime);
238297
if (watcher) {
239-
setupDirectoryWatcher(watcher, key, directories);
298+
directoryWatchers.set(
299+
key,
300+
new WatchpackDirectoryWatcher(this, watcher, directories)
301+
);
240302
}
241303
}
242304
});
243-
// Close old watchers
244-
for (const w of fileWatchersToClose) w.close();
245-
for (const w of directoryWatchersToClose) w.close();
246-
// Store watchers
247-
this.fileWatchers = newFileWatchers;
248-
this.directoryWatchers = newDirectoryWatchers;
305+
this._missing = missingFiles;
249306
this.startTime = startTime;
250307
}
251308

@@ -265,8 +322,11 @@ class Watchpack extends EventEmitter {
265322

266323
getTimes() {
267324
const directoryWatchers = new Set();
268-
addWatchersToSet(this.fileWatchers.values(), directoryWatchers);
269-
addWatchersToSet(this.directoryWatchers.values(), directoryWatchers);
325+
addWatchpackWatchersToSet(this.fileWatchers.values(), directoryWatchers);
326+
addWatchpackWatchersToSet(
327+
this.directoryWatchers.values(),
328+
directoryWatchers
329+
);
270330
const obj = Object.create(null);
271331
for (const w of directoryWatchers) {
272332
const times = w.getTimes();
@@ -281,8 +341,11 @@ class Watchpack extends EventEmitter {
281341
.EXISTANCE_ONLY_TIME_ENTRY;
282342
}
283343
const directoryWatchers = new Set();
284-
addWatchersToSet(this.fileWatchers.values(), directoryWatchers);
285-
addWatchersToSet(this.directoryWatchers.values(), directoryWatchers);
344+
addWatchpackWatchersToSet(this.fileWatchers.values(), directoryWatchers);
345+
addWatchpackWatchersToSet(
346+
this.directoryWatchers.values(),
347+
directoryWatchers
348+
);
286349
const map = new Map();
287350
for (const w of directoryWatchers) {
288351
const times = w.getTimeInfoEntries();

test/ManyWatchers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ describe("ManyWatchers", function() {
6666
}
6767
w.watch({ files });
6868
console.timeEnd("creating/closing watchers");
69+
console.time("calling watch with the same files");
70+
for (let i = 0; i < 2000; i++) {
71+
w.watch({ files });
72+
}
73+
console.timeEnd("calling watch with the same files");
74+
6975
testHelper.tick(10000, () => {
7076
console.time("detecting change event");
7177
testHelper.file("4096/900/file");

0 commit comments

Comments
 (0)