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

Add cleanupEmptyChildNamespaces server option #4602

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ interface ServerOptions extends EngineOptions, AttachOptions {
*/
skipMiddlewares?: boolean;
};
/**
* whether or not to remove child namespaces that have no sockets connected to them
* @default false
*/
cleanupEmptyChildNamespaces: boolean;
}

/**
Expand Down Expand Up @@ -153,6 +158,7 @@ export class Server<
*
*/
public engine: BaseServer;
public readonly cleanupEmptyChildNamespaces: boolean;

/** @private */
readonly _parser: typeof parser;
Expand Down Expand Up @@ -226,6 +232,7 @@ export class Server<
this.path(opts.path || "/socket.io");
this.connectTimeout(opts.connectTimeout || 45000);
this.serveClient(false !== opts.serveClient);
this.cleanupEmptyChildNamespaces = !!opts.cleanupEmptyChildNamespaces;
this._parser = opts.parser || parser;
this.encoder = new this._parser.Encoder();
this.opts = opts;
Expand Down
23 changes: 22 additions & 1 deletion lib/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,24 @@ export class Namespace<
/** @private */
_ids: number = 0;

private cleanupEmptyNamespaceFunc?: (nsp: Namespace) => void;

/**
* Namespace constructor.
*
* @param server instance
* @param name
* @param cleanupEmptyNamespaceFunc (optional) function to run if the namespace is empty
*/
constructor(
server: Server<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
name: string
name: string,
cleanupEmptyNamespaceFunc?: (nsp: Namespace) => void
) {
super();
this.server = server;
this.name = name;
this.cleanupEmptyNamespaceFunc = cleanupEmptyNamespaceFunc;
this._initAdapter();
}

Expand Down Expand Up @@ -694,4 +699,20 @@ export class Namespace<
this.adapter
).disconnectSockets(close);
}

/**
* Cleans up the namespace if necessary (if the server option cleanupEmptyChildNamespaces is true and there are no sockets connected to the namespace).
*
*/
public cleanupEmptyNamespace() {
if (
!this.server.cleanupEmptyChildNamespaces ||
this.sockets.size !== 0 ||
typeof this.cleanupEmptyNamespaceFunc !== "function"
) {
return;
}

this.cleanupEmptyNamespaceFunc(this);
}
}
6 changes: 5 additions & 1 deletion lib/parent-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export class ParentNamespace<
createChild(
name: string
): Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
const namespace = new Namespace(this.server, name);
const namespace = new Namespace(this.server, name, (nsp: Namespace) => {
nsp.adapter.close();
this.server._nsps.delete(nsp.name);
this.children.delete(nsp);
});
namespace._fns = this._fns.slice(0);
this.listeners("connect").forEach((listener) =>
namespace.on("connect", listener)
Expand Down
1 change: 1 addition & 0 deletions lib/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ export class Socket<
this.client._remove(this);
this.connected = false;
this.emitReserved("disconnect", reason);
this.nsp.cleanupEmptyNamespace();
return;
}

Expand Down
88 changes: 88 additions & 0 deletions test/namespaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,34 @@ describe("namespaces", () => {
io.of("/nsp");
});

it("should not clean up parent namespace when cleanupEmptyChildNamespaces is on and there are no more sockets in a namespace", (done) => {
const io = new Server(0, { cleanupEmptyChildNamespaces: true });
const c1 = createClient(io, "/chat");

c1.on("connect", () => {
c1.disconnect();

// Give it some time to disconnect the client
setTimeout(() => {
const socket = createClient(io, "/chat");

socket.on("connect_error", () => {
done(
new Error(
"Client got a connect error when connecting to a parent namespace"
)
);
});

socket.on("connect", () => {
success(done, io, socket);
});
}, 500);
});

const nsp = io.of("/chat");
});

describe("dynamic namespaces", () => {
it("should allow connections to dynamic namespaces with a regex", (done) => {
const io = new Server(0);
Expand Down Expand Up @@ -571,5 +599,65 @@ describe("namespaces", () => {
one.on("message", handler);
two.on("message", handler);
});

it("should clean up namespace when cleanupEmptyChildNamespaces is on and there are no more sockets in a namespace", (done) => {
const io = new Server(0, { cleanupEmptyChildNamespaces: true });
const c1 = createClient(io, "/dynamic-101");

c1.on("connect", () => {
c1.disconnect();

// Give it some time to disconnect and clean up the namespace
setTimeout(() => {
expect(io._nsps.has("/dynamic-101")).to.be(false);
success(done, io);
}, 100);
});

io.of(/^\/dynamic-\d+$/);
});

it("should allow a client to connect to a cleaned up namespace", (done) => {
const io = new Server(0, { cleanupEmptyChildNamespaces: true });
const c1 = createClient(io, "/dynamic-101");

c1.on("connect", () => {
c1.disconnect();

// Give it some time to disconnect and clean up the namespace
setTimeout(() => {
const c2 = createClient(io, "/dynamic-101");

c2.on("connect", () => {
success(done, io, c2);
});

c2.on("connect_error", () => {
done(
new Error("Client got error when connecting to dynamic namespace")
);
});
}, 100);
});

io.of(/^\/dynamic-\d+$/);
});

it("should not clean up namespace when cleanupEmptyChildNamespaces is off and there are no more sockets in a namespace", (done) => {
const io = new Server(0);
const c1 = createClient(io, "/dynamic-101");

c1.on("connect", () => {
c1.disconnect();

// Give it some time to disconnect and clean up the namespace
setTimeout(() => {
expect(io._nsps.has("/dynamic-101")).to.be(true);
success(done, io);
}, 300);
});

io.of(/^\/dynamic-\d+$/);
});
});
});