Skip to content

Commit

Permalink
fix: handle case with no available worker
Browse files Browse the repository at this point in the history
If there are no available workers, the HTTP server on the primary
process will now return a HTTP 503 error.

Related: #10
  • Loading branch information
darrachequesne committed Feb 24, 2023
1 parent f59a376 commit e8b4203
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 0 deletions.
19 changes: 19 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const { randomBytes } = require("crypto");

const randomId = () => randomBytes(8).toString("hex");

function destroySocket() {
this.destroy();
}

const setupMaster = (httpServer, opts) => {
if (!cluster.isMaster) {
throw new Error("not master");
Expand Down Expand Up @@ -69,6 +73,21 @@ const setupMaster = (httpServer, opts) => {
};

socket.on("data", (buffer) => {
if (Object.keys(cluster.workers).length === 0) {
if (workerId) {
// the socket was already assigned to a worker, so we destroy it directly
socket.destroy();
} else {
socket.on("error", destroySocket);
socket.once("finish", destroySocket);

socket.end(`HTTP/1.1 503 Service Unavailable\r\n
Connection: 'close'\r\n
Content-Length: 0\r\n
\r\n`);
}
return;
}
const data = buffer.toString();
if (workerId && connectionId) {
cluster.workers[workerId].send(
Expand Down
59 changes: 59 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
const { join } = require("path");
const { exec } = require("child_process");
const { createServer } = require("http");
const { setupMaster } = require("../index");
const ioc = require("socket.io-client");

const fixture = (filename) => {
return (
'"' + process.execPath + '" "' + join(__dirname, "fixtures", filename) + '"'
);
};

function waitFor(emitter, event) {
return new Promise((resolve) => {
emitter.once(event, resolve);
});
}

function success(done, httpServer, socket) {
httpServer.close();
socket.disconnect();
done();
}

describe("@socket.io/sticky", () => {
it("should work with least-connection load-balancing", (done) => {
exec(fixture("connection.js"), done);
Expand All @@ -27,4 +42,48 @@ describe("@socket.io/sticky", () => {
it("should work with HTTP long-polling only", (done) => {
exec(fixture("connection.js"), { env: { TRANSPORT: "polling" } }, done);
});

it("should work with HTTP long-polling only", (done) => {
exec(fixture("connection.js"), { env: { TRANSPORT: "polling" } }, done);
});

it("should return a 503 error when no worker is available (polling)", (done) => {
const httpServer = createServer();

setupMaster(httpServer, {
loadBalancingMethod: "least-connection",
});

httpServer.listen(async () => {
const { port } = httpServer.address();

const socket = ioc(`http://localhost:${port}`, {
transports: ["polling"],
});

await waitFor(socket, "connect_error");

success(done, httpServer, socket);
});
});

it("should return a 503 error when no worker is available (websocket)", (done) => {
const httpServer = createServer();

setupMaster(httpServer, {
loadBalancingMethod: "least-connection",
});

httpServer.listen(async () => {
const { port } = httpServer.address();

const socket = ioc(`http://localhost:${port}`, {
transports: ["websocket"],
});

await waitFor(socket, "connect_error");

success(done, httpServer, socket);
});
});
});

0 comments on commit e8b4203

Please sign in to comment.