@joshbetz/memcached
Version:
Memcached client for modern Node JS
225 lines (224 loc) • 6.99 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const pool_1 = require("./pool");
const HashRing = require('hashring');
class HashPool extends events_1.EventEmitter {
constructor(nodes, opts) {
super();
this.retries = 0;
this.isReady = false;
this.hashring = new HashRing();
this.nodes = new Map();
this.opts = Object.assign({
retry: (retries) => {
const exp = Math.pow(2, retries) * 250;
// exponential backoff up to 30 seconds
return Math.min(exp, 30000);
},
pingInterval: 60000,
// Pool options
max: 10,
min: 2,
acquireTimeoutMillis: 200,
destroyTimeoutMillis: 200,
maxWaitingClients: 2,
idleTimeoutMillis: 30000,
// Connection options
socketTimeout: 100,
}, opts);
// initialize hash pool
for (const node of nodes) {
this.connect(node);
}
if (this.opts.pingInterval > 0) {
setInterval(() => {
this.ping();
}, this.opts.pingInterval).unref();
}
}
connect(node) {
if (this.nodes.has(node)) {
throw new Error(`Pool already has node ${node}`);
}
const [host, port] = node.split(':');
const pool = new pool_1.default(parseInt(port, 10), host, this.opts);
pool.on('error', () => {
const host = this.nodes.get(node);
if (!host || host.reconnecting) {
return;
}
this.disconnect(node);
});
this.nodes.set(node, {
pool,
reconnecting: false,
});
pool.ready()
.then(() => {
this.hashring.add(node);
this.retries = 0;
this.isReady = true;
this.emit('ready');
})
.catch(() => {
this.disconnect(node);
});
}
reconnect(node) {
setTimeout(() => {
this.connect(node);
}, this.opts.retry(this.retries++));
}
disconnect(node, reconnect = true) {
const host = this.nodes.get(node);
if (!host || host.reconnecting) {
return;
}
host.reconnecting = true;
this.hashring.remove(node);
host.pool.end().then(() => {
this.nodes.delete(node);
if (!this.nodes.size) {
this.isReady = false;
}
if (reconnect) {
this.reconnect(node);
}
});
}
ready() {
return __awaiter(this, void 0, void 0, function* () {
if (this.isReady) {
return true;
}
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('No hosts'));
}, this.opts.socketTimeout).unref();
this.once('ready', () => {
clearTimeout(timeout);
resolve();
});
});
});
}
getHost(key) {
return __awaiter(this, void 0, void 0, function* () {
yield this.ready();
const host = this.hashring.get(key);
const node = this.nodes.get(host);
if (!node) {
throw new Error('Could not find node');
}
return node.pool;
});
}
flush() {
return __awaiter(this, void 0, void 0, function* () {
for (const host of this.nodes.values()) {
yield host.pool.flush();
}
});
}
set(key, value, ttl = 0) {
return __awaiter(this, void 0, void 0, function* () {
let host;
try {
host = yield this.getHost(key);
}
catch (_) {
return false;
}
return host.set(key, value, ttl);
});
}
add(key, value, ttl = 0) {
return __awaiter(this, void 0, void 0, function* () {
let host;
try {
host = yield this.getHost(key);
}
catch (_) {
return false;
}
return host.add(key, value, ttl);
});
}
get(key) {
return __awaiter(this, void 0, void 0, function* () {
let host;
try {
host = yield this.getHost(key);
}
catch (_) {
return false;
}
return host.get(key);
});
}
del(key) {
return __awaiter(this, void 0, void 0, function* () {
let host;
try {
host = yield this.getHost(key);
}
catch (_) {
return false;
}
return host.del(key);
});
}
incr(key, value = 1) {
return __awaiter(this, void 0, void 0, function* () {
let host;
try {
host = yield this.getHost(key);
}
catch (_) {
return false;
}
return host.incr(key, value);
});
}
decr(key, value = 1) {
return __awaiter(this, void 0, void 0, function* () {
let host;
try {
host = yield this.getHost(key);
}
catch (_) {
return false;
}
return host.decr(key, value);
});
}
ping() {
return __awaiter(this, void 0, void 0, function* () {
const pings = [];
for (const host of this.nodes.values()) {
pings.push(host.pool.ping());
}
return (yield Promise.all(pings)).every(ping => ping === true);
});
}
end() {
return __awaiter(this, void 0, void 0, function* () {
this.isReady = false;
for (const [node, host] of this.nodes.entries()) {
yield host.pool.end();
this.nodes.delete(node);
}
});
}
}
exports.default = HashPool;