Skip to content

Instantly share code, notes, and snippets.

@vasturiano
Last active April 20, 2017 19:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vasturiano/0cb8150e5bb2d6aba673 to your computer and use it in GitHub Desktop.
Save vasturiano/0cb8150e5bb2d6aba673 to your computer and use it in GitHub Desktop.
BottleNTP - A simple NTP service simulator in NodeJS
license: mit

BottleNTP

BottleNTP is a simple NTP service using a messaging-queue simulator, written in JavaScript/Node. It features one producer which publishes NTP messages every second containing the current NTP time. The service subscribes multiple NTP consumers which log the NTP time received.

A consumer must send keep-alive messages to ensure continued subscription to the service. If no keep-alive message is received from a consumer for longer than 10 seconds, that consumer is unsubscribed from the service and will receive no more messages.

Each consumer will emit keep-alive messages every 5 seconds for N times. N is a random number between 0 and 12 and is a property of the consumer itself. This way, different consumers will be unsubscribed from the service asynchronously at different times.

Setup

Clone directly from this GitHub Gist:

git clone https://gist.github.com/0cb8150e5bb2d6aba673.git BottleNTP

If needed, install Node.js. For example, if you have Homebrew on OS X:

brew install node

Usage

node BottleNTP \

This will run the main script BottleNTP/index.js. num-consumers is a positive number representing the number of consumers to subscribe to the service.

Example

> node BottleNTP 5

Started NTP broadcast service
Subscribing consumer 0 to NTP service for 25 secs (4 keepalives)
Subscribing consumer 1 to NTP service for 60 secs (11 keepalives)
Subscribing consumer 2 to NTP service for 15 secs (2 keepalives)
Subscribing consumer 3 to NTP service for 5 secs (0 keepalives)
Subscribing consumer 4 to NTP service for 15 secs (2 keepalives)
Consumer 0 NTP time: Sat Feb 28 2015 18:07:46 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:07:46 GMT-0800 (PST)
Consumer 2 NTP time: Sat Feb 28 2015 18:07:46 GMT-0800 (PST)
Consumer 3 NTP time: Sat Feb 28 2015 18:07:46 GMT-0800 (PST)
Consumer 4 NTP time: Sat Feb 28 2015 18:07:46 GMT-0800 (PST)
Consumer 0 NTP time: Sat Feb 28 2015 18:07:47 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:07:47 GMT-0800 (PST)
Consumer 2 NTP time: Sat Feb 28 2015 18:07:47 GMT-0800 (PST)
Consumer 3 NTP time: Sat Feb 28 2015 18:07:47 GMT-0800 (PST)
Consumer 4 NTP time: Sat Feb 28 2015 18:07:47 GMT-0800 (PST)

...

Consumer 2 just sent its last keepalive
Consumer 4 just sent its last keepalive
Consumer 0 NTP time: Sat Feb 28 2015 18:07:55 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:07:55 GMT-0800 (PST)
Consumer 2 NTP time: Sat Feb 28 2015 18:07:55 GMT-0800 (PST)
Consumer 4 NTP time: Sat Feb 28 2015 18:07:55 GMT-0800 (PST)

...

Consumer 1 just sent its last keepalive
Consumer 1 NTP time: Sat Feb 28 2015 18:08:41 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:42 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:43 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:44 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:45 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:46 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:47 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:48 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:49 GMT-0800 (PST)
Consumer 1 NTP time: Sat Feb 28 2015 18:08:50 GMT-0800 (PST)
Stopped NTP service
/**
* NodeJS Simple NTP Service
* Author: Vasco Asturiano
*/
// Parse command-line arguments
if (process.argv.length!=3 || isNaN(process.argv[2] || parseFloat(process.argv[2]) % 1 === 0)) {
console.log('Usage: ' + process.argv.slice(0,2).join(' ') + ' <number-consumers>');
process.exit(1);
}
var numConsumers = parseInt(process.argv[2]);
// Launch service
var NTPService = require('./ntp-service.js');
var bottleNTP = new NTPService(numConsumers, 0, 12);
// Stop service after all consumer subscriptions expired
setTimeout(function() { bottleNTP.stop(); }, 2*10000 + 12*5000);
module.exports = MessageBroker;
var Timer = require('./timer.js');
/***** Message Broker Class *****/
function MessageBroker(keepalive) {
this.idCnt = 0;
this.subscribers = {};
this.keepalive = keepalive || 10000; // ms
}
MessageBroker.prototype.publish = function (msg) {
// Deliver message to all current subscribers
for (var subId in this.subscribers) {
this.subscribers[subId].callback(msg);
}
};
MessageBroker.prototype.subscribe = function (callback) {
var self = this;
var subId = this.idCnt++;
// Register callback
this.subscribers[subId] = {
callback: callback
};
// Setup keepalive expiration
this.subscribers[subId].expireTimer = new Timer(
this.keepalive,
function () {
delete self.subscribers[subId];
}
);
// Hooks to manage subscription
return {
keepalive: function() {
if (self.subscribers.hasOwnProperty(subId)) {
self.subscribers[subId].expireTimer.reset();
return true;
} else {
// Expired subscription
return false;
}
},
unsubscribe: function() {
if (self.subscribers.hasOwnProperty(subId)) {
self.subscribers[subId].expireTimer.runNow();
}
}
}
};
module.exports = NTPService;
var MessageBroker = require('./message-broker.js');
/***** NTP Producer Class *****/
function NTPProducer(messageBroker, frequency) {
frequency = frequency || 1000; // ms
// Send NTP time every <frequency> msecs
this.interval = setInterval(
function() {
messageBroker.publish(new Date());
},
frequency
);
}
NTPProducer.prototype.stop = function() {
clearInterval(this.interval);
};
/***** NTP Consumer Class *****/
function NTPConsumer(id, messageBroker, subscriptionTime, keepaliveFrequency) {
keepaliveFrequency = keepaliveFrequency || 5000; // ms
if (subscriptionTime<=0 || keepaliveFrequency<=0) return; // no-op subscription
var numKeepalives = Math.ceil(subscriptionTime/keepaliveFrequency)-1;
console.log('Subscribing consumer ' + id + ' to NTP service for '
+ subscriptionTime/1000 + ' secs (' + numKeepalives + ' keepalives)'
);
// Subscribe consumer to NTP message queue
this.subscription = messageBroker.subscribe(function (ntpTime) {
console.log('Consumer ' + id + ' NTP time: ' + ntpTime);
});
// Setup keepalives
var subscription = this.subscription;
var keepalivesLeft = numKeepalives;
setInterval(
function () {
if (keepalivesLeft--) {
subscription.keepalive();
if (!keepalivesLeft) {
console.log('Consumer ' + id + ' just sent its last keepalive');
}
} else {
// Stop sending keepalives
clearInterval(this);
}
},
keepaliveFrequency
);
}
/***** NTP Service Class *****/
function NTPService(numConsumers, minKeepalives, maxKeepalives, consumerKeepaliveFrequency) {
minKeepalives = minKeepalives || 0;
maxKeepalives = maxKeepalives || 100;
consumerKeepaliveFrequency = consumerKeepaliveFrequency || 5000; // ms
this.messageBroker = new MessageBroker();
this.producer = new NTPProducer(this.messageBroker);
this.consumers = [];
console.log('Started NTP broadcast service');
for (var i=0; i<numConsumers; i++) {
// Random number of keepalives within min->max range
var numKeepalives = minKeepalives + Math.round(Math.random() * (maxKeepalives-minKeepalives));
var subscriptionTime = consumerKeepaliveFrequency * (numKeepalives+1);
this.consumers.push(
new NTPConsumer(i, this.messageBroker, subscriptionTime, consumerKeepaliveFrequency)
);
}
}
NTPService.prototype.stop = function() {
this.producer.stop(); // That's it
console.log('Stopped NTP service');
};
module.exports = Timer;
/***** Timer Class *****/
function Timer(timeoutTime, callback) {
this.timeoutTime = timeoutTime;
this.callback = callback;
this.timeout = null;
this.reset();
}
Timer.prototype.stop = function() {
if (this.timeout) {
clearTimeout(this.timeout);
}
};
Timer.prototype.reset = function() {
this.stop();
this.timeout = setTimeout(this.callback, this.timeoutTime);
};
Timer.prototype.runNow = function() {
this.stop();
this.callback(); // Run callback immediately
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment