Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: remove process listeners after stopping the server #4013

Merged
merged 3 commits into from Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 43 additions & 21 deletions lib/Server.js
Expand Up @@ -32,6 +32,7 @@ class Server {

this.options = options;
this.staticWatchers = [];
this.listeners = [];
// Keep track of websocket proxies for external websocket upgrade.
this.webSocketProxies = [];
this.sockets = [];
Expand Down Expand Up @@ -1168,15 +1169,11 @@ class Server {

let needForceShutdown = false;

const exitProcess = () => {
// eslint-disable-next-line no-process-exit
process.exit();
};

signals.forEach((signal) => {
process.on(signal, () => {
const listener = () => {
if (needForceShutdown) {
exitProcess();
// eslint-disable-next-line no-process-exit
process.exit();
}

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

this.stopCallback(() => {
if (typeof this.compiler.close === "function") {
this.compiler.close(exitProcess);
this.compiler.close(() => {
// eslint-disable-next-line no-process-exit
process.exit();
});
} else {
exitProcess();
// eslint-disable-next-line no-process-exit
process.exit();
}
});
});
};

this.listeners.push({ name: signal, listener });

process.on(signal, listener);
});
}

Expand Down Expand Up @@ -1712,22 +1717,25 @@ class Server {
);
}

runBonjour() {
const bonjour = require("bonjour")();
stopBonjour(callback = () => {}) {
this.bonjour.unpublishAll(() => {
this.bonjour.destroy();

if (callback) {
callback();
}
});
}

bonjour.publish({
runBonjour() {
this.bonjour = require("bonjour")();
this.bonjour.publish({
name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
port: this.options.port,
type: this.options.server.type === "http" ? "http" : "https",
subtypes: ["webpack"],
...this.options.bonjour,
});

process.on("exit", () => {
bonjour.unpublishAll(() => {
bonjour.destroy();
});
});
}

logStatus() {
Expand Down Expand Up @@ -2198,13 +2206,21 @@ class Server {
}
}

startCallback(callback) {
startCallback(callback = () => {}) {
this.start()
.then(() => callback(null), callback)
.catch(callback);
}

async stop() {
if (this.bonjour) {
await new Promise((resolve) => {
this.stopBonjour(() => {
resolve();
});
});
}

this.webSocketProxies = [];

await Promise.all(this.staticWatchers.map((watcher) => watcher.close()));
Expand Down Expand Up @@ -2258,9 +2274,15 @@ class Server {
this.middleware = null;
}
}

// We add listeners to signals when creating a new Server instance
// So ensure they are removed to prevent EventEmitter memory leak warnings
for (const item of this.listeners) {
process.removeListener(item.name, item.listener);
}
}

stopCallback(callback) {
stopCallback(callback = () => {}) {
this.stop()
.then(() => callback(null), callback)
.catch(callback);
Expand Down
40 changes: 37 additions & 3 deletions test/e2e/bonjour.test.js
Expand Up @@ -8,8 +8,17 @@ const runBrowser = require("../helpers/run-browser");
const port = require("../ports-map").bonjour;

describe("bonjour option", () => {
const mockPublish = jest.fn();
const mockUnpublishAll = jest.fn();
let mockPublish;
let mockUnpublishAll;
let mockDestroy;

beforeEach(() => {
mockPublish = jest.fn();
mockUnpublishAll = jest.fn((callback) => {
callback();
});
mockDestroy = jest.fn();
});

describe("as true", () => {
let compiler;
Expand All @@ -24,6 +33,7 @@ describe("bonjour option", () => {
return {
publish: mockPublish,
unpublishAll: mockUnpublishAll,
destroy: mockDestroy,
};
});

Expand All @@ -42,8 +52,10 @@ describe("bonjour option", () => {
afterEach(async () => {
await browser.close();
await server.stop();

mockPublish.mockReset();
mockUnpublishAll.mockReset();
mockDestroy.mockReset();
});

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

expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
expect(mockDestroy).toHaveBeenCalledTimes(0);

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

Expand All @@ -93,6 +106,7 @@ describe("bonjour option", () => {
return {
publish: mockPublish,
unpublishAll: mockUnpublishAll,
destroy: mockDestroy,
};
});

Expand All @@ -111,8 +125,10 @@ describe("bonjour option", () => {
afterEach(async () => {
await browser.close();
await server.stop();

mockPublish.mockReset();
mockUnpublishAll.mockReset();
mockDestroy.mockReset();
});

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

expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
expect(mockDestroy).toHaveBeenCalledTimes(0);

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

Expand All @@ -162,6 +179,7 @@ describe("bonjour option", () => {
return {
publish: mockPublish,
unpublishAll: mockUnpublishAll,
destroy: mockDestroy,
};
});

Expand All @@ -180,8 +198,10 @@ describe("bonjour option", () => {
afterEach(async () => {
await browser.close();
await server.stop();

mockPublish.mockReset();
mockUnpublishAll.mockReset();
mockDestroy.mockReset();
});

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

expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
expect(mockDestroy).toHaveBeenCalledTimes(0);

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

Expand All @@ -231,6 +252,7 @@ describe("bonjour option", () => {
return {
publish: mockPublish,
unpublishAll: mockUnpublishAll,
destroy: mockDestroy,
};
});

Expand Down Expand Up @@ -258,8 +280,10 @@ describe("bonjour option", () => {
afterEach(async () => {
await browser.close();
await server.stop();

mockPublish.mockReset();
mockUnpublishAll.mockReset();
mockDestroy.mockReset();
});

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

expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
expect(mockDestroy).toHaveBeenCalledTimes(0);

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

Expand All @@ -310,6 +335,7 @@ describe("bonjour option", () => {
return {
publish: mockPublish,
unpublishAll: mockUnpublishAll,
destroy: mockDestroy,
};
});

Expand Down Expand Up @@ -338,8 +364,10 @@ describe("bonjour option", () => {
afterEach(async () => {
await browser.close();
await server.stop();

mockPublish.mockReset();
mockUnpublishAll.mockReset();
mockDestroy.mockReset();
});

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

expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
expect(mockDestroy).toHaveBeenCalledTimes(0);

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

Expand All @@ -390,6 +419,7 @@ describe("bonjour option", () => {
return {
publish: mockPublish,
unpublishAll: mockUnpublishAll,
destroy: mockDestroy,
};
});

Expand All @@ -402,7 +432,9 @@ describe("bonjour option", () => {
type: "http",
protocol: "udp",
},
server: "https",
server: {
type: "https",
},
},
compiler
);
Expand All @@ -418,6 +450,7 @@ describe("bonjour option", () => {
afterEach(async () => {
await browser.close();
await server.stop();

mockPublish.mockReset();
mockUnpublishAll.mockReset();
});
Expand Down Expand Up @@ -446,6 +479,7 @@ describe("bonjour option", () => {
});

expect(mockUnpublishAll).toHaveBeenCalledTimes(0);
expect(mockDestroy).toHaveBeenCalledTimes(0);

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

Expand Down