Skip to content

Commit d198e4e

Browse files
authored
fix: remove process listeners after stopping the server (#4013)
1 parent 67b52c9 commit d198e4e

File tree

2 files changed

+80
-24
lines changed

2 files changed

+80
-24
lines changed

lib/Server.js

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Server {
3232

3333
this.options = options;
3434
this.staticWatchers = [];
35+
this.listeners = [];
3536
// Keep track of websocket proxies for external websocket upgrade.
3637
this.webSocketProxies = [];
3738
this.sockets = [];
@@ -1168,15 +1169,11 @@ class Server {
11681169

11691170
let needForceShutdown = false;
11701171

1171-
const exitProcess = () => {
1172-
// eslint-disable-next-line no-process-exit
1173-
process.exit();
1174-
};
1175-
11761172
signals.forEach((signal) => {
1177-
process.on(signal, () => {
1173+
const listener = () => {
11781174
if (needForceShutdown) {
1179-
exitProcess();
1175+
// eslint-disable-next-line no-process-exit
1176+
process.exit();
11801177
}
11811178

11821179
this.logger.info(
@@ -1187,12 +1184,20 @@ class Server {
11871184

11881185
this.stopCallback(() => {
11891186
if (typeof this.compiler.close === "function") {
1190-
this.compiler.close(exitProcess);
1187+
this.compiler.close(() => {
1188+
// eslint-disable-next-line no-process-exit
1189+
process.exit();
1190+
});
11911191
} else {
1192-
exitProcess();
1192+
// eslint-disable-next-line no-process-exit
1193+
process.exit();
11931194
}
11941195
});
1195-
});
1196+
};
1197+
1198+
this.listeners.push({ name: signal, listener });
1199+
1200+
process.on(signal, listener);
11961201
});
11971202
}
11981203

@@ -1712,22 +1717,25 @@ class Server {
17121717
);
17131718
}
17141719

1715-
runBonjour() {
1716-
const bonjour = require("bonjour")();
1720+
stopBonjour(callback = () => {}) {
1721+
this.bonjour.unpublishAll(() => {
1722+
this.bonjour.destroy();
1723+
1724+
if (callback) {
1725+
callback();
1726+
}
1727+
});
1728+
}
17171729

1718-
bonjour.publish({
1730+
runBonjour() {
1731+
this.bonjour = require("bonjour")();
1732+
this.bonjour.publish({
17191733
name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
17201734
port: this.options.port,
17211735
type: this.options.server.type === "http" ? "http" : "https",
17221736
subtypes: ["webpack"],
17231737
...this.options.bonjour,
17241738
});
1725-
1726-
process.on("exit", () => {
1727-
bonjour.unpublishAll(() => {
1728-
bonjour.destroy();
1729-
});
1730-
});
17311739
}
17321740

17331741
logStatus() {
@@ -2198,13 +2206,21 @@ class Server {
21982206
}
21992207
}
22002208

2201-
startCallback(callback) {
2209+
startCallback(callback = () => {}) {
22022210
this.start()
22032211
.then(() => callback(null), callback)
22042212
.catch(callback);
22052213
}
22062214

22072215
async stop() {
2216+
if (this.bonjour) {
2217+
await new Promise((resolve) => {
2218+
this.stopBonjour(() => {
2219+
resolve();
2220+
});
2221+
});
2222+
}
2223+
22082224
this.webSocketProxies = [];
22092225

22102226
await Promise.all(this.staticWatchers.map((watcher) => watcher.close()));
@@ -2258,9 +2274,15 @@ class Server {
22582274
this.middleware = null;
22592275
}
22602276
}
2277+
2278+
// We add listeners to signals when creating a new Server instance
2279+
// So ensure they are removed to prevent EventEmitter memory leak warnings
2280+
for (const item of this.listeners) {
2281+
process.removeListener(item.name, item.listener);
2282+
}
22612283
}
22622284

2263-
stopCallback(callback) {
2285+
stopCallback(callback = () => {}) {
22642286
this.stop()
22652287
.then(() => callback(null), callback)
22662288
.catch(callback);

test/e2e/bonjour.test.js

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,17 @@ const runBrowser = require("../helpers/run-browser");
88
const port = require("../ports-map").bonjour;
99

1010
describe("bonjour option", () => {
11-
const mockPublish = jest.fn();
12-
const mockUnpublishAll = jest.fn();
11+
let mockPublish;
12+
let mockUnpublishAll;
13+
let mockDestroy;
14+
15+
beforeEach(() => {
16+
mockPublish = jest.fn();
17+
mockUnpublishAll = jest.fn((callback) => {
18+
callback();
19+
});
20+
mockDestroy = jest.fn();
21+
});
1322

1423
describe("as true", () => {
1524
let compiler;
@@ -24,6 +33,7 @@ describe("bonjour option", () => {
2433
return {
2534
publish: mockPublish,
2635
unpublishAll: mockUnpublishAll,
36+
destroy: mockDestroy,
2737
};
2838
});
2939

@@ -42,8 +52,10 @@ describe("bonjour option", () => {
4252
afterEach(async () => {
4353
await browser.close();
4454
await server.stop();
55+
4556
mockPublish.mockReset();
4657
mockUnpublishAll.mockReset();
58+
mockDestroy.mockReset();
4759
});
4860

4961
it("should call bonjour with correct params", async () => {
@@ -69,6 +81,7 @@ describe("bonjour option", () => {
6981
});
7082

7183
expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
84+
expect(mockDestroy).toHaveBeenCalledTimes(0);
7285

7386
expect(response.status()).toMatchSnapshot("response status");
7487

@@ -93,6 +106,7 @@ describe("bonjour option", () => {
93106
return {
94107
publish: mockPublish,
95108
unpublishAll: mockUnpublishAll,
109+
destroy: mockDestroy,
96110
};
97111
});
98112

@@ -111,8 +125,10 @@ describe("bonjour option", () => {
111125
afterEach(async () => {
112126
await browser.close();
113127
await server.stop();
128+
114129
mockPublish.mockReset();
115130
mockUnpublishAll.mockReset();
131+
mockDestroy.mockReset();
116132
});
117133

118134
it("should call bonjour with 'https' type", async () => {
@@ -138,6 +154,7 @@ describe("bonjour option", () => {
138154
});
139155

140156
expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
157+
expect(mockDestroy).toHaveBeenCalledTimes(0);
141158

142159
expect(response.status()).toMatchSnapshot("response status");
143160

@@ -162,6 +179,7 @@ describe("bonjour option", () => {
162179
return {
163180
publish: mockPublish,
164181
unpublishAll: mockUnpublishAll,
182+
destroy: mockDestroy,
165183
};
166184
});
167185

@@ -180,8 +198,10 @@ describe("bonjour option", () => {
180198
afterEach(async () => {
181199
await browser.close();
182200
await server.stop();
201+
183202
mockPublish.mockReset();
184203
mockUnpublishAll.mockReset();
204+
mockDestroy.mockReset();
185205
});
186206

187207
it("should call bonjour with 'https' type", async () => {
@@ -207,6 +227,7 @@ describe("bonjour option", () => {
207227
});
208228

209229
expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
230+
expect(mockDestroy).toHaveBeenCalledTimes(0);
210231

211232
expect(response.status()).toMatchSnapshot("response status");
212233

@@ -231,6 +252,7 @@ describe("bonjour option", () => {
231252
return {
232253
publish: mockPublish,
233254
unpublishAll: mockUnpublishAll,
255+
destroy: mockDestroy,
234256
};
235257
});
236258

@@ -258,8 +280,10 @@ describe("bonjour option", () => {
258280
afterEach(async () => {
259281
await browser.close();
260282
await server.stop();
283+
261284
mockPublish.mockReset();
262285
mockUnpublishAll.mockReset();
286+
mockDestroy.mockReset();
263287
});
264288

265289
it("should apply bonjour options", async () => {
@@ -286,6 +310,7 @@ describe("bonjour option", () => {
286310
});
287311

288312
expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
313+
expect(mockDestroy).toHaveBeenCalledTimes(0);
289314

290315
expect(response.status()).toMatchSnapshot("response status");
291316

@@ -310,6 +335,7 @@ describe("bonjour option", () => {
310335
return {
311336
publish: mockPublish,
312337
unpublishAll: mockUnpublishAll,
338+
destroy: mockDestroy,
313339
};
314340
});
315341

@@ -338,8 +364,10 @@ describe("bonjour option", () => {
338364
afterEach(async () => {
339365
await browser.close();
340366
await server.stop();
367+
341368
mockPublish.mockReset();
342369
mockUnpublishAll.mockReset();
370+
mockDestroy.mockReset();
343371
});
344372

345373
it("should apply bonjour options", async () => {
@@ -366,6 +394,7 @@ describe("bonjour option", () => {
366394
});
367395

368396
expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
397+
expect(mockDestroy).toHaveBeenCalledTimes(0);
369398

370399
expect(response.status()).toMatchSnapshot("response status");
371400

@@ -390,6 +419,7 @@ describe("bonjour option", () => {
390419
return {
391420
publish: mockPublish,
392421
unpublishAll: mockUnpublishAll,
422+
destroy: mockDestroy,
393423
};
394424
});
395425

@@ -402,7 +432,9 @@ describe("bonjour option", () => {
402432
type: "http",
403433
protocol: "udp",
404434
},
405-
server: "https",
435+
server: {
436+
type: "https",
437+
},
406438
},
407439
compiler
408440
);
@@ -418,6 +450,7 @@ describe("bonjour option", () => {
418450
afterEach(async () => {
419451
await browser.close();
420452
await server.stop();
453+
421454
mockPublish.mockReset();
422455
mockUnpublishAll.mockReset();
423456
});
@@ -446,6 +479,7 @@ describe("bonjour option", () => {
446479
});
447480

448481
expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
482+
expect(mockDestroy).toHaveBeenCalledTimes(0);
449483

450484
expect(response.status()).toMatchSnapshot("response status");
451485

0 commit comments

Comments
 (0)