memcache-client
Version:
NodeJS memcached client
319 lines • 12.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemcacheConnection = exports.Status = void 0;
const tslib_1 = require("tslib");
const net_1 = tslib_1.__importDefault(require("net"));
const tls_1 = tslib_1.__importDefault(require("tls"));
const assert_1 = tslib_1.__importDefault(require("assert"));
const optional_require_1 = require("optional-require");
const Promise = (0, optional_require_1.optionalRequire)("bluebird", {
default: global.Promise,
});
const memcache_parser_1 = require("memcache-parser");
const cmd_actions_1 = tslib_1.__importDefault(require("./cmd-actions"));
const defaults_1 = tslib_1.__importDefault(require("./defaults"));
/* eslint-disable no-bitwise,no-magic-numbers,max-params,no-unused-vars */
/* eslint-disable no-console,camelcase,max-statements,no-var */
exports.Status = {
INIT: 1,
CONNECTING: 2,
READY: 3,
SHUTDOWN: 4,
};
const StatusStr = {
[exports.Status.INIT]: "INIT",
[exports.Status.CONNECTING]: "CONNECTING",
[exports.Status.READY]: "READY",
[exports.Status.SHUTDOWN]: "SHUTDOWN",
};
class MemcacheConnection extends memcache_parser_1.MemcacheParser {
// TODO: still don't know which type client is
constructor(client, node) {
super(client._logger);
this._reset = false;
this.client = client;
this.node = node;
this.socket = undefined;
this._cmdQueue = [];
this._connectPromise = undefined;
this._id = client.socketID++;
this._checkCmdTimer = undefined;
this._cmdTimeout = (client.options && client.options.cmdTimeout) || defaults_1.default.CMD_TIMEOUT_MS;
(0, assert_1.default)(this._cmdTimeout > 0, "cmdTimeout must be > 0");
this._cmdCheckInterval = Math.min(250, Math.ceil(this._cmdTimeout / 4));
this._cmdCheckInterval = Math.max(50, this._cmdCheckInterval);
this._status = exports.Status.INIT;
}
waitDangleSocket(socket) {
if (!socket)
return;
const client = this.client;
client === null || client === void 0 ? void 0 : client.emit("dangle-wait", { type: "wait", socket });
const dangleWaitTimeout = setTimeout(() => {
socket.removeAllListeners("error");
socket.removeAllListeners("connect");
socket.destroy();
client === null || client === void 0 ? void 0 : client.emit("dangle-wait", { type: "timeout" });
}, (client === null || client === void 0 ? void 0 : client.options.dangleSocketWaitTimeout) || defaults_1.default.DANGLE_SOCKET_WAIT_TIMEOUT);
socket.once("error", (err) => {
clearTimeout(dangleWaitTimeout);
socket.destroy();
client === null || client === void 0 ? void 0 : client.emit("dangle-wait", { type: "error", err });
});
}
connect(server) {
var _a, _b;
const serverStringArray = server.split(":");
const host = serverStringArray[0];
const port = +serverStringArray[1];
(0, assert_1.default)(host, "Must provide server hostname");
(0, assert_1.default)(typeof port === "number" && port > 0, "Must provide valid server port");
let socket;
if (((_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.tls) !== undefined) {
// Create a TLS connection
socket = tls_1.default.connect({
host: host,
port: port,
...this.client.options.tls
});
}
else {
// Create a regular TCP connection
socket = net_1.default.createConnection({ host, port });
}
this._connectPromise = new Promise((resolve, reject) => {
this._status = exports.Status.CONNECTING;
const selfTimeout = () => {
var _a, _b, _c, _d;
if (!(((_c = (_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.connectTimeout) !== null && _c !== void 0 ? _c : 0) > 0))
return undefined;
return setTimeout(() => {
var _a, _b;
socket.removeAllListeners("error");
socket.removeAllListeners("connect");
(_a = this.client) === null || _a === void 0 ? void 0 : _a.emit("timeout", {
socket,
keepDangleSocket: this.client.options.keepDangleSocket,
});
if ((_b = this.client) === null || _b === void 0 ? void 0 : _b.options.keepDangleSocket) {
this.waitDangleSocket(socket);
this._shutdown("connect timeout", true);
}
else {
this._shutdown("connect timeout");
}
const err = new Error("connect timeout");
err.connecting = true;
reject(err);
}, (_d = this.client) === null || _d === void 0 ? void 0 : _d.options.connectTimeout);
};
const connTimeout = selfTimeout();
socket.once("error", (err) => {
this._shutdown("connect failed");
if (connTimeout) {
clearTimeout(connTimeout);
}
err.connecting = true;
reject(err);
});
socket.once("connect", () => {
this.socket = socket;
this._status = exports.Status.READY;
this._connectPromise = undefined;
socket.removeAllListeners("error");
this._setupConnection(socket);
if (connTimeout) {
clearTimeout(connTimeout);
}
resolve(this);
});
this.socket = socket;
});
return this._connectPromise;
}
isReady() {
return this._status === exports.Status.READY;
}
isConnecting() {
return this._status === exports.Status.CONNECTING;
}
isShutdown() {
return this._status === exports.Status.SHUTDOWN;
}
getStatusStr() {
return StatusStr[this._status] || "UNKNOWN";
}
waitReady() {
if (this.isConnecting()) {
(0, assert_1.default)(this._connectPromise, "MemcacheConnection not pending connect");
return this._connectPromise;
}
else if (this.isReady()) {
return Promise.resolve(this);
}
else {
throw new Error(`MemcacheConnection can't waitReady for status ${this.getStatusStr()}`);
}
}
queueCommand(context) {
context.queuedTime = Date.now();
this._startCmdTimeout();
this._cmdQueue.unshift(context);
}
dequeueCommand() {
if (this.isShutdown()) {
return { callback: () => undefined };
}
return this._cmdQueue.pop();
}
peekCommand() {
return this._cmdQueue[this._cmdQueue.length - 1];
}
processCmd(cmdTokens) {
const action = cmd_actions_1.default[cmdTokens[0]];
return this[`cmdAction_${action}`](cmdTokens);
}
receiveResult(pending) {
var _a;
if (this.isReady()) {
const retrieve = this.peekCommand();
try {
retrieve.results[pending.cmdTokens[1]] = {
tokens: pending.cmdTokens,
casUniq: pending.cmdTokens[4],
value: (_a = this.client) === null || _a === void 0 ? void 0 : _a._unpackValue(pending),
};
}
catch (err) {
retrieve.error = err;
}
}
delete pending.data;
}
shutdown() {
this._shutdown("Shutdown requested");
}
//
// Internal methods
//
cmdAction_OK(cmdTokens) {
var _a;
(_a = this.dequeueCommand()) === null || _a === void 0 ? void 0 : _a.callback(null, cmdTokens);
}
cmdAction_ERROR(cmdTokens) {
var _a;
const msg = (m) => (m ? ` ${m}` : "");
const error = new Error(`${cmdTokens[0]}${msg(cmdTokens.slice(1).join(" "))}`);
error.cmdTokens = cmdTokens;
(_a = this.dequeueCommand()) === null || _a === void 0 ? void 0 : _a.callback(error);
}
cmdAction_RESULT(cmdTokens) {
if (this.isReady() && cmdTokens) {
const retrieve = this.peekCommand();
const cmd = cmdTokens[0];
const results = retrieve.results;
if (!results[cmd]) {
results[cmd] = [];
}
results[cmd].push(cmdTokens.slice(1));
}
}
cmdAction_SINGLE_RESULT(cmdTokens) {
this.cmdAction_OK(cmdTokens);
}
cmdAction_SELF(cmdTokens) {
this[`cmd_${cmdTokens[0]}`](cmdTokens);
}
cmdAction_undefined(cmdTokens) {
var _a;
// incr/decr response
// - <value>\r\n , where <value> is the new value of the item's data,
// after the increment/decrement operation was carried out.
if (cmdTokens.length === 1 && cmdTokens[0].match(/[+-]?[0-9]+/)) {
(_a = this.dequeueCommand()) === null || _a === void 0 ? void 0 : _a.callback(null, cmdTokens[0]);
return true;
}
else {
console.log("No command action defined for", cmdTokens);
}
return false;
}
cmd_VALUE(cmdTokens) {
this.initiatePending(cmdTokens, +cmdTokens[3]);
}
// eslint-disable-next-line
cmd_END(cmdTokens) {
var _a;
(_a = this.dequeueCommand()) === null || _a === void 0 ? void 0 : _a.callback();
}
_shutdown(msg, keepSocket) {
var _a;
if (this.isShutdown()) {
return;
}
delete this._connectPromise;
let cmd;
while ((cmd = this.dequeueCommand())) {
cmd.callback(new Error(msg));
}
this._status = exports.Status.SHUTDOWN;
// reset connection
(_a = this.node) === null || _a === void 0 ? void 0 : _a.endConnection(this);
if (this.socket) {
this.socket.end();
if (!keepSocket)
this.socket.destroy();
this.socket.unref();
}
delete this.socket;
delete this.client;
delete this.node;
}
_checkCmdTimeout() {
this._checkCmdTimer = undefined;
if (this._cmdQueue.length > 0) {
const cmd = this.peekCommand();
const now = Date.now();
if (now - cmd.queuedTime > this._cmdTimeout) {
this._shutdown("Command timeout");
}
else {
this._startCmdTimeout();
}
}
}
_startCmdTimeout() {
if (!this._checkCmdTimer) {
this._checkCmdTimer = setTimeout(this._checkCmdTimeout.bind(this), this._cmdCheckInterval);
}
}
_setupConnection(socket) {
var _a, _b, _c, _d;
const keepAlive = (_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.keepAlive;
if (keepAlive !== false) {
const initialDelay = typeof keepAlive === "number" && Number.isFinite(keepAlive)
? keepAlive
: 60000;
socket.setKeepAlive(true, initialDelay);
}
if ((_d = (_c = this.client) === null || _c === void 0 ? void 0 : _c.options) === null || _d === void 0 ? void 0 : _d.noDelay) {
socket.setNoDelay(true);
}
socket.on("data", this.onData.bind(this));
socket.on("end", () => {
this._shutdown("socket end");
});
socket.on("error", (err) => {
this._shutdown(`socket error ${err.message}`);
});
socket.on("close", () => {
this._shutdown("socket close");
});
socket.on("timeout", () => {
this._shutdown("socket timeout");
});
}
}
exports.MemcacheConnection = MemcacheConnection;
MemcacheConnection.Status = exports.Status;
//# sourceMappingURL=connection.js.map
;