tiny-zmq
Version:
A NodeJS package that provides simple load balanced and broadcast messaging on distributed environments
183 lines (152 loc) • 6.25 kB
JavaScript
const zmq = require('zmq');
const config = require('./config.js');
const procSignals = ['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGTERM'/*, 'SIGUSR2'*/];
// STATE
var brokerURI;
var subscriber;
var initialized = false;
var terminating = false;
var notifySubscriber;
var lastActivity = Date.now();
function init(){
if(initialized) return;
initialized = true;
// Internal health checks
setInterval(checkHealth, config.PING_BASE_INTERVAL + 100);
// Termination handlers
procSignals.forEach(sig => process.on(sig, onTerminate) );
}
function checkHealth() {
if(terminating) return;
else if(!subscriber) return lastActivity = Date.now();
else if((Date.now() - lastActivity) >= config.INACTIVITY_TIMEOUT) {
// Reset clock
lastActivity = Date.now();
console.error(`[TinyZMQ] WARNING: No heartbeat from clients for ${config.INACTIVITY_TIMEOUT / 1000} seconds`);
reconnect();
}
}
// MESSAGE HANDLER
function onRequest(notificationBuffer) {
let notificationPayload;
lastActivity = Date.now();
if(notificationBuffer.length == 4 && notificationBuffer.toString() == 'ping') return subscriber.send('pong'); // done
else if(!notifySubscriber) return console.error('ERROR: No worker callback is registered. Ignoring notification.');
try {
notificationPayload = JSON.parse(notificationBuffer.toString());
}
catch(err) {
console.error("[TinyZMQ] Got an invalid notification: ", notificationBuffer.toString());
subscriber.send(JSON.stringify({error: true, message: "Unable to parse the payload"}));
return;
}
// NOTIFY
notifySubscriber(notificationPayload);
// DONE
subscriber.send('');
}
// Lifecycle
function connect(uri, subscriberCallback){
if(terminating) return;
else if(!initialized) {
if(!uri || typeof uri != "string" || !uri.length)
throw new Error('[TinyZMQ] ERROR: expected the broker URI to connect the worker to');
else if(!subscriberCallback || typeof subscriberCallback != 'function')
throw new Error('[TinyZMQ] ERROR: expected a callback function to notify the worker');
brokerURI = uri;
notifySubscriber = subscriberCallback;
}
else { // already on
if(uri == brokerURI) { // no need to connect
if(typeof subscriberCallback == 'function')
notifySubscriber = subscriberCallback;
else
console.log('[TinyZMQ] WARNING: Connection already established to the broker');
return;
}
else if(!uri || typeof uri != "string" || !uri.length)
throw new Error('[TinyZMQ] ERROR: expected the broker URI to connect the worker to');
else if(subscriber) {
console.log('[TinyZMQ] WARNING: Connecting to another broker URI. Closing the previous connection.');
subscriber.close(brokerURI);
brokerURI = uri;
}
if(typeof subscriberCallback == 'function')
notifySubscriber = subscriberCallback;
}
// INITIALIZE
init();
// CONNECT TO THE BROKER
subscriber = zmq.socket('sub');
subscriber.subscribe('');
subscriber.on('message', onRequest);
if(config.DEBUG) {
// Register to monitoring events
subscriber.on('connect', function(fd, ep) {console.log('[TinyZMQ] Connect');});
subscriber.on('connect_delay', function(fd, ep) {console.log('[TinyZMQ] Connect delay');});
subscriber.on('connect_retry', function(fd, ep) {console.log('[TinyZMQ] Connect retry');});
subscriber.on('listen', function(fd, ep) {console.log('[TinyZMQ] Listen');});
subscriber.on('bind_error', function(fd, ep) {console.log('[TinyZMQ] Bind error');});
subscriber.on('accept', function(fd, ep) {console.log('[TinyZMQ] Accept');});
subscriber.on('accept_error', function(fd, ep) {console.log('[TinyZMQ] Accept_error');});
subscriber.on('close', function(fd, ep) {console.log('[TinyZMQ] Close');});
subscriber.on('close_error', function(fd, ep) {console.log('[TinyZMQ] Close_error');});
subscriber.on('disconnect', function(fd, ep) {console.log('[TinyZMQ] WARNING: Disconnected');});
// Handle monitor error
subscriber.on('monitor_error', function(err) {
console.log('[TinyZMQ] WARNING: Error in monitoring: %s, will restart monitoring in .5 seconds', err);
setTimeout(function() { subscriber.monitor(500, 0); }, 500);
});
subscriber.monitor(500, 0);
}
// READY
subscriber.connect(brokerURI);
}
function reconnect(){
if(terminating) return;
console.error("[TinyZMQ] Trying to reconnect...");
subscriber.disconnect(brokerURI);
subscriber.close(brokerURI);
setTimeout(function(){
// CONNECT TO THE BROKER
subscriber = zmq.socket('sub');
subscriber.subscribe('');
subscriber.on('message', onRequest);
if(config.DEBUG) {
// Register to monitoring events
subscriber.on('connect', function(fd, ep) {console.log('[TinyZMQ] Connect');});
subscriber.on('connect_delay', function(fd, ep) {console.log('[TinyZMQ] Connect delay');});
subscriber.on('connect_retry', function(fd, ep) {console.log('[TinyZMQ] Connect retry');});
subscriber.on('listen', function(fd, ep) {console.log('[TinyZMQ] Listen');});
subscriber.on('bind_error', function(fd, ep) {console.log('[TinyZMQ] Bind error');});
subscriber.on('accept', function(fd, ep) {console.log('[TinyZMQ] Accept');});
subscriber.on('accept_error', function(fd, ep) {console.log('[TinyZMQ] Accept_error');});
subscriber.on('close', function(fd, ep) {console.log('[TinyZMQ] Close');});
subscriber.on('close_error', function(fd, ep) {console.log('[TinyZMQ] Close_error');});
subscriber.on('disconnect', function(fd, ep) {console.log('[TinyZMQ] WARNING: Disconnected');});
// Handle monitor error
subscriber.on('monitor_error', function(err) {
console.log('[TinyZMQ] WARNING: Error in monitoring: %s, will restart monitoring in .5 seconds', err);
setTimeout(function() { subscriber.monitor(500, 0); }, 500);
});
subscriber.monitor(500, 0);
}
// READY
subscriber.connect(brokerURI);
}, 50);
}
function onTerminate(code){
console.log("\n[TinyZMQ] The process is terminating", code || '');
terminating = true;
if(subscriber) {
try {
subscriber.disconnect(brokerURI);
subscriber.close(brokerURI);
}
catch(e){ ; }
}
process.exit(0);
}
module.exports = {
connect // this function gets the callback to execute when a matching notification is received
};