Skip to content

Commit

Permalink
Merge pull request #20 from rgrove/tcp-nodelay
Browse files Browse the repository at this point in the history
Add a `noDelay` client option that allows enabling `TCP_NODELAY` to improve latency
  • Loading branch information
smuthya authored Aug 23, 2023
2 parents c8b8621 + 6d90249 commit 6bc63db
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 10 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
noDelay: true, // whether to enable TCP_NODELAY on connections
compressor: require("custom-compressor"),
logger: require("./custom-logger"),
Promise,
Expand All @@ -185,6 +186,7 @@ const client = new MemcacheClient(options);
- `ignoreNotStored` - **_optional_** If set to true, then will not treat `NOT_STORED` reply from any store commands as error. Use this for [Mcrouter AllAsyncRoute] mode.
- `lifetime` - **_optional_** Your cache TTL in **_seconds_** to use for all entries. DEFAULT: 60 seconds.
- `noDelay` - **_optional_** Whether to enable `TCP_NODELAY` on connections to decrease latency. DEFAULT: false
- `cmdTimeout` - **_optional_** Command timeout in milliseconds. DEFAULT: 5000 ms.
- 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
Expand Down
18 changes: 9 additions & 9 deletions packages/memcache-client/src/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,11 @@ export class MemcacheClient extends EventEmitter {
): Promise<Response> {
return this.send(
(socket) => {
socket?.write(data);
if (options && options.noreply) {
socket?.write(" noreply\r\n");
} else {
socket?.write("\r\n");
let line = data;
if (options?.noreply) {
line += " noreply";
}
socket?.write(`${line}\r\n`);
},
options,
callback
Expand Down Expand Up @@ -318,10 +317,11 @@ export class MemcacheClient extends EventEmitter {
(options as Partial<CasCommandOptions>)?.compress === true
);
const bytes = Buffer.byteLength(packed.data);
const msg = `${cmd} ${key} ${packed.flag} ${lifetime} ${bytes}${casUniq}${noreply}\r\n`;
socket?.write(msg);
socket?.write(packed.data);
socket?.write("\r\n");
socket?.write(Buffer.concat([
Buffer.from(`${cmd} ${key} ${packed.flag} ${lifetime} ${bytes}${casUniq}${noreply}\r\n`),
Buffer.isBuffer(packed.data) ? packed.data : Buffer.from(packed.data),
Buffer.from("\r\n"),
]));
};

return this._callbackSend(_data, options, callback) as unknown as Promise<string[]>;
Expand Down
4 changes: 4 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,10 @@ export class MemcacheConnection extends MemcacheParser {
}

_setupConnection(socket: Socket): void {
if (this.client?.options?.noDelay) {
socket.setNoDelay(true);
}

socket.on("data", this.onData.bind(this));

socket.on("end", () => {
Expand Down
36 changes: 35 additions & 1 deletion packages/memcache-client/src/test/spec/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Path from "path";
import { text1, text2, poem1, poem2, poem3, poem4, poem5 } from "../data/text";
import NullLoger from "../../lib/null-logger";
import memcached, { MemcachedServer } from "memcached-njs";
import { AddressInfo } from "net";
import { AddressInfo, Socket } from "net";

describe("memcache client", function () {
process.on("unhandledRejection", (e) => {
Expand Down Expand Up @@ -279,6 +279,40 @@ describe("memcache client", function () {
});
});

it("should not enable TCP_NODELAY by default", async () => {
await startSingleServer();

const _setNoDelay = Socket.prototype.setNoDelay;
const mockNoDelay = jest.fn();

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

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();

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

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

const testMulti = (maxConnections: number = 1, done: () => void) => {
const key1 = "text1维基百科";
const key2 = "blah";
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 @@ -43,6 +43,7 @@ export type MemcacheClientOptions = {
server: ServerDefinition | SingleServerEntry | MultipleServerEntry | string;
ignoreNotStored?: boolean;
lifetime?: number;
noDelay?: boolean;
cmdTimeout?: number;
connectTimeout?: number;
keepDangleSocket?: boolean;
Expand Down

0 comments on commit 6bc63db

Please sign in to comment.