@omneedia/socketcluster
Version:
SocketCluster - A Highly parallelized WebSocket server cluster to make the most of multi-core machines/instances.
204 lines (176 loc) • 5.18 kB
JavaScript
var async = require('async');
var EventEmitter = require('events').EventEmitter;
var ClientCluster = function (clients) {
var self = this;
var handleMessage = function () {
var args = Array.prototype.slice.call(arguments);
self.emit.apply(self, ['message'].concat(args));
};
clients.forEach(function (client) {
client.on('message', handleMessage);
});
var i, method;
var client = clients[0];
var clientIds = [];
var clientInterface = [
'subscribe',
'isSubscribed',
'unsubscribe',
'publish',
'set',
'getExpiry',
'add',
'concat',
'get',
'getRange',
'getAll',
'count',
'registerDeathQuery',
'exec',
'query',
'remove',
'removeRange',
'removeAll',
'splice',
'pop',
'hasKey',
'send',
'end'
];
var clientUtils = [
'extractKeys',
'extractValues'
];
for (var i in clients) {
if (clients.hasOwnProperty(i)) {
var client = clients[i];
client.on('error', function (error) {
self.emit('error', error);
});
client.on('warning', function (warning) {
self.emit('warning', warning);
});
client.id = i;
clientIds.push(i);
}
}
// Default mapper maps to all clients.
var mapper = function () {
return clientIds;
};
clientInterface.forEach(function (method) {
self[method] = function () {
var key = arguments[0];
var lastArg = arguments[arguments.length - 1];
var results = [];
var mapOutput = self.detailedMap(key, method);
var activeClients = mapOutput.targets;
if (lastArg instanceof Function) {
if (mapOutput.type === 'single') {
activeClients[0][method].apply(activeClients[0], arguments);
} else {
var result;
var tasks = [];
var args = Array.prototype.slice.call(arguments, 0, -1);
var cb = lastArg;
var len = activeClients.length;
for (var i = 0; i < len; i++) {
(function (activeClient) {
tasks.push(function () {
var callback = arguments[arguments.length - 1];
result = activeClient[method].apply(activeClient, args.concat(callback));
results.push(result);
});
})(activeClients[i]);
}
async.parallel(tasks, cb);
}
} else {
var len = activeClients.length;
for (var i = 0; i < len; i++) {
result = activeClients[i][method].apply(activeClients[i], arguments);
results.push(result);
}
}
return results;
}
});
var multiKeyClientInterface = [
'expire',
'unexpire'
];
multiKeyClientInterface.forEach(function (method) {
self[method] = function () {
var activeClients, activeClientsLen, mapping, key;
var keys = arguments[0];
var tasks = [];
var results = [];
var expiryMap = {};
var cb = arguments[arguments.length - 1];
var len = keys.length;
for (var j = 0; j < len; j++) {
key = keys[j];
activeClients = self.map(key, method);
activeClientsLen = activeClients.length;
for (var k = 0; k < activeClientsLen; k++) {
mapping = activeClients[k].id;
if (expiryMap[mapping] == null) {
expiryMap[mapping] = [];
}
expiryMap[mapping].push(key);
}
}
var partArgs = Array.prototype.slice.call(arguments, 1, -1);
for (mapping in expiryMap) {
if (expiryMap.hasOwnProperty(mapping)) {
(function (activeClient, expiryKeys) {
var newArgs = [expiryKeys].concat(partArgs);
tasks.push(function () {
var callback = arguments[arguments.length - 1];
var result = activeClient[method].apply(activeClient, newArgs.concat(callback));
results.push(result);
});
})(clients[mapping], expiryMap[mapping]);
}
}
async.parallel(tasks, cb);
return results;
};
});
clientUtils.forEach(function (method) {
this[method] = client[method].bind(client);
});
this.setMapper = function (mapperFunction) {
mapper = mapperFunction;
};
this.getMapper = function (mapperFunction) {
return mapper;
};
this.detailedMap = function (key, method) {
var result = mapper(key, method, clientIds);
var targets, type;
if (typeof result === 'number') {
type = 'single';
targets = [clients[result % clients.length]];
} else {
type = 'multi';
if (result instanceof Array) {
var dataClients = [];
for (var i in result) {
if (result.hasOwnProperty(i)) {
dataClients.push(clients[result[i] % clients.length]);
}
}
targets = dataClients;
} else {
targets = [];
}
}
return {type: type, targets: targets};
};
this.map = function (key, method) {
return self.detailedMap(key, method).targets;
};
};
ClientCluster.prototype = Object.create(EventEmitter.prototype);
module.exports.ClientCluster = ClientCluster;