Skip to content

Commit

Permalink
Merge pull request #24 from rgrove/keepalive
Browse files Browse the repository at this point in the history
Add a `keepAlive` option and enable `SO_KEEPALIVE` by default
  • Loading branch information
smuthya authored Oct 20, 2023
2 parents 6bc63db + 1e683c2 commit 3f7f8db
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 6 deletions.
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

0 comments on commit 3f7f8db

Please sign in to comment.