UNPKG

@joshbetz/memcached

Version:

Memcached client for modern Node JS

225 lines (224 loc) 6.99 kB
"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;