memcached
Version:
A fully featured Memcached API client, supporting both single and clustered Memcached servers through consistent hashing and failover/failure. Memcached is rewrite of nMemcached, which will be deprecated in the near future.
131 lines (103 loc) • 3.83 kB
JavaScript
"use strict";
var EventEmitter = require('events').EventEmitter
, spawn = require('child_process').spawn
, Utils = require('./utils')
, util = require('util');
exports.IssueLog = IssueLog; // connection issue handling
exports.Available = ping; // connection availablity
function ping (host, callback) {
var isWin = process.platform.indexOf('win') === 0; // win32 or win64
var arg = isWin ? '-n' : '-c';
var pong = spawn('ping', [arg, '3', host]); // only ping 3 times
pong.stdout.on('data', function stdoutdata (data) {
callback(false, data.toString().split('\n')[0].substr(14));
pong.kill();
});
pong.stderr.on('data', function stderrdata (data) {
callback(new Error(data.toString().split('\n')[0].substr(14)), false);
pong.kill();
});
}
function IssueLog (args) {
this.config = args;
this.messages = [];
this.failed = false;
this.locked = false;
this.isScheduledToReconnect = false;
this.totalFailures = 0;
this.retry = 0;
this.totalReconnectsAttempted = 0;
this.totalReconnectsSuccess = 0;
Utils.merge(this, args);
EventEmitter.call(this);
}
util.inherits(IssueLog, EventEmitter);
var issues = IssueLog.prototype;
issues.log = function log (message) {
var issue = this;
this.failed = true;
this.messages.push(message || 'No message specified');
// All failures must occur within `failuresTimeout` ms from the initial
// failure in order for node to be disconnected or removed.
if (this.failures && this.failures == this.config.failures)
this.failuresResetId = setTimeout(issue.failuresReset.bind(issue), this.failuresTimeout);
if (this.failures && !this.locked) {
this.locked = true;
setTimeout(issue.attemptRetry.bind(issue), this.retry);
return this.emit('issue', this.details);
}
if (this.failuresResetId) clearTimeout(this.failuresResetId);
if (this.remove) return this.emit('remove', this.details);
if (!this.isScheduledToReconnect) {
this.isScheduledToReconnect = true;
setTimeout(issue.attemptReconnect.bind(issue), this.reconnect);
}
};
issues.failuresReset = function failuresReset() {
//this.failures = this.config.failures;
Utils.merge(this, JSON.parse(JSON.stringify(this.config)));
};
Object.defineProperty(issues, 'details', {
get: function getDetails () {
var res = {};
res.server = this.server;
res.tokens = this.tokens;
res.messages = this.messages;
if (this.failures) {
res.failures = this.failures;
res.totalFailures = this.totalFailures;
} else {
res.totalReconnectsAttempted = this.totalReconnectsAttempted;
res.totalReconnectsSuccess = this.totalReconnectsSuccess;
res.totalReconnectsFailed = this.totalReconnectsAttempted - this.totalReconnectsSuccess;
res.totalDownTime = (res.totalReconnectsFailed * this.reconnect) + (this.totalFailures * this.retry);
}
return res;
}
});
issues.attemptRetry = function attemptRetry () {
this.totalFailures++;
this.failures--;
this.failed = false;
this.locked = false;
};
issues.attemptReconnect = function attemptReconnect () {
var issue = this;
this.totalReconnectsAttempted++;
this.emit('reconnecting', this.details);
// Ping the server
ping(this.tokens[1], function pingpong (err) {
// still no access to the server
if (err) {
issue.messages.push(err.message || 'No message specified');
return setTimeout(issue.attemptReconnect.bind(issue), issue.reconnect);
}
issue.emit('reconnected', issue.details);
issue.totalReconnectsSuccess++;
issue.messages.length = 0;
issue.failed = false;
issue.isScheduledToReconnect = false;
// we connected again, so we are going through the whole cycle again
Utils.merge(issue, JSON.parse(JSON.stringify(issue.config)));
});
};