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 a keepAlive option and enable SO_KEEPALIVE by default #24

Merged
merged 3 commits into from
Oct 20, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/memcache-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ const options = {
lifetime: 100, // TTL 100 seconds
cmdTimeout: 3000, // command timeout in milliseconds
connectTimeout: 8000, // connect to server timeout in ms
keepAlive: 120000, // keepalive initial delay in ms, or `false` to disable
noDelay: true, // whether to enable TCP_NODELAY on connections
compressor: require("custom-compressor"),
logger: require("./custom-logger"),
Expand All @@ -191,6 +192,7 @@ const client = new MemcacheClient(options);
- If a command didn't receive response before this timeout value, then it will cause the connection to shutdown and returns Error.
- `connectTimeout` - **_optional_** Custom self connect to server timeout in milliseconds. It's disabled if set to 0. DEFAULT: 0
- The error object from this will have `connecting` set to `true`
- `keepAlive` - **_optional_** Initial delay (in milliseconds) between the last data packet received on a connection and when a keepalive probe should be sent, or `false` to disable the `SO_KEEPALIVE` socket option entirely. DEFAULT: 1 minute (60000 milliseconds)
- `keepDangleSocket` - **_optional_** After `connectTimeout` trigger, do not destroy the socket but keep listening for errors on it. DEFAULT: false
- `dangleSocketWaitTimeout` - **_optional_** How long to wait for errors on dangle socket before destroying it. DEFAULT: 5 minutes (30000 milliseconds)
- `compressor` - **_optional_** a custom compressor for compressing the data. See [data compression](#data-compression) for more details.
Expand Down
10 changes: 10 additions & 0 deletions packages/memcache-client/src/lib/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,16 @@ export class MemcacheConnection extends MemcacheParser {
}

_setupConnection(socket: Socket): void {
const keepAlive = this.client?.options?.keepAlive;

if (keepAlive !== false) {
const initialDelay = typeof keepAlive === "number" && Number.isFinite(keepAlive)
? keepAlive
: 60000;

socket.setKeepAlive(true, initialDelay);
}

if (this.client?.options?.noDelay) {
socket.setNoDelay(true);
}
Expand Down
58 changes: 52 additions & 6 deletions packages/memcache-client/src/test/spec/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,35 +279,81 @@ describe("memcache client", function () {
});
});

it("should not enable TCP_NODELAY by default", async () => {
await startSingleServer();
it("should enable SO_KEEPALIVE with an initial delay of 60000 ms by default", async () => {
const _setKeepAlive = Socket.prototype.setKeepAlive;
const mockKeepAlive = jest.fn();
const x = new MemcacheClient({ server });

try {
Socket.prototype.setKeepAlive = mockKeepAlive;
await x.set("foo", "bar");
} finally {
Socket.prototype.setKeepAlive = _setKeepAlive;
x.shutdown();
}

expect(mockKeepAlive).toHaveBeenCalledWith(true, 60000);
});

it("should enable SO_KEEPALIVE with a custom initial delay when the keepAlive client option is a number", async () => {
const _setKeepAlive = Socket.prototype.setKeepAlive;
const mockKeepAlive = jest.fn();
const x = new MemcacheClient({ server, keepAlive: 10000 });

try {
Socket.prototype.setKeepAlive = mockKeepAlive;
await x.set("foo", "bar");
} finally {
Socket.prototype.setKeepAlive = _setKeepAlive;
x.shutdown();
}

expect(mockKeepAlive).toHaveBeenCalledWith(true, 10000);
});

it("should not enable SO_KEEPALIVE when the keepAlive client option is `false`", async () => {
const _setKeepAlive = Socket.prototype.setKeepAlive;
const mockKeepAlive = jest.fn();
const x = new MemcacheClient({ server, keepAlive: false });

try {
Socket.prototype.setKeepAlive = mockKeepAlive;
await x.set("foo", "bar");
} finally {
Socket.prototype.setKeepAlive = _setKeepAlive;
x.shutdown();
}

expect(mockKeepAlive).not.toHaveBeenCalled();
});

it("should not enable TCP_NODELAY by default", async () => {
const _setNoDelay = Socket.prototype.setNoDelay;
const mockNoDelay = jest.fn();
const x = new MemcacheClient({ server });

try {
Socket.prototype.setNoDelay = mockNoDelay;
const x = new MemcacheClient({ server: server });
await x.set("foo", "bar");
} finally {
Socket.prototype.setNoDelay = _setNoDelay;
x.shutdown();
}

expect(mockNoDelay).not.toHaveBeenCalled();
});

it("should enable TCP_NODELAY when options.noDelay is true", async () => {
await startSingleServer();

const _setNoDelay = Socket.prototype.setNoDelay;
const mockNoDelay = jest.fn();
const x = new MemcacheClient({ server, noDelay: true });

try {
Socket.prototype.setNoDelay = mockNoDelay;
const x = new MemcacheClient({ server: server, noDelay: true });
await x.set("foo", "bar");
} finally {
Socket.prototype.setNoDelay = _setNoDelay;
x.shutdown();
}

expect(mockNoDelay).toHaveBeenCalled();
Expand Down
1 change: 1 addition & 0 deletions packages/memcache-client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export type MemcacheClientOptions = {
noDelay?: boolean;
cmdTimeout?: number;
connectTimeout?: number;
keepAlive?: number | false;
keepDangleSocket?: boolean;
dangleSocketWaitTimeout?: number;
compressor?: CompressorLibrary;
Expand Down