Comparing version 0.5.0 to 0.6.0
{ | ||
"name": "sneeze", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Easily join SWIM networks", | ||
"main": "sneeze.js", | ||
"scripts": { | ||
"test": "lab -P test -v -t", | ||
"test": "lab -P test -v -t 90 -r console -I Reflect", | ||
"test-cov-html": "lab -P test -r html -o coverage.html" | ||
@@ -28,2 +28,3 @@ }, | ||
"lodash": "4.5.0", | ||
"optioner": "0.6.0", | ||
"swim": "0.3.0" | ||
@@ -30,0 +31,0 @@ }, |
# sneeze | ||
Easily join SWIM networks. See http://www.cs.cornell.edu/~asdas/research/dsn02-SWIM.pdf. | ||
Easily join SWIM networks. See | ||
http://www.cs.cornell.edu/~asdas/research/dsn02-SWIM.pdf. | ||
This module is used by [seneca-mesh](github.com/rjrodger/seneca-mesh) | ||
to provide zero-configuration service discovery. | ||
## Quick Example | ||
The *base* node serves as the well-known starting point. As other | ||
nodes join and leave all nodes eventually learn about them | ||
nodes (A and B) join and leave. All nodes eventually learn about them. | ||
@@ -28,2 +32,5 @@ ```js | ||
## Questions? | ||
@@ -30,0 +37,0 @@ |
286
sneeze.js
@@ -13,4 +13,27 @@ /* | ||
var Swim = require('swim') | ||
var Optioner = require('optioner') | ||
var Joi = Optioner.Joi | ||
var DEFAULT_HOST = module.exports.DEFAULT_HOST = '127.0.0.1' | ||
var DEFAULT_PORT = module.exports.DEFAULT_PORT = 39999 | ||
var optioner = Optioner({ | ||
isbase: false, | ||
host: DEFAULT_HOST, | ||
bases: Joi.array().default([DEFAULT_HOST+':'+DEFAULT_PORT]), | ||
retry_attempts: 22, | ||
retry_min: 111, | ||
retry_max: 555, | ||
silent: true, | ||
log: null, | ||
tag: null, | ||
port: null, | ||
identifier: null, | ||
// [include,exclude] | ||
port_range: [40000,50000] | ||
}) | ||
module.exports = function (options) { | ||
@@ -23,8 +46,8 @@ return new Sneeze( options ) | ||
var self = this | ||
var tick = 0 | ||
/* | ||
options = _.defaultsDeep(options,{ | ||
isbase: false, | ||
host: '127.0.0.1', | ||
bases: ['127.0.0.1:39999'], | ||
host: DEFAULT_HOST, | ||
bases: [DEFAULT_HOST+':'+DEFAULT_PORT], | ||
retry_attempts: 22, | ||
@@ -35,172 +58,189 @@ retry_min: 111, | ||
log: null, | ||
tag: null | ||
tag: null, | ||
port: null, | ||
// [include,exclude] | ||
port_range: [40000,50000] | ||
}) | ||
*/ | ||
if( !options.silent && null == options.log ) { | ||
options.log = function () { | ||
console.log.apply(null,_.flatten(['SNEEZE',tick,arguments])) | ||
} | ||
} | ||
optioner(options, function(err, options) { | ||
if (err) throw err | ||
var log = options.log || _.noop | ||
var isbase = !!options.isbase | ||
if( options.isbase ) { | ||
options.port = null == options.port ? 39999 : options.port | ||
} | ||
else { | ||
options.port = options.port || function() { | ||
return 40000 + Math.floor((10000*Math.random())) | ||
} | ||
} | ||
self.log = | ||
!!options.silent ? _.noop : | ||
_.isFunction(options.log) ? options.log : | ||
function () { | ||
console.log.apply(null,_.flatten( | ||
['SNEEZE', (''+Date.now()).substring(8), arguments])) | ||
} | ||
var swim | ||
var members = {} | ||
self.makeport = _.isFunction(options.port) ? options.port : | ||
function() { | ||
var port = parseInt(options.port) | ||
var pr = options.port_range | ||
this.join = function( meta ) { | ||
meta = meta || {} | ||
port = !isNaN(port) ? port : | ||
isbase ? DEFAULT_PORT : | ||
pr[0] + | ||
Math.floor(((pr[1]-pr[0])*Math.random())) | ||
var attempts = 0, max_attempts = options.retry_attempts, joined = false | ||
return port | ||
} | ||
function join() { | ||
var port = (_.isFunction(options.port) ? options.port() : options.port ) | ||
var host = options.host + ':' + port | ||
var incarnation = Date.now() | ||
var swim | ||
var members = {} | ||
self.id = meta.identifier$ = null == options.identifier ? | ||
host+'~'+incarnation+'~'+Math.random() : options.identifier | ||
self.join = function( meta ) { | ||
meta = meta || {} | ||
meta.tag$ = options.tag | ||
var attempts = 0, max_attempts = options.retry_attempts, joined = false | ||
log('joining',attempts,host,meta.identifier$,meta.tag$) | ||
function join() { | ||
//var port = (_.isFunction(options.port) ? options.port() : options.port ) | ||
var port = self.makeport() | ||
var host = options.host + ':' + port | ||
var incarnation = Date.now() | ||
var swim_opts = _.defaultsDeep(options.swim,{ | ||
codec: 'msgpack', | ||
disseminationFactor: 22, | ||
interval: 111, | ||
joinTimeout: 777, | ||
pingTimeout: 444, | ||
pingReqTimeout: 333, | ||
pingReqGroupSize: 7, | ||
udp: {maxDgramSize: 2048}, | ||
}) | ||
self.id = meta.identifier$ = null == options.identifier ? | ||
host+'~'+incarnation+'~'+Math.random() : options.identifier | ||
swim_opts.local = { | ||
host: host, | ||
meta: meta, | ||
incarnation: incarnation, | ||
} | ||
meta.tag$ = options.tag | ||
var bases = _.compact(_.clone(options.bases)) | ||
if( options.isbase ) { | ||
_.remove(bases,function(r) { return r === host }) | ||
} | ||
self.log('joining',attempts,host,meta.identifier$,meta.tag$) | ||
swim = new Swim(swim_opts) | ||
var swim_opts = _.defaultsDeep(options.swim,{ | ||
codec: 'msgpack', | ||
disseminationFactor: 22, | ||
interval: 111, | ||
joinTimeout: 777, | ||
pingTimeout: 444, | ||
pingReqTimeout: 333, | ||
pingReqGroupSize: 7, | ||
udp: {maxDgramSize: 2048}, | ||
}) | ||
swim.net.on('error', function(err) { | ||
if (err && !joined && attempts < max_attempts) { | ||
attempts++ | ||
swim_opts.local = { | ||
host: host, | ||
meta: meta, | ||
incarnation: incarnation, | ||
} | ||
var wait = options.retry_min + | ||
Math.floor(Math.random() * (options.retry_max-options.retry_min)) | ||
setTimeout(join, wait) | ||
return | ||
var bases = _.compact(_.clone(options.bases)) | ||
if( isbase ) { | ||
_.remove(bases,function(r) { return r === host }) | ||
} | ||
else if( err ) { | ||
self.emit('error',err) | ||
return | ||
} | ||
}) | ||
swim.bootstrap(bases, function onBootstrap(err) { | ||
if (!options.isbase && err && !joined && attempts < max_attempts) { | ||
attempts++ | ||
swim = new Swim(swim_opts) | ||
var wait = options.retry_min + | ||
Math.floor(Math.random() * (options.retry_max-options.retry_min)) | ||
swim.net.on('error', function(err) { | ||
if (err && !joined && attempts < max_attempts) { | ||
attempts++ | ||
setTimeout(join, wait) | ||
return | ||
} | ||
else if( err ) { | ||
// first base node will see a JoinFailedError as there is | ||
// nobody else out there | ||
if( !options.isbase || 'JoinFailedError' !== err.name ) { | ||
var wait = options.retry_min + | ||
Math.floor(Math.random() * (options.retry_max-options.retry_min)) | ||
setTimeout(join, wait) | ||
return | ||
} | ||
else if( err ) { | ||
self.emit('error',err) | ||
return | ||
} | ||
} | ||
}) | ||
joined = true | ||
swim.bootstrap(bases, function onBootstrap(err) { | ||
if (!isbase && err && !joined && attempts < max_attempts) { | ||
attempts++ | ||
_.each( swim.members(), updateinfo ) | ||
var wait = options.retry_min + | ||
Math.floor(Math.random() * (options.retry_max-options.retry_min)) | ||
swim.on(Swim.EventType.Update, function onUpdate(info) { | ||
updateinfo(info) | ||
}) | ||
setTimeout(join, wait) | ||
return | ||
} | ||
else if( err ) { | ||
// first base node will see a JoinFailedError as there is | ||
// nobody else out there | ||
if( !isbase || 'JoinFailedError' !== err.name ) { | ||
self.emit('error',err) | ||
return | ||
} | ||
} | ||
swim.on(Swim.EventType.Change, function onChange(info) { | ||
updateinfo(info) | ||
joined = true | ||
_.each( swim.members(), updateinfo ) | ||
swim.on(Swim.EventType.Update, function onUpdate(info) { | ||
updateinfo(info) | ||
}) | ||
swim.on(Swim.EventType.Change, function onChange(info) { | ||
updateinfo(info) | ||
}) | ||
self.emit('ready') | ||
}) | ||
self.emit('ready') | ||
}) | ||
function updateinfo( m ) { | ||
if (!m.meta) { | ||
return | ||
} | ||
function updateinfo( m ) { | ||
if (!m.meta) { | ||
return | ||
} | ||
if( null != meta.tag$ && m.meta.tag$ !== meta.tag$ ) { | ||
return | ||
} | ||
if( null != meta.tag$ && m.meta.tag$ !== meta.tag$ ) { | ||
return | ||
} | ||
if( m.meta.identifier$ === meta.identifier$ ) { | ||
return | ||
} | ||
if( m.meta.identifier$ === meta.identifier$ ) { | ||
return | ||
} | ||
if( 0 === m.state ) { | ||
add_node( host, m.meta ) | ||
} | ||
if( 0 === m.state ) { | ||
add_node( host, m.meta ) | ||
// Note: trigger happy | ||
else if( 2 === m.state ) { | ||
remove_node( host, m.meta ) | ||
} | ||
} | ||
} | ||
// Note: trigger happy | ||
else if( 2 === m.state ) { | ||
remove_node( host, m.meta ) | ||
} | ||
} | ||
join() | ||
} | ||
join() | ||
} | ||
self.members = function() { | ||
return _.clone( members ) | ||
} | ||
self.members = function() { | ||
return _.clone( members ) | ||
} | ||
self.leave = function() { | ||
swim && swim.leave() | ||
} | ||
self.leave = function() { | ||
swim && swim.leave() | ||
} | ||
function add_node( host, meta ) { | ||
self.log('add', host, meta.identifier$, meta.tag$, meta) | ||
members[meta.identifier$] = meta | ||
self.emit('add', meta) | ||
} | ||
function add_node( host, meta ) { | ||
log('add', host, meta.identifier$, meta.tag$, meta) | ||
members[meta.identifier$] = meta | ||
self.emit('add', meta) | ||
} | ||
function remove_node( host, meta ) { | ||
self.log('remove', host, meta.identifier$, meta.tag$, meta) | ||
delete members[meta.identifier$] | ||
self.emit('remove', meta) | ||
} | ||
function remove_node( host, meta ) { | ||
log('remove', host, meta.identifier$, meta.tag$, meta) | ||
delete members[meta.identifier$] | ||
self.emit('remove', meta) | ||
} | ||
self.on('error',function(err){ | ||
self.log('ERROR',err) | ||
}) | ||
self.on('error',function(err){ | ||
log('ERROR QQQ',err) | ||
}) | ||
} | ||
Util.inherits(Sneeze, Events.EventEmitter) |
@@ -5,12 +5,14 @@ // quick start with single base: | ||
// multi-base network: | ||
// $ node base.js b0 39000 127.0.0.1:39000,127.0.0.1:39001 | ||
// $ node base.js b1 39001 127.0.0.1:39000,127.0.0.1:39001 | ||
// $ node base.js b0 127.0.0.1 39000 127.0.0.1:39000,127.0.0.1:39001 | ||
// $ node base.js b1 127.0.0.1 39001 127.0.0.1:39000,127.0.0.1:39001 | ||
var NAME = process.env.NAME || process.argv[2] || 'b0' | ||
var PORT = process.env.PORT || process.argv[3] || 39999 | ||
var BASES = (process.env.BASES || process.argv[4] || '').split(',') | ||
var HOST = process.env.HOST || process.argv[3] || '127.0.0.1' | ||
var PORT = process.env.PORT || process.argv[4] || 39000 | ||
var BASES = (process.env.BASES || process.argv[5] || '127.0.0.1:39000').split(',') | ||
require('..')({ | ||
isbase: true, | ||
silent: true, | ||
silent: false, | ||
host: HOST, | ||
port: PORT, | ||
@@ -17,0 +19,0 @@ bases: BASES |
@@ -5,14 +5,16 @@ // quick start with single base: | ||
// multi-base network: | ||
// $ node member.js m0 127.0.0.1:39000,127.0.0.1:39001 | ||
// $ node member.js m1 127.0.0.1:39000,127.0.0.1:39001 | ||
// $ node member.js m2 127.0.0.1:39000,127.0.0.1:39001 | ||
// $ node member.js m0 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001 | ||
// $ node member.js m1 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001 | ||
// $ node member.js m2 127.0.0.1 127.0.0.1:39000,127.0.0.1:39001 | ||
// ... | ||
var NAME = process.env.NAME || process.argv[2] || 'm0' | ||
var BASES = (process.env.BASES || process.argv[3] || '127.0.0.1:39999').split(',') | ||
var HOST = process.env.HOST || process.argv[3] || '127.0.0.1' | ||
var BASES = (process.env.BASES || process.argv[4] || '127.0.0.1:39000').split(',') | ||
require('..')({ | ||
bases: BASES, | ||
bases: BASES, | ||
host: HOST, | ||
silent: false | ||
}) | ||
.join({name: NAME}) |
@@ -56,2 +56,48 @@ 'use strict' | ||
it('methods', function (done) { | ||
var s0 = Sneeze() | ||
s0.log('vanish') | ||
for( var i = 0; i < 22222; ++i) { | ||
var p = s0.makeport() | ||
expect(40000<=p && p<50000).to.equal(true) | ||
} | ||
s0.leave() | ||
var log = [] | ||
var s1 = Sneeze({ | ||
port: 33333, | ||
silent: false, | ||
log: function () { | ||
log.push(Array.prototype.slice.call(arguments)) | ||
} | ||
}) | ||
s1.log('hello1') | ||
expect(s1.makeport()).to.equal(33333) | ||
expect(log).to.deep.equal([['hello1']]) | ||
log = [] | ||
var origwrite = process.stdout.write | ||
process.stdout.write = function() { | ||
log.push(arguments) | ||
} | ||
var s2 = Sneeze({ | ||
port: function() { | ||
return 22222 | ||
}, | ||
silent: false | ||
}) | ||
s2.log('hello2') | ||
process.stdout.write = origwrite | ||
expect(s2.makeport()).to.equal(22222) | ||
expect(log[0][0]).to.contain('hello2') | ||
done() | ||
}) | ||
it('collision', { parallel: false, timeout:5555 }, function (done) { | ||
@@ -199,3 +245,3 @@ var base = Sneeze({isbase: true}) | ||
isbase: true, silent: silent, identifier:'b0', | ||
host:'127.0.0.1',port:39000, | ||
host:'127.0.0.1', port:39000, | ||
bases:bases | ||
@@ -202,0 +248,0 @@ }) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18954
536
45
3
7
+ Addedoptioner@0.6.0
+ Addedhoek@4.0.1(transitive)
+ Addedisemail@2.2.1(transitive)
+ Addeditems@2.2.1(transitive)
+ Addedjoi@9.0.4(transitive)
+ Addedmoment@2.30.1(transitive)
+ Addedoptioner@0.6.0(transitive)
+ Addedtopo@2.1.1(transitive)