Full Code of mrhooray/swim-js for AI

master bd3d2f87d60e cached
30 files
116.0 KB
26.8k tokens
29 symbols
1 requests
Download .txt
Repository: mrhooray/swim-js
Branch: master
Commit: bd3d2f87d60e
Files: 30
Total size: 116.0 KB

Directory structure:
gitextract_5lnnmlc_/

├── .gitignore
├── .jscsrc
├── .jshintrc
├── .npmignore
├── .travis.yml
├── README.md
├── bench/
│   ├── index.js
│   ├── lib/
│   │   └── runner.js
│   ├── scenario/
│   │   └── single-node-failure.js
│   └── script/
│       ├── convergence-time.js
│       ├── dumb.js
│       └── worker.js
├── docs/
│   └── api.md
├── index.js
├── lib/
│   ├── codec.js
│   ├── disseminator.js
│   ├── error.js
│   ├── failure-detector.js
│   ├── member.js
│   ├── membership.js
│   ├── message-type.js
│   ├── net.js
│   └── swim.js
├── package.json
└── test/
    ├── codec.js
    ├── disseminator.js
    ├── failure-detector.js
    ├── index.js
    ├── membership.js
    └── net.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules/


================================================
FILE: .jscsrc
================================================
{
    "fileExtensions": [".js"],

    "maximumLineLength": 120,
    "validateIndentation": 4,
    "validateLineBreaks": "LF",
    "validateQuoteMarks": "'",

    "disallowAnonymousFunctions": true,
    "disallowMultipleLineBreaks": true,
    "disallowMultipleVarDecl": true,
    "disallowQuotedKeysInObjects": true,
    "disallowSpacesInsideObjectBrackets": "all",

    "excludeFiles": [
        "node_modules/**"
    ],

    // crockford preset with exceptions
    "requireCurlyBraces": [
        "if",
        "else",
        "for",
        "while",
        "do",
        "try",
        "catch"
    ],
    "requireSpaceAfterKeywords": [
        "if",
        "else",
        "for",
        "while",
        "do",
        "switch",
        "case",
        "return",
        "try",
        "catch",
        "function",
        "typeof"
    ],
    "requireSpaceBeforeBlockStatements": true,
    "requireParenthesesAroundIIFE": true,
    "requireSpacesInConditionalExpression": true,
    "disallowSpacesInNamedFunctionExpression": {
        "beforeOpeningRoundBrace": true
    },
    "disallowSpacesInFunctionDeclaration": {
        "beforeOpeningRoundBrace": true
    },
    // "requireMultipleVarDecl": "onevar",
    "requireBlocksOnNewline": true,
    "disallowEmptyBlocks": true,
    "disallowSpacesInsideArrayBrackets": true,
    "disallowSpacesInsideParentheses": true,
    "disallowDanglingUnderscores": true,
    "requireCommaBeforeLineBreak": true,
    "disallowSpaceAfterPrefixUnaryOperators": true,
    "disallowSpaceBeforePostfixUnaryOperators": true,
    "disallowSpaceBeforeBinaryOperators": [
        ","
    ],
    "requireSpacesInForStatement": true,
    "requireSpaceBeforeBinaryOperators": true,
    "requireSpaceAfterBinaryOperators": true,
    "disallowKeywords": [
        "with",
        "continue"
    ],
    "validateIndentation": 4,
    "disallowMixedSpacesAndTabs": true,
    "disallowTrailingWhitespace": true,
    "disallowTrailingComma": true,
    "disallowKeywordsOnNewLine": [
        "else"
    ],
    "requireLineFeedAtFileEnd": true,
    "requireCapitalizedConstructors": true,
    "requireDotNotation": true,
    "disallowNewlineBeforeBlockStatements": true,
    "disallowMultipleLineStrings": true
}


================================================
FILE: .jshintrc
================================================
{
  "bitwise": true,
  "camelcase": true,
  "curly": true,
  "eqeqeq": true,
  "esnext": true,
  "freeze": true,
  "immed": true,
  "indent": 4,
  "latedef": "nofunc",
  "maxlen": 120,
  "newcap": true,
  "noarg": true,
  "noempty": true,
  "nonew": true,
  "quotmark": "single",
  "strict": true,
  "undef": true,
  "unused": true,
  "node": true
}


================================================
FILE: .npmignore
================================================
# Do not package non-functional code when importing this package via
# npm.
docs/
bench/
test/
.travis.yml
.jscsrc
.jshint.rc


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - "node"
  - "lts/*"
env:
  - CXX=g++-4.8
addons:
  apt:
    sources:
    - ubuntu-toolchain-r-test
    packages:
    - g++-4.8


================================================
FILE: README.md
================================================
# swim-js [![Build Status](https://travis-ci.org/mrhooray/swim-js.svg?branch=master)](https://travis-ci.org/mrhooray/swim-js)
> JavaScript implementation of [SWIM](http://www.cs.cornell.edu/~asdas/research/dsn02-SWIM.pdf) membership protocol

* [npm](https://www.npmjs.com/package/swim)
* [Motivation](#motivation)
* [Usage](#usage)
* [Benchmark](#benchmark)
* [License](#license)

## Motivation

Membership management is important to distributed systems and large clusters need a decentralized protocol such as SWIM,
 which handles failure detection and membership dissemination in a scalable and weakly-consistent way.
It can be used to implement functionalities based on membership like distributed consensus, application layer sharding, log replication, etc.

## Usage

Installation
```sh
npm install swim --save
```
```js
var Swim = require('swim');
var opts = {
    local: {
        host: '10.31.1.191:11000',
        meta: {'application': 'info'} // optional
    },
    codec: 'msgpack', // optional
    disseminationFactor: 15, // optional
    interval: 100, // optional
    joinTimeout: 200, // optional
    pingTimeout: 20, // optional
    pingReqTimeout: 60, // optional
    pingReqGroupSize: 3, // optional
    suspectTimeout: 60, // optional
    udp: {maxDgramSize: 512}, // optional
    preferCurrentMeta: true // optional
};
var swim = new Swim(opts);
var hostsToJoin = ['10.31.1.192:11000', '10.31.1.193:11000'];

swim.bootstrap(hostsToJoin, function onBootstrap(err) {
    if (err) {
        // error handling
        return;
    }

    // ready
    console.log(swim.whoami());
    console.log(swim.members());
    console.log(swim.checksum());

    // change on membership, e.g. new node or node died/left
    swim.on(Swim.EventType.Change, function onChange(update) {});
    // update on membership, e.g. node recovered or update on meta data
    swim.on(Swim.EventType.Update, function onUpdate(update) {});

    // shutdown
    swim.leave();
});

// or
swim.bootstrap(hostsToJoin);
// bootstrap error handling
swim.on(Swim.EventType.Error, function onError(err) {});
// bootstrap ready
swim.on(Swim.EventType.Ready, function onReady() {});
```

[Additional API Documentation](docs/api.md)

## Benchmark

Benchmark convergence time under different configuration
```sh
node bench/script/convergence-time.js -h

  Usage: convergence-time [options]

  Options:

    -h, --help                      output usage information
    --cycles [value]                number of cycles
    --workers [value]               number of workers
    --codec [value]                 msgpack or json
    --dissemination-factor [value]  dissemination factor
    --interval [value]              interval
    --join-timeout [value]          join timeout
    --ping-timeout [value]          ping timeout
    --ping-req-timeout [value]      ping req timeout
    --ping-req-group-size [value]   ping req group size
    --max-dgram-size [value]        max dgram size
```
```sh
node bench/script/convergence-time.js

configuration:
- cycles 10
- workers 10
- codec msgpack
- dissemination factor 15
- interval 20 ms
- join timeout 100 ms
- ping timeout 4 ms
- ping req timeout 12 ms
- ping req group size 3
- max dgram size 512 bytes
convergence time under single node failure
histogram data:
- count 10
- min 76
- max 123
- mean 100
- median 101
- variance 308.44444444444446
- std dev 17.56258649642599
- p75 116.25
- p95 123
- p99 123
```

## License

MIT


================================================
FILE: bench/index.js
================================================
'use strict';
// var cycles = [10, 20];
// var workers = [4, 16, 32, 64];
// var codec = ['json', 'msgpack'];
// var disseminationFactor = [4, 16, 32];
// var interval = [10, 100];
// var pingTimeout = [4, 16];
// var pingReqTimeout = [12, 48];
// var pingReqGroupSize = [1, 4];
// var maxDgramSize = [512, 1024, 2048];


================================================
FILE: bench/lib/runner.js
================================================
'use strict';
var async = require('async');
var events = require('events');
var util = require('util');

function Runner(opts) {
    this.cycles = opts.cycles || 0;
    this.setup = opts.setup || noop;
    this.teardown = opts.teardown || noop;
    this.before = opts.suite.before || noop;
    this.fn = opts.suite.fn || noop;
    this.after = opts.suite.after || noop;
}

util.inherits(Runner, events.EventEmitter);

Runner.prototype.run = function run(callback) {
    var self = this;

    async.series([
        function setup(callback) {
            self.emit(Runner.EventType.Setup);
            self.setup(callback);
        },
        function run(callback) {
            async.timesSeries(self.cycles, function wrappedRun(i, callback) {
                async.series([
                    function before(callback) {
                        self.emit(Runner.EventType.Before);
                        self.before(callback);
                    },
                    function fn(callback) {
                        self.emit(Runner.EventType.Fn);
                        self.fn(callback);
                    },
                    function after(callback) {
                        self.emit(Runner.EventType.After);
                        self.after(callback);
                    }
                ], callback);
            }, callback);
        },
        function teardown(callback) {
            self.emit(Runner.EventType.Teardown);
            self.teardown(callback);
        }
    ], callback);
};

Runner.EventType = {
    After: 'after',
    Before: 'before',
    Fn: 'fn',
    Setup: 'setup',
    Teardown: 'teardown'
};

module.exports = Runner;

function noop(callback) {
    if (typeof callback === 'function') {
        process.nextTick(callback);
    }
}


================================================
FILE: bench/scenario/single-node-failure.js
================================================
'use strict';

module.exports = function singleNodeFailure(context, callback) {
    var hosts = Object.keys(context.hostToAliveWorker);
    var host = hosts[Math.floor(Math.random() * hosts.length)];

    if (host) {
        context.hostToFaultyWorker[host] = context.hostToAliveWorker[host];
        delete context.hostToAliveWorker[host];
        context.hostToFaultyWorker[host].send({
            cmd: 'leave'
        });
    }

    process.nextTick(callback);
};


================================================
FILE: bench/script/convergence-time.js
================================================
#!/usr/bin/env node
'use strict';
var cp = require('child_process');
var metrics = require('metrics');
var program = require('commander');

var Runner = require('../lib/runner');
var singleNodeFailure = require('../scenario/single-node-failure');

var PORT_BASE = 20000;

if (require.main === module) {
    parseArgs();
    main();
}

function parseArgs() {
    program
    .option('--cycles [value]', 'number of cycles', parseInt, 10)
    .option('--workers [value]', 'number of workers', parseInt, 10)
    .option('--codec [value]', 'msgpack or json', 'msgpack')
    .option('--dissemination-factor [value]', 'dissemination factor', parseInt, 15)
    .option('--interval [value]', 'interval', parseInt, 20)
    .option('--join-timeout [value]', 'join timeout', parseInt, 100)
    .option('--ping-timeout [value]', 'ping timeout', parseInt, 4)
    .option('--ping-req-timeout [value]', 'ping req timeout', parseInt, 12)
    .option('--ping-req-group-size [value]', 'ping req group size', parseInt, 3)
    .option('--max-dgram-size [value]', 'max dgram size', parseInt, 512)
    .parse(process.argv);

    console.log('configuration:');
    console.log('- cycles', program.cycles);
    console.log('- workers', program.workers);
    console.log('- codec', program.codec);
    console.log('- dissemination factor', program.disseminationFactor);
    console.log('- interval', program.interval, 'ms');
    console.log('- join timeout', program.joinTimeout, 'ms');
    console.log('- ping timeout', program.pingTimeout, 'ms');
    console.log('- ping req timeout', program.pingReqTimeout, 'ms');
    console.log('- ping req group size', program.pingReqGroupSize);
    console.log('- max dgram size', program.maxDgramSize, 'bytes');
}

function main() {
    var histogram = new metrics.Histogram();
    var context = {
        hostToAliveWorker: Object.create(null),
        hostToFaultyWorker: Object.create(null),
        hostToChecksum: Object.create(null),
        numberOfWorkers: program.workers
    };
    var runner = new Runner({
        cycles: program.cycles,
        setup: setup.bind(undefined, context),
        teardown: teardown.bind(undefined, context),
        suite: {
            before: before.bind(undefined, context),
            fn: fn.bind(undefined, context),
            after: after.bind(undefined, context)
        }
    });
    var time;

    runner.on(Runner.EventType.Fn, function onCycleStart() {
        time = Date.now();
    });
    runner.on(Runner.EventType.After, function onCycleComplete() {
        histogram.update(Date.now() - time);
    });

    console.log('convergence time under single node failure');

    runner.run(function report() {
        var result = histogram.printObj();

        console.log('histogram data:');
        console.log('- count', result.count);
        console.log('- min', result.min);
        console.log('- max', result.max);
        console.log('- mean', result.mean);
        console.log('- median', result.median);
        console.log('- variance', result.variance);
        /* jshint camelcase: false */
        console.log('- std dev', result.std_dev);
        /* jshint camelcase: true */
        console.log('- p75', result.p75);
        console.log('- p95', result.p95);
        console.log('- p99', result.p99);
    });
}

function setup(context, callback) {
    var readyCount = 0;

    fork(context, function onMessage(message) {
        switch (message.type) {
            case 'ready':
                readyCount += 1;
                if (readyCount === context.numberOfWorkers) {
                    Object.keys(context.hostToAliveWorker).forEach(function join(host) {
                        context.hostToAliveWorker[host].send({
                            cmd: 'join',
                            hosts: getHostsToJoin(Math.ceil(context.numberOfWorkers / 3))
                        });
                    });
                    waitForConvergence(context, callback);
                }
                break;
            case 'checksum':
                context.hostToChecksum[message.host] = message.value;
                break;
        }
    });
}

function fork(context, onMessage) {
    var args;
    var host;
    var worker;
    var i;

    for (i = 0; i < context.numberOfWorkers; i++) {
        host = '127.0.0.1:' + (PORT_BASE + i);
        args = [];
        args.push('--host', host);
        if (program.codec) {
            args.push('--codec', program.codec);
        }
        if (program.disseminationFactor) {
            args.push('--dissemination-factor', program.disseminationFactor);
        }
        if (program.interval) {
            args.push('--interval', program.interval);
        }
        if (program.joinTimeout) {
            args.push('--join-timeout', program.joinTimeout);
        }
        if (program.pingTimeout) {
            args.push('--ping-timeout', program.pingTimeout);
        }
        if (program.pingReqTimeout) {
            args.push('--ping-req-timeout', program.pingReqTimeout);
        }
        if (program.pingReqGroupSize) {
            args.push('--ping-req-group-size', program.pingReqGroupSize);
        }
        if (program.maxDgramSize) {
            args.push('--max-dgram-size', program.maxDgramSize);
        }

        worker = cp.fork(__dirname + '/worker.js', args);
        worker.on('message', onMessage);
        context.hostToAliveWorker[host] = worker;
    }
}

function getHostsToJoin(n) {
    var hostToJoin = [];
    var i;

    for (i = 0; i < n; i++) {
        hostToJoin.push('127.0.0.1:' + (PORT_BASE + i));
    }

    return hostToJoin;
}

function waitForConvergence(context, callback) {
    var handle = setInterval(function check() {
        var hosts = Object.keys(context.hostToAliveWorker);
        var i;

        for (i = 1; i < hosts.length; i++) {
            if (!context.hostToChecksum[hosts[i]] ||
                context.hostToChecksum[hosts[i - 1]] !== context.hostToChecksum[hosts[i]]) {
                return;
            }
        }

        if (Object.keys(context.hostToChecksum).length >= Object.keys(context.hostToAliveWorker).length) {
            context.hostToChecksum = Object.create(null);
            clearInterval(handle);
            callback();
        }
    }, 5);
}

function teardown(context, callback) {
    Object.keys(context.hostToAliveWorker).forEach(function shutdown(host) {
        context.hostToAliveWorker[host].send({
            cmd: 'shutdown'
        });
    });
    process.nextTick(callback);
}

function before(context, callback) {
    singleNodeFailure(context, callback);
}

function fn(context, callback) {
    waitForConvergence(context, callback);
}

function after(context, callback) {
    Object.keys(context.hostToFaultyWorker).forEach(function join(host) {
        context.hostToAliveWorker[host] = context.hostToFaultyWorker[host];
        delete context.hostToFaultyWorker[host];
        context.hostToAliveWorker[host].send({
            cmd: 'bootstrap',
            hosts: getHostsToJoin(Math.ceil(context.numberOfWorkers / 3))
        });
    });
    waitForConvergence(context, callback);
}


================================================
FILE: bench/script/dumb.js
================================================
#!/usr/bin/env node
'use strict';
var Swim = require('../../lib/swim');

var hosts = [
    '127.0.0.1:11111',
    '127.0.0.1:22222',
    '127.0.0.1:33333'
];
var swim0 = new Swim({
    local: {
        host: hosts[0],
        meta: {
            node: 1
        }
    }
});

var swim1 = new Swim({
    local: {
        host: hosts[1],
        meta: {
            node: 2
        }
    }
});

var swim2 = new Swim({
    local: {
        host: hosts[2],
        meta: {
            node: 3
        }
    }
});

swim0.bootstrap([], function onBootstrap(err) {
    console.log('swim0 bootstrap error', err);
    swim1.bootstrap(hosts, function onBootstrap(err) {
        console.log('swim1 bootstrap error', err);
    });
    swim2.bootstrap(hosts, function onBootstrap(err) {
        console.log('swim2 bootstrap error', err);
    });
});

setInterval(function print() {
    console.log('-------------------------------');
    console.log(swim0.localhost(), swim0.members());
    console.log(swim1.localhost(), swim1.members());
    console.log(swim2.localhost(), swim2.members());
}, 1000);

setTimeout(function leave() {
    console.log('swim0 leaves');
    swim0.leave();
}, 1000 * 5);

setTimeout(function rejoin() {
    console.log('swim0 rejoins');
    swim0.bootstrap(hosts, function onBootstrap(err) {
        console.log('swim0 bootstrap error', err);
    });
}, 1000 * 15);

setTimeout(function leave() {
    console.log('swim0 leaves');
    swim0.leave();
}, 1000 * 20);

setTimeout(function rejoin() {
    console.log('swim0 rejoins');
    swim0.bootstrap(hosts, function onBootstrap(err) {
        console.log('swim0 bootstrap error', err);
    });
}, 1000 * 23);


================================================
FILE: bench/script/worker.js
================================================
#!/usr/bin/env node
'use strict';
var assert = require('assert');
var program = require('commander');

var Swim = require('../../');

if (require.main === module) {
    parseArgs();
    bootstrap(function onBootstrap(err, swim) {
        if (err) {
            console.log(err);
            process.exit(1);
        }

        handleMessage(swim);
    });
}

function parseArgs() {
    program
    .option('--host <value>', 'host')
    .option('--hosts-to-join [hosts]', 'hosts to join', function split(list) {
        return list.split(',');
    }, [])
    .option('--codec [value]', 'msgpack or json')
    .option('--dissemination-factor [value]', 'dissemination factor', parseInt)
    .option('--interval [value]', 'interval', parseInt)
    .option('--join-timeout [value]', 'join timeout', parseInt)
    .option('--ping-timeout [value]', 'ping timeout', parseInt)
    .option('--ping-req-timeout [value]', 'ping req timeout', parseInt)
    .option('--ping-req-group-size [value]', 'ping req group size', parseInt)
    .option('--max-dgram-size [value]', 'max dgram size', parseInt)
    .parse(process.argv);

    assert(/^(\d+\.\d+\.\d+\.\d+):(\d+)$/.test(program.host));
    assert(Array.isArray(program.hostsToJoin));
}

function handleMessage(swim) {
    var onMessage = function onMessage(message) {
        switch (message.cmd) {
            case 'bootstrap':
                swim.bootstrap(message.hosts, function onBootstrap(err) {
                    if (err) {
                        console.log(err);
                        process.exit(1);
                    }

                    process.send({
                        type: 'checksum',
                        host: swim.localhost(),
                        value: swim.checksum()
                    });
                });
                break;
            case 'join':
                swim.join(message.hosts, function onBootstrap(err) {
                    if (err) {
                        console.log(err);
                        process.exit(1);
                    }

                    process.send({
                        type: 'checksum',
                        host: swim.localhost(),
                        value: swim.checksum()
                    });
                });
                break;
            case 'leave':
                swim.leave();
                break;
            case 'shutdown':
                swim.leave();
                process.removeListener('message', onMessage);
                break;
        }
    };

    process.on('message', onMessage);
}

function bootstrap(callback) {
    var opts = {
        local: {
            host: program.host,
            meta: {
                app: 'benchmark'
            }
        },
        codec: program.codec,
        disseminationFactor: program.disseminationFactor,
        interval: program.interval,
        joinTimeout: program.joinTimeout,
        pingTimeout: program.pingTimeout,
        pingReqTimeout: program.pingReqTimeout,
        pingReqGroupSize: program.pingReqGroupSize,
        udp: {
            maxDgramSize: program.maxDgramSize
        }
    };
    var swim = new Swim(opts);

    swim.on(Swim.EventType.Update, function onUpdate() {
        process.send({
            type: 'checksum',
            host: swim.localhost(),
            value: swim.checksum()
        });
    });

    swim.bootstrap(program.hostsToJoin, function onBootstrap(err) {
        if (err) {
            return callback(err);
        }

        process.send({
            type: 'checksum',
            host: swim.localhost(),
            value: swim.checksum()
        });

        process.send({
            type: 'ready'
        });

        callback(null, swim);
    });
}


================================================
FILE: docs/api.md
================================================
# API Documentation

## Swim object

### Constructor(options)
See Options Object.

### boostrap(hosts, callback)
Bootstrap swim instance and join cluster.
* hosts - address:port list to connect to when joining the cluster
* callback(err) - callback function

### join(hosts, callback)
Join cluster.
* hosts - address:port list to connect to when joining the cluster
* callback(err) - callback function

### leave()
Leave cluster.

### localhost()
Get host for local node.

### whoami()
Alias to localhost().

### members(hasLocal, hasFaulty)
Get members in the cluster.
* hasLocal - include local node
* hasFaulty - include nodes marked as faulty

### checksum()
Get membership checksum.

### updateMeta(meta)
Update metadata of local node and disseminate within cluster.

## Options Object
This object contains configuration settings.

### local
Local Option.
* local.host (required) - address:port for this instance, e.g. 127.0.0.1:11000
* local.meta (optional) - metadata about this node, which will be disseminated within cluster

### codec
Codec of message payload. Default: msgpack.
* json - https://www.json.org/
* msgpack - https://msgpack.org/

### disseminationFactor
Dissemination factor can be used to fine tune the responsiveness of the cluster.
Greater dissemination factor results to:
* more hosts being notified in every round of dissemination
* lower convergence time of cluster membership
* more/bigger network packets being sent

and vice versa.

### interval
Number of milliseconds between failure detections, also known as the protocol
interval. Every X milliseconds, nodes will ping a member of the SWIM network to
check its liveness with Time-Bounded Strong Completeness as described in the
[paper](http://www.cs.cornell.edu/~asdas/research/dsn02-SWIM.pdf).

### joinTimeout
Number of milliseconds before emitting a JoinTimeout error. The node will still
run as a base node separate from the network.

### pingTimout
Number of milliseconds before sending ping-req messages to the unresponsive node.

### pingReqTimout
Number of milliseconds elapsed from sending ping-req message before marking the
unresponsive node suspicious.

### pingReqGroupSize
Number of hosts to send ping-req messages to for pinging unresponsive nodes
indirectly to reduce false positives.

### suspectTimeout
Number of milliseconds before considering a suspect node faulty.

### udp
UDP Option.
* udp.maxDgramSize - Max size of UDP datagram. If bigger than what the network supports,
messages might be chunked into multiple packets and discarded at receiver end.

### preferCurrentMeta
If set to true, current metadata of local node, instead of the copy of metadata of local node in cluster membership, will be used during conflict resolution. Defaut: false.


================================================
FILE: index.js
================================================
'use strict';
module.exports = require('./lib/swim');


================================================
FILE: lib/codec.js
================================================
'use strict';
var assert = require('assert');
var msgpack = require('msgpack');

function Codec(opts) {
    opts = opts || {};

    this.codec = opts.codec || Codec.Default.codec;

    assert.strictEqual(['json', 'msgpack'].indexOf(this.codec) !== -1, true, 'unsupported codec');

    if (this.codec === 'json') {
        this.encode = function encode(obj) {
            return new Buffer(JSON.stringify(obj));
        };
        this.decode = function decode(buffer) {
            return JSON.parse(buffer.toString());
        };
    } else {
        this.encode = msgpack.pack.bind(msgpack);
        this.decode = msgpack.unpack.bind(msgpack);
    }
}

Codec.Default = {
    codec: 'msgpack'
};

module.exports = Codec;


================================================
FILE: lib/disseminator.js
================================================
'use strict';
var debug = require('debug');
var clone = require('clone');
var events = require('events');
var util = require('util');

var Membership = require('./membership');
var MessageType = require('./message-type');
var Net = require('./net');

function Disseminator(opts) {
    this.swim = opts.swim;

    this.disseminationFactor = opts.disseminationFactor || Disseminator.Default.disseminationFactor;
    this.disseminationFormula =  opts.disseminationFormula || Disseminator.Default.disseminationFormula;
    this.disseminationLimit = Disseminator.Default.disseminationLimit;

    this.updateListener = this.onUpdate.bind(this);
    this.changeListener = this.onChange.bind(this);

    this.attemptsToUpdates = Object.create(null);
    this.hostToAttempts = Object.create(null);
    this.debug = debug('swim:disseminator').bind(undefined, opts.debugIdentifier);
}

util.inherits(Disseminator, events.EventEmitter);

Disseminator.prototype.start = function start() {
    this.swim.membership.on(Membership.EventType.Change, this.changeListener);
    this.swim.membership.on(Membership.EventType.Update, this.updateListener);
    this.updateDisseminationLimit();
};

Disseminator.prototype.stop = function stop() {
    this.swim.membership.removeListener(Membership.EventType.Change, this.changeListener);
    this.swim.membership.removeListener(Membership.EventType.Update, this.updateListener);
};

Disseminator.prototype.onChange = function onChange() {
    this.updateDisseminationLimit();
};

Disseminator.prototype.updateDisseminationLimit = function updateDisseminationLimit() {
    var self = this;

    self.disseminationLimit = self.disseminationFormula(self.disseminationFactor, self.swim.membership.size(true));

    Object.keys(self.attemptsToUpdates).forEach(function removeAttempts(attempts) {
        if (attempts >= self.disseminationLimit) {
            Object.keys(self.attemptsToUpdates[attempts]).forEach(function removeUpdate(host) {
                delete self.hostToAttempts[host];
            });

            delete self.attemptsToUpdates[attempts];
        }
    });
};

Disseminator.prototype.onUpdate = function onUpdate(data) {
    var update = clone(data);

    update.attempts = 0;

    this.removeUpdate(update);
    this.addUpdate(update);
};

Disseminator.prototype.addUpdate = function addUpdate(update) {
    if (update.attempts >= this.disseminationLimit) {
        return;
    }

    if (!this.attemptsToUpdates[update.attempts]) {
        this.attemptsToUpdates[update.attempts] = Object.create(null);
    }

    this.attemptsToUpdates[update.attempts][update.host] = update;
    this.hostToAttempts[update.host] = update.attempts;
};

Disseminator.prototype.removeUpdate = function removeUpdate(update) {
    if (this.hostToAttempts[update.host] >= 0) {
        delete this.attemptsToUpdates[this.hostToAttempts[update.host]][update.host];
    }

    delete this.hostToAttempts[update.host];
};

Disseminator.prototype.getUpdatesUpTo = function getUpdatesUpTo(bytesAvailable) {
    var self = this;
    var buffers = [];
    var updates = [];
    var hostToUpdates;
    var attempts;
    var buffer;
    var update;

    for (attempts = 0; attempts < self.disseminationLimit; attempts++) {
        if (bytesAvailable <= Net.MessageTypeSize) {
            break;
        }

        hostToUpdates = self.attemptsToUpdates[attempts];

        if (hostToUpdates) {
            /* jshint loopfunc: true */
            Object.keys(hostToUpdates).forEach(function disseminateUpdateOf(host) {
                if (bytesAvailable <= Net.MessageTypeSize) {
                    return;
                }

                update = hostToUpdates[host];
                buffer = self.serializeUpdate(update);

                if (buffer.length + Net.LengthSize <= bytesAvailable) {
                    buffers.push(buffer);
                    updates.push(update);
                    self.removeUpdate(update);
                    bytesAvailable -= buffer.length + Net.LengthSize;
                }
            });
            /* jshint loopfunc: false */
        }
    }

    updates.forEach(function addBack(update) {
        update.attempts += 1;
        self.addUpdate(update);
    });

    return buffers;
};

Disseminator.prototype.serializeUpdate = function serializeUpdate(update) {
    var header = new Buffer(Net.MessageTypeSize);

    Net.WriteMessageType.call(header, MessageType.Update, 0);

    return Buffer.concat([header, this.swim.codec.encode({
        meta: update.meta,
        host: update.host,
        state: update.state,
        incarnation: update.incarnation
    })]);
};

Disseminator.Default = {
    disseminationFactor: 15,
    disseminationLimit: 3,
    disseminationFormula: function disseminationFormula(factor, size) {
        return Math.ceil(factor * Math.log(size + 1) / Math.log(10));
    }
};

module.exports = Disseminator;


================================================
FILE: lib/error.js
================================================
'use strict';
var util = require('util');

function JoinFailedError(meta) {
    Error.captureStackTrace(this, this.constructor);

    Object.defineProperty(this, 'name', {
        value: this.constructor.name
    });

    if (meta) {
        this.meta = meta;
    }
}

function InvalidStateError(meta) {
    Error.captureStackTrace(this, this.constructor);

    Object.defineProperty(this, 'name', {
        value: this.constructor.name
    });

    if (meta) {
        this.meta = meta;
    }
}

function ListenFailedError(meta) {
    Error.captureStackTrace(this, this.constructor);

    Object.defineProperty(this, 'name', {
        value: this.constructor.name
    });

    if (meta) {
        this.meta = meta;
    }
}

util.inherits(JoinFailedError, Error);
util.inherits(InvalidStateError, Error);
util.inherits(ListenFailedError, Error);

module.exports = {
    JoinFailedError: JoinFailedError,
    InvalidStateError: InvalidStateError,
    ListenFailedError: ListenFailedError
};


================================================
FILE: lib/failure-detector.js
================================================
'use strict';
var debug = require('debug');
var events = require('events');
var util = require('util');

var MessageType = require('./message-type');
var Net = require('./net');

function FailureDetector(opts) {
    this.swim = opts.swim;
    this.interval = opts.interval || FailureDetector.Default.interval;
    this.pingTimeout = opts.pingTimeout || FailureDetector.Default.pingTimeout;
    this.pingReqTimeout = opts.pingReqTimeout || FailureDetector.Default.pingReqTimeout;
    this.pingReqGroupSize = opts.pingReqGroupSize || FailureDetector.Default.pingReqGroupSize;

    this.seq = 0;
    this.pingListener = this.onPing.bind(this);
    this.pingReqListener = this.onPingReq.bind(this);
    this.ackListener = this.onAck.bind(this);

    this.tickHandle = undefined;
    this.seqToTimeout = Object.create(null);
    this.seqToCallback = Object.create(null);
    this.debug = debug('swim:failure-detector').bind(undefined, opts.debugIdentifier);
}

util.inherits(FailureDetector, events.EventEmitter);

FailureDetector.prototype.start = function start() {
    this.swim.net.on(Net.EventType.Ping, this.pingListener);
    this.swim.net.on(Net.EventType.PingReq, this.pingReqListener);
    this.swim.net.on(Net.EventType.Ack, this.ackListener);
    this.tick();
};

FailureDetector.prototype.stop = function stop() {
    var self = this;

    clearInterval(self.tickHandle);
    self.tickHandle = undefined;

    self.swim.net.removeListener(Net.EventType.Ping, self.pingListener);
    self.swim.net.removeListener(Net.EventType.PingReq, self.pingReqListener);
    self.swim.net.removeListener(Net.EventType.Ack, self.ackListener);

    Object.keys(self.seqToTimeout).forEach(function clearTimeoutWithDeletion(seq) {
        clearTimeout(self.seqToTimeout[seq]);
        delete self.seqToTimeout[seq];
    });

    Object.keys(self.seqToCallback).forEach(function clearCallback(seq) {
        delete self.seqToCallback[seq];
    });
};

FailureDetector.prototype.tick = function tick() {
    setImmediate(this.ping.bind(this));
    this.tickHandle = setInterval(this.ping.bind(this), this.interval);
};

FailureDetector.prototype.ping = function ping() {
    this.pingMember(this.swim.membership.next());
};

FailureDetector.prototype.pingMember = function pingMember(member) {
    var self = this;
    var seq = self.seq;

    if (!member) {
        return;
    }

    self.seq += 1;

    self.seqToTimeout[seq] = setTimeout(function receiveTimeout() {
        self.clearSeq(seq);
        self.pingReq(member);
    }, self.pingTimeout);

    self.swim.net.sendMessage({
        type: MessageType.Ping,
        data: {
            seq: seq
        }
    }, member.host);
};

FailureDetector.prototype.pingReq = function pingReq(member) {
    var self = this;
    var relayMembers = self.swim.membership.random(self.pingReqGroupSize);
    var timeout;

    if (relayMembers.length === 0) {
        return;
    }

    timeout = setTimeout(function pingReqTimeout() {
        self.emit(FailureDetector.EventType.Suspect, member);
    }, self.pingReqTimeout);

    relayMembers.forEach(function pingThrough(relayMember) {
        self.pingReqThroughMember(member, relayMember, function pingReqThroughMemberCallback() {
            clearTimeout(timeout);
        });
    });
};

FailureDetector.prototype.pingReqThroughMember = function pingReqThroughMember(member, relayMember, callback) {
    var self = this;
    var seq = self.seq;

    self.seq += 1;

    self.seqToTimeout[seq] = setTimeout(function receiveTimeout() {
        self.clearSeq(seq);
    }, self.pingReqTimeout);

    self.seqToCallback[seq] = function pingReqAckReceiveCallback() {
        self.clearSeq(seq);
        callback.apply(undefined, arguments);
    };

    self.swim.net.sendMessage({
        type: MessageType.PingReq,
        data: {
            seq: seq,
            destination: member.host
        }
    }, relayMember.host);
};

FailureDetector.prototype.onPing = function onPing(data, host) {
    this.swim.net.sendMessage({
        type: MessageType.Ack,
        data: {
            seq: data.seq
        }
    }, host);
};

FailureDetector.prototype.onPingReq = function onPingReq(data, host) {
    var self = this;
    var seq = self.seq;

    self.seq += 1;

    self.seqToTimeout[seq] = setTimeout(function receiveTimeout() {
        self.clearSeq(seq);
    }, self.pingTimeout);

    self.seqToCallback[seq] = function pingAckReceiveCallback() {
        self.clearSeq(seq);
        self.swim.net.sendMessage({
            type: MessageType.Ack,
            data: {
                seq: data.seq
            }
        }, host);
    };

    self.swim.net.sendMessage({
        type: MessageType.Ping,
        data: {
            seq: seq
        }
    }, data.destination);
};

FailureDetector.prototype.onAck = function onAck(data) {
    var callback = this.seqToCallback[data.seq];

    if (callback) {
        process.nextTick(callback);
    }

    this.clearSeq(data.seq);
};

FailureDetector.prototype.clearSeq = function clearSeq(seq) {
    clearTimeout(this.seqToTimeout[seq]);
    delete this.seqToCallback[seq];
    delete this.seqToTimeout[seq];
};

FailureDetector.Default = {
    interval: 20,
    pingTimeout: 4,
    pingReqTimeout: 12,
    pingReqGroupSize: 3
};

FailureDetector.EventType = {
    Suspect: 'suspect'
};

module.exports = FailureDetector;


================================================
FILE: lib/member.js
================================================
'use strict';
var clone = require('clone');

function Member(opts) {
    this.meta = opts.meta || undefined;
    this.host = opts.host;
    this.state = opts.state || Member.State.Alive;
    this.incarnation = opts.incarnation || 0;
}

Member.prototype.data = function data() {
    return clone({
        meta: this.meta,
        host: this.host,
        state: this.state,
        incarnation: this.incarnation
    });
};

Member.prototype.incarnate = function incarnate(data, force, preferCurrentMeta) {
    if (!data) {
        this.incarnation += 1;
        return true;
    }

    if (data.incarnation > this.incarnation) {
        if (!preferCurrentMeta) {
            this.meta = data.meta;
        }
        this.incarnation = data.incarnation + 1;
        return true;
    }

    if (data.incarnation === this.incarnation && force) {
        this.incarnation = data.incarnation + 1;
        return true;
    }

    return false;
};

Member.State = {
    Alive: 0,
    Suspect: 1,
    Faulty: 2
};

module.exports = Member;


================================================
FILE: lib/membership.js
================================================
'use strict';
var debug = require('debug');
var events = require('events');
var farmhash = require('farmhash');
var util = require('util');

var FailureDetector = require('./failure-detector');
var Member = require('./member');
var MessageType = require('./message-type');
var Net = require('./net');

function Membership(opts) {
    this.swim = opts.swim;
    this.local = new Member(opts.local);
    this.suspectTimeout = opts.suspectTimeout || Membership.Default.suspectTimeout;
    this.preferCurrentMeta = opts.preferCurrentMeta || Membership.Default.preferCurrentMeta;

    this.ackListener = this.onAck.bind(this);
    this.updateListener = this.onUpdate.bind(this);
    this.suspectListener = this.onSuspect.bind(this);
    this.syncListener = this.onSync.bind(this);

    this.hostToMember = Object.create(null);
    this.hostToIterable = Object.create(null);
    this.hostToFaulty = Object.create(null);
    this.hostToSuspectTimeout = Object.create(null);
    this.debug = debug('swim:membership').bind(undefined, opts.debugIdentifier);
}

util.inherits(Membership, events.EventEmitter);

Membership.prototype.start = function start() {
    var self = this;

    self.swim.failureDetector.on(FailureDetector.EventType.Suspect, self.suspectListener);
    self.swim.net.on(Net.EventType.Ack, self.ackListener);
    self.swim.net.on(Net.EventType.Sync, self.syncListener);
    self.swim.net.on(Net.EventType.Update, self.updateListener);

    Object.keys(self.hostToSuspectTimeout).forEach(function resumeSuspect(host) {
        self.onSuspect(self.get(host));
    });
};

Membership.prototype.stop = function stop() {
    var self = this;

    self.swim.failureDetector.removeListener(FailureDetector.EventType.Suspect, self.suspectListener);
    self.swim.net.removeListener(Net.EventType.Ack, self.ackListener);
    self.swim.net.removeListener(Net.EventType.Sync, self.syncListener);
    self.swim.net.removeListener(Net.EventType.Update, self.updateListener);

    Object.keys(self.hostToSuspectTimeout).forEach(function clearTimeoutWithoutDeletion(host) {
        clearTimeout(self.hostToSuspectTimeout[host]);
    });
};

Membership.prototype.onAck = function onAck(data, host) {
    if (this.hostToMember[host] && this.hostToMember[host].state === Member.State.Suspect) {
        this.swim.net.sendMessage({
            type: MessageType.Update,
            data: this.hostToMember[host].data()
        }, host);
    }
};

Membership.prototype.onSuspect = function onSuspect(member) {
    var self = this;
    var data;

    member = new Member(member.data());
    member.state = Member.State.Suspect;
    data = member.data();

    clearTimeout(self.hostToSuspectTimeout[data.host]);
    delete self.hostToSuspectTimeout[data.host];

    self.hostToSuspectTimeout[data.host] = setTimeout(function setFaulty() {
        delete self.hostToSuspectTimeout[data.host];

        data.state = Member.State.Faulty;

        self.onUpdate(data);
    }, self.suspectTimeout);

    self.onUpdate(member.data());
};

Membership.prototype.onSync = function onSync(data) {
    var host = data.host;

    this.updateAlive(data);

    this.swim.net.sendMessages(this.all(true, true).map(function toMessage(data) {
        return {
            type: MessageType.Update,
            data: data
        };
    }), host);
};

Membership.prototype.sync = function sync(hosts) {
    var self = this;
    var messages = [{
        type: MessageType.Sync,
        data: self.local.data()
    }];

    this.all(false, true).forEach(function addMessage(data) {
        messages.push({
            type: MessageType.Update,
            data: data
        });
    });

    hosts.forEach(function sendToHost(host) {
        self.swim.net.sendMessages(messages, host);
    });
};

Membership.prototype.onUpdate = function onUpdate(data) {
    this.debug('received update', data);

    switch (data.state) {
        case Member.State.Alive:
            this.updateAlive(data);
            break;
        case Member.State.Suspect:
            this.updateSuspect(data);
            break;
        case Member.State.Faulty:
            this.updateFaulty(data);
            break;
    }
};

Membership.prototype.updateAlive = function updateAlive(data) {
    if (this.isLocal(data.host)) {
        if (this.local.incarnate(data, false, this.preferCurrentMeta)) {
            this.emit(Membership.EventType.Update, this.local.data());
        } else {
            this.emit(Membership.EventType.Drop, data);
        }
        return;
    }

    if (this.hostToFaulty[data.host] && this.hostToFaulty[data.host].incarnation >= data.incarnation) {
        this.emit(Membership.EventType.Drop, data);
        return;
    }

    if (!this.hostToMember[data.host] ||
        data.incarnation > this.hostToMember[data.host].incarnation) {

        clearTimeout(this.hostToSuspectTimeout[data.host]);
        delete this.hostToSuspectTimeout[data.host];
        delete this.hostToFaulty[data.host];

        if (!this.hostToMember[data.host]) {
            this.hostToMember[data.host] = new Member(data);
            this.hostToIterable[data.host] = this.hostToMember[data.host];
            this.emit(Membership.EventType.Change, this.hostToMember[data.host].data());
        } else {
            this.hostToMember[data.host] = new Member(data);
        }

        this.emit(Membership.EventType.Update, this.hostToMember[data.host].data());
    } else {
        this.emit(Membership.EventType.Drop, data);
    }
};

Membership.prototype.updateSuspect = function updateSuspect(data) {
    if (this.isLocal(data.host)) {
        this.emit(Membership.EventType.Drop, data);
        this.local.incarnate(data, true, this.preferCurrentMeta);
        this.emit(Membership.EventType.Update, this.local.data());
        return;
    }

    if (this.hostToFaulty[data.host] && this.hostToFaulty[data.host].incarnation >= data.incarnation) {
        this.emit(Membership.EventType.Drop, data);
        return;
    }

    if (!this.hostToMember[data.host] ||
        data.incarnation > this.hostToMember[data.host].incarnation ||
        data.incarnation === this.hostToMember[data.host].incarnation &&
        this.hostToMember[data.host].state === Member.State.Alive) {

        delete this.hostToFaulty[data.host];

        if (!this.hostToMember[data.host]) {
            this.hostToMember[data.host] = new Member(data);
            this.hostToIterable[data.host] = this.hostToMember[data.host];
            this.emit(Membership.EventType.Change, this.hostToMember[data.host].data());
        } else {
            this.hostToMember[data.host] = new Member(data);
        }

        this.emit(Membership.EventType.Update, this.hostToMember[data.host].data());
    } else {
        this.emit(Membership.EventType.Drop, data);
    }
};

Membership.prototype.updateFaulty = function updateFaulty(data) {
    if (this.isLocal(data.host)) {
        this.emit(Membership.EventType.Drop, data);
        this.local.incarnate(data, true, this.preferCurrentMeta);
        this.emit(Membership.EventType.Update, this.local.data());
        return;
    }

    if (this.hostToMember[data.host] &&
        data.incarnation >= this.hostToMember[data.host].incarnation) {

        this.hostToFaulty[data.host] = new Member(data);
        delete this.hostToMember[data.host];
        delete this.hostToIterable[data.host];
        this.emit(Membership.EventType.Change, data);

        this.emit(Membership.EventType.Update, data);
    } else {
        this.emit(Membership.EventType.Drop, data);
    }
};

Membership.prototype.next = function next() {
    var hosts = Object.keys(this.hostToIterable);
    var host;
    var member;

    if (hosts.length === 0) {
        this.shuffle();
        hosts = Object.keys(this.hostToIterable);
    }

    host = hosts[Math.floor(Math.random() * hosts.length)];
    member = this.hostToIterable[host];
    delete this.hostToIterable[host];

    return member;
};

Membership.prototype.random = function random(n) {
    var hosts = Object.keys(this.hostToMember);
    var selected = [];
    var index;
    var i;

    for (i = 0; i < n && i < hosts.length; i++) {
        index = i + Math.floor(Math.random() * (hosts.length - i));
        selected.push(this.hostToMember[hosts[index]]);
        hosts[index] = hosts[i];
    }

    return selected;
};

Membership.prototype.shuffle = function shuffle() {
    var self = this;

    self.hostToIterable = Object.create(null);

    Object.keys(self.hostToMember).forEach(function addToIterable(host) {
        self.hostToIterable[host] = self.hostToMember[host];
    });
};

Membership.prototype.get = function get(host) {
    return this.hostToMember[host];
};

Membership.prototype.size = function size(hasLocal) {
    return Object.keys(this.hostToMember).length + (hasLocal ? 1 : 0);
};

Membership.prototype.all = function all(hasLocal, hasFaulty) {
    var self = this;
    var results = Object.keys(self.hostToMember).map(function toData(host) {
        return self.hostToMember[host].data();
    });

    if (hasLocal) {
        results.push(self.local.data());
    }

    if (hasFaulty) {
        Object.keys(self.hostToFaulty).forEach(function toData(host) {
            results.push(self.hostToFaulty[host].data());
        });
    }

    return results;
};

Membership.prototype.checksum = function checksum() {
    var self = this;
    var strs = self.all(true).sort(function compare(a, b) {
        return parseInt(a.host.split(':')[1]) - parseInt(b.host.split(':')[1]);
    }).map(function toString(member) {
        return member.host + member.state + member.incarnation;
    });

    return farmhash.hash64(strs.join('-'));
};

Membership.prototype.isLocal = function isLocal(host) {
    return host === this.local.host;
};

Membership.prototype.localhost = function localhost() {
    return this.local.host;
};

Membership.prototype.updateMeta = function updateMeta(meta) {
    this.local.meta = meta;
    this.local.incarnate();
    this.emit(Membership.EventType.Update, this.local.data());
};

Membership.Default = {
    suspectTimeout: 10,
    preferCurrentMeta: false
};

Membership.EventType = {
    Change: 'change',
    Drop: 'drop',
    Update: 'update'
};

module.exports = Membership;


================================================
FILE: lib/message-type.js
================================================
'use strict';

module.exports = {
    Compound: 0,
    Compressed: 1,
    Encrypted: 2,
    Ping: 10,
    PingReq: 11,
    Sync: 12,
    Ack: 13,
    Update: 14
};


================================================
FILE: lib/net.js
================================================
'use strict';
var debug = require('debug');
var dgram = require('dgram');
var events = require('events');
var util = require('util');

var MessageType = require('./message-type');

function Net(opts) {
    this.swim = opts.swim;
    this.udp = {
        port: opts.udp.port,
        type: opts.udp.type || Net.Default.udp.type,
        maxDgramSize: opts.udp.maxDgramSize || Net.Default.udp.maxDgramSize
    };

    this.errorListener = this.emit.bind(this, Net.EventType.Error);
    this.listeningListener = this.emit.bind(this, Net.EventType.Listening);
    this.messageListener = this.onNetMessage.bind(this);

    this.udpSocket = dgram.createSocket(this.udp.type);
    this.debug = debug('swim:net').bind(undefined, opts.debugIdentifier);
}

util.inherits(Net, events.EventEmitter);

Net.prototype.listen = function listen(callback) {
    this.udpSocket.on('error', this.errorListener);
    this.udpSocket.on('listening', this.listeningListener);
    this.udpSocket.on('message', this.messageListener);
    this.udpSocket.bind(this.udp.port, callback);
};

Net.prototype.close = function close() {
    this.udpSocket.removeListener('error', this.errorListener);
    this.udpSocket.removeListener('listening', this.listeningListener);
    this.udpSocket.removeListener('message', this.messageListener);
    this.udpSocket.close();
    this.udpSocket = dgram.createSocket(this.udp.type);
};

Net.prototype.onNetMessage = function onNetMessage(buffer, rinfo) {
    this.debug('received buffer', {
        from: formatRinfo(rinfo),
        length: buffer.length
    });

    this.onMessage(buffer, rinfo);
};

Net.prototype.onMessage = function onMessage(buffer, rinfo) {
    if (buffer.length < Net.MessageTypeSize) {
        return this.onUnknown(buffer, rinfo);
    }

    var messageType = Net.ReadMessageType.call(buffer, 0);

    switch (messageType) {
        case MessageType.Compound:
            this.onCompound(buffer.slice(Net.MessageTypeSize), rinfo);
            break;
        case MessageType.Ping:
            this.onPing(buffer.slice(Net.MessageTypeSize), rinfo);
            break;
        case MessageType.PingReq:
            this.onPingReq(buffer.slice(Net.MessageTypeSize), rinfo);
            break;
        case MessageType.Sync:
            this.onSync(buffer.slice(Net.MessageTypeSize), rinfo);
            break;
        case MessageType.Ack:
            this.onAck(buffer.slice(Net.MessageTypeSize), rinfo);
            break;
        case MessageType.Update:
            this.onUpdate(buffer.slice(Net.MessageTypeSize), rinfo);
            break;
        default:
            this.onUnknown(buffer, rinfo);
    }
};

Net.prototype.onCompound = function onCompound(buffer, rinfo) {
    this.debug('received compound message');

    if (buffer.length < Net.LengthSize) {
        this.debug('cannot parse number of messages in compound message', {
            from: formatRinfo(rinfo),
            length: buffer.length,
            buffer: buffer.toString()
        });
        return;
    }

    var numberOfMessages = Net.ReadLength.call(buffer, 0);
    var readIndex = Net.LengthSize;
    var length;
    var i;

    for (i = 0; i < numberOfMessages; i++) {
        if (buffer.length - readIndex < Net.LengthSize) {
            this.debug('cannot parse length of message in compound message', {
                readIndex: readIndex,
                from: formatRinfo(rinfo),
                length: buffer.length,
                buffer: buffer.toString()
            });
            break;
        }

        length = Net.ReadLength.call(buffer, readIndex);
        readIndex += Net.LengthSize;
        this.onMessage(buffer.slice(readIndex, readIndex + length), rinfo);
        readIndex += length;
    }
};

Net.prototype.onPing = function onPing(buffer, rinfo) {
    var data;

    try {
        data = this.swim.codec.decode(buffer);
    } catch (e) {
        this.debug('failed to decode data', {
            stack: e.stack,
            from: formatRinfo(rinfo),
            length: buffer.length,
            buffer: buffer.toString()
        });
        return;
    }

    this.debug('received ping message', {
        from: formatRinfo(rinfo),
        data: data
    });

    this.emit(Net.EventType.Ping, data, formatRinfo(rinfo));
};

Net.prototype.onPingReq = function onPingReq(buffer, rinfo) {
    var data;

    try {
        data = this.swim.codec.decode(buffer);
    } catch (e) {
        this.debug('failed to decode data', {
            stack: e.stack,
            from: formatRinfo(rinfo),
            length: buffer.length,
            buffer: buffer.toString()
        });
        return;
    }

    this.debug('received pingreq message', {
        from: formatRinfo(rinfo),
        data: data
    });

    this.emit(Net.EventType.PingReq, data, formatRinfo(rinfo));
};

Net.prototype.onSync = function onSync(buffer, rinfo) {
    var data;

    try {
        data = this.swim.codec.decode(buffer);
    } catch (e) {
        this.debug('failed to decode data', {
            stack: e.stack,
            from: formatRinfo(rinfo),
            length: buffer.length,
            buffer: buffer.toString()
        });
        return;
    }

    this.debug('received sync message', {
        from: formatRinfo(rinfo),
        data: data
    });

    this.emit(Net.EventType.Sync, data, formatRinfo(rinfo));
};

Net.prototype.onAck = function onAck(buffer, rinfo) {
    var data;

    try {
        data = this.swim.codec.decode(buffer);
    } catch (e) {
        this.debug('failed to decode data', {
            stack: e.stack,
            from: formatRinfo(rinfo),
            length: buffer.length,
            buffer: buffer.toString()
        });
        return;
    }

    this.debug('received ack message', {
        from: formatRinfo(rinfo),
        data: data
    });

    this.emit(Net.EventType.Ack, data, formatRinfo(rinfo));
};

Net.prototype.onUpdate = function onUpdate(buffer, rinfo) {
    var data;

    try {
        data = this.swim.codec.decode(buffer);
    } catch (e) {
        this.debug('failed to decode data', {
            stack: e.stack,
            from: formatRinfo(rinfo),
            length: buffer.length,
            buffer: buffer.toString()
        });
        return;
    }

    this.debug('received update message', {
        from: formatRinfo(rinfo),
        data: data
    });

    this.emit(Net.EventType.Update, data, formatRinfo(rinfo));
};

Net.prototype.onUnknown = function onUnknown(buffer, rinfo) {
    this.debug('received unknown buffer', {
        from: formatRinfo(rinfo),
        buffer: buffer.toString()
    });

    this.emit(Net.EventType.Unknown, buffer, formatRinfo(rinfo));
};

Net.prototype.sendMessages = function sendMessages(messages, host) {
    var self = this;
    var bytesAvailable = self.udp.maxDgramSize - Net.MessageTypeSize - Net.LengthSize;
    var buffers = [];
    var buffer;
    var message;
    var i;

    for (i = 0; i < messages.length; i++) {
        message = messages[i];
        buffer = new Buffer(Net.MessageTypeSize);
        Net.WriteMessageType.call(buffer, message.type, 0);
        buffer = Buffer.concat([buffer, self.swim.codec.encode(message.data)]);

        if (buffer.length + Net.LengthSize < bytesAvailable) {
            buffers.push(buffer);
            bytesAvailable -= buffer.length + Net.LengthSize;
        } else if (buffers.length === 0) {
            this.debug('oversized message', {
                length: buffer.length,
                message: message
            });
        } else {
            self.sendBuffer(self.makeCompoundMessage(buffers), host);
            bytesAvailable = self.udp.maxDgramSize - Net.LengthSize;
            buffers = [];
            i--;
        }
    }

    if (buffers.length > 0) {
        self.sendBuffer(self.makeCompoundMessage(buffers), host);
    }
};

Net.prototype.sendMessage = function sendMessage(message, host) {
    var header = new Buffer(Net.MessageTypeSize);

    Net.WriteMessageType.call(header, message.type, 0);

    if (message.data) {
        this.piggybackAndSend(Buffer.concat([header, this.swim.codec.encode(message.data)]), host);
    } else {
        this.piggybackAndSend(header, host);
    }
};

Net.prototype.piggybackAndSend = function piggybackAndSend(buffer, host) {
    var bytesAvailable = this.udp.maxDgramSize - Net.MessageTypeSize - Net.LengthSize * 2 - buffer.length;
    var buffers = this.swim.disseminator.getUpdatesUpTo(bytesAvailable);

    if (buffers.length === 0) {
        return this.sendBuffer(buffer, host);
    }

    buffers.unshift(buffer);

    this.sendBuffer(this.makeCompoundMessage(buffers), host);
};

Net.prototype.makeCompoundMessage = function makeCompoundMessage(buffers) {
    var header = new Buffer(Net.MessageTypeSize + Net.LengthSize);

    Net.WriteMessageType.call(header, MessageType.Compound, 0);
    Net.WriteLength.call(header, buffers.length, Net.MessageTypeSize);

    buffers = buffers.map(function toBuffer(buffer) {
        var header = new Buffer(Net.LengthSize);

        Net.WriteLength.call(header, buffer.length, 0);

        return Buffer.concat([header, buffer]);
    });

    buffers.unshift(header);

    return Buffer.concat(buffers);
};

Net.prototype.sendBuffer = function sendBuffer(buffer, host) {
    var address = host.split(':')[0];
    var port = host.split(':')[1];

    this.udpSocket.send(buffer, 0, buffer.length, port, address);
    this.debug('sent buffer', {
        to: host,
        length: buffer.length
    });
};

Net.MessageTypeSize = 1;
Net.LengthSize = 2;
Net.ReadMessageType = Buffer.prototype.readUInt8;
Net.ReadLength = Buffer.prototype.readUInt16LE;
Net.WriteMessageType = Buffer.prototype.writeUInt8;
Net.WriteLength = Buffer.prototype.writeUInt16LE;

Net.Default = {
    udp: {
        type: 'udp4',
        maxDgramSize: 512
    }
};

Net.EventType = {
    Error: 'error',
    Listening: 'listening',
    Ping: 'ping',
    PingReq: 'pingreq',
    Sync: 'sync',
    Ack: 'ack',
    Update: 'update',
    Unknown: 'unknown'
};

function formatRinfo(rinfo) {
    return rinfo.address + ':' + rinfo.port;
}

module.exports = Net;


================================================
FILE: lib/swim.js
================================================
'use strict';
var debug = require('debug');
var events = require('events');
var util = require('util');

var Codec = require('./codec');
var Disseminator = require('./disseminator');
var FailureDetector = require('./failure-detector');
var Membership = require('./membership');
var Net = require('./net');

var JoinFailedError = require('./error').JoinFailedError;
var InvalidStateError = require('./error').InvalidStateError;
var ListenFailedError = require('./error').ListenFailedError;

function Swim(opts) {
    this.opts = opts;

    this.codec = new Codec({
        codec: opts.codec,
        swim: this,
        debugIdentifier: opts.local.host
    });
    this.disseminator = new Disseminator({
        disseminationFactor: opts.disseminationFactor,
        swim: this,
        debugIdentifier: opts.local.host
    });
    this.failureDetector = new FailureDetector({
        interval: opts.interval,
        pingTimeout: opts.pingTimeout,
        pingReqTimeout: opts.pingReqTimeout,
        pingReqGroupSize: opts.pingReqGroupSize,
        swim: this,
        debugIdentifier: opts.local.host
    });
    this.membership = new Membership({
        local: opts.local,
        suspectTimeout: opts.suspectTimeout,
        preferCurrentMeta: opts.preferCurrentMeta,
        swim: this,
        debugIdentifier: opts.local.host
    });
    this.net = new Net({
        udp: {
            port: parseInt(opts.local.host.split(':')[1]),
            type: opts.udp && opts.udp.type,
            maxDgramSize: opts.udp && opts.udp.maxDgramSize
        },
        swim: this,
        debugIdentifier: opts.local.host
    });
    this.state = Swim.State.Stopped;

    this.joinTimeout = opts.joinTimeout || Swim.Default.joinTimeout;
    this.joinCheckInterval = opts.joinCheckInterval || Swim.Default.joinCheckInterval;

    this.changeListener = this.emit.bind(this, Swim.EventType.Change);
    this.updateListener = this.emit.bind(this, Swim.EventType.Update);
    this.debug = debug('swim').bind(undefined, opts.local.host);
}

util.inherits(Swim, events.EventEmitter);

Swim.prototype.bootstrap = function bootstrap(hosts, callback) {
    var self = this;
    var err;

    if (self.state !== Swim.State.Stopped) {
        err = new InvalidStateError({
            current: self.state,
            expected: Swim.State.Stopped
        });

        if (typeof callback === 'function') {
            callback(err);
        } else {
            self.emit(Swim.EventType.Error, err);
        }

        return;
    }

    self.net.listen(function onListen(err) {
        if (err) {
            err = new ListenFailedError({
                host: self.opts.local.host,
                type: self.opts.udp.type
            });

            if (typeof callback === 'function') {
                callback(err);
            } else {
                self.emit(Swim.EventType.Error, err);
            }

            return;
        }

        self.failureDetector.start();
        self.membership.start();
        self.disseminator.start();
        self.membership.on(Membership.EventType.Change, self.changeListener);
        self.membership.on(Membership.EventType.Update, self.updateListener);

        self.state = Swim.State.Started;
        self.join(hosts, callback);
    });
};

Swim.prototype.join = function join(hosts, callback) {
    var self = this;
    var err;

    if (self.state !== Swim.State.Started) {
        err = new InvalidStateError({
            currect: self.state,
            expected: Swim.State.Started
        });

        if (typeof callback === 'function') {
            callback(err);
        } else {
            self.emit(Swim.EventType.Error, err);
        }
    }

    if (!hosts || hosts.length === 0) {
        if (typeof callback === 'function') {
            callback();
        } else {
            self.emit(Swim.EventType.Ready);
        }

        return;
    }

    hosts = hosts.filter(function notLocal(host) {
        return host !== self.opts.local.host;
    });

    self.membership.sync(hosts);

    var checker = null;

    var timeout = setTimeout(function joinTimeout() {
        clearInterval(checker);
        var numberOfHostsResponded = checkJoin();
        if (numberOfHostsResponded === 0) {
            var err = new JoinFailedError({
                local: self.localhost(),
                hosts: hosts,
                numberOfHostsResponded: numberOfHostsResponded,
                timeout: self.joinTimeout
            });
            if (typeof callback === 'function') {
                callback(err);
            } else {
                self.emit(Swim.EventType.Error, err);
            }
        }
    }, self.joinTimeout);

    if (self.joinCheckInterval < self.joinTimeout) {
        checker = setInterval(checkJoin, self.joinCheckInterval);
    }

    function checkJoin() {
        var numberOfHostsResponded = hosts.reduce(function countRespondedHosts(num, host) {
            num += self.membership.get(host) ? 1 : 0;
            return num;
        }, 0);
        if (numberOfHostsResponded >= 1) {
            clearInterval(checker);
            clearTimeout(timeout);
            if (typeof callback === 'function') {
                callback();
            } else {
                self.emit(Swim.EventType.Ready);
            }
        }
        return numberOfHostsResponded;
    }
};

Swim.prototype.leave = function leave() {
    this.membership.removeListener(Membership.EventType.Update, this.updateListener);
    this.membership.removeListener(Membership.EventType.Change, this.changeListener);
    this.disseminator.stop();
    this.membership.stop();
    this.failureDetector.stop();
    this.net.close();
    this.state = Swim.State.Stopped;
};

Swim.prototype.members = function members(hasLocal, hasFaulty) {
    return this.membership.all(hasLocal, hasFaulty);
};

Swim.prototype.checksum = function checksum() {
    return this.membership.checksum();
};

Swim.prototype.localhost = function localhost() {
    return this.membership && this.membership.localhost() || this.opts.local.host;
};

Swim.prototype.whoami = function whoami() {
    return this.localhost();
};

Swim.prototype.updateMeta = function updateMeta(meta) {
    return this.membership.updateMeta(meta);
};

Swim.Default = {
    joinTimeout: 300,
    joinCheckInterval: 50
};

Swim.EventType = {
    Change: 'change',
    Update: 'update',
    Error: 'error',
    Ready: 'ready'
};

Swim.State = {
    Started: 'started',
    Stopped: 'stopped'
};

module.exports = Swim;
module.exports.Error = require('./error');


================================================
FILE: package.json
================================================
{
  "name": "swim",
  "version": "0.6.0",
  "description": "Gossip protocol based on SWIM",
  "main": "index.js",
  "scripts": {
    "cover": "covert test/index -q",
    "lint": "jshint . --exclude-path=./.gitignore && jscs .",
    "test": "npm run lint && node test/index | tap-spec"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mrhooray/swim-js.git"
  },
  "keywords": [
    "gossip",
    "swim",
    "failure",
    "detection",
    "membership"
  ],
  "author": "Rui Hu <code@mrhooray.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mrhooray/swim-js/issues"
  },
  "homepage": "https://github.com/mrhooray/swim-js",
  "devDependencies": {
    "async": "^0.9.0",
    "covert": "^1.0.1",
    "metrics": "^0.1.9",
    "jscs": "^1.10.0",
    "jshint": "^2.6.0",
    "tap-spec": "^2.2.0",
    "tape": "^3.4.0"
  },
  "dependencies": {
    "clone": "^1.0.2",
    "commander": "^2.6.0",
    "debug": "^2.1.1",
    "farmhash": "^2.0.0",
    "msgpack": "^1.0.2"
  }
}


================================================
FILE: test/codec.js
================================================
'use strict';
var test = require('tape');

var Codec = require('../lib/codec');

test('Codec constructs a codec instance', function t(assert) {
    assert.strictEqual(new Codec() instanceof Codec, true);
    assert.end();
});

test('Codec supports json or msgpack', function t(assert) {
    assert.doesNotThrow(Codec.bind({}, {codec: 'json'}));
    assert.doesNotThrow(Codec.bind({}, {codec: 'msgpack'}));
    assert.throws(Codec.bind({}, {codec: 'protobuf'}));
    assert.end();
});

test('Codec can decode data encoded by same codec', function t(assert) {
    var data = {foo: 'bar'};
    var jsonCodec = new Codec({codec: 'json'});
    var msgpackCodec = new Codec({codec: 'msgpack'});

    assert.deepEqual(jsonCodec.decode(jsonCodec.encode(data)), data);
    assert.deepEqual(msgpackCodec.decode(msgpackCodec.encode(data)), data);
    assert.end();
});


================================================
FILE: test/disseminator.js
================================================
'use strict';
var events = require('events');
var test = require('tape');

var Codec = require('../lib/codec');
var Disseminator = require('../lib/disseminator');
var Membership = require('../lib/membership');
var MessageType = require('../lib/message-type');
var Net = require('../lib/net');

var codec = new Codec();

test('Disseminator honors bytesAvailable, dissemination limit and priority', function t(assert) {
    var membership = new events.EventEmitter();
    var disseminator = new Disseminator({
        swim: {
            codec: codec,
            membership: membership
        }
    });
    var minDgramSize = Math.pow(2, 6);
    var maxDgramSize = Math.pow(2, 9);
    var numberOfUpdates = Math.ceil(Math.random() * 5);
    var numberOfMembers = Math.ceil(Math.random() * 3);
    var numberOfBuffers = 0;
    var hostToCount = Object.create(null);
    var buffers;
    var bytesAvailable;
    var update;
    var length;
    var i;

    membership.size = function size() {
        return numberOfMembers;
    };

    disseminator.start();

    buffers = disseminator.getUpdatesUpTo(Infinity);
    assert.strictEqual(buffers.length, 0);

    for (i = 0; i < numberOfUpdates; i++) {
        membership.emit(Membership.EventType.Update, {
            host: 'localhost:' + i
        });
    }

    bytesAvailable = Math.ceil(Math.random() * (maxDgramSize - minDgramSize)) + minDgramSize;
    buffers = disseminator.getUpdatesUpTo(bytesAvailable);

    while (buffers.length > 0) {
        length = 0;

        /* jshint loopfunc: true */
        buffers.forEach(function verify(buffer) {
            assert.strictEqual(Net.ReadMessageType.call(buffer, 0), MessageType.Update);

            length += buffer.length;
            update = codec.decode(buffer.slice(Net.MessageTypeSize));

            Object.keys(hostToCount).forEach(function verifyPriority(host) {
                assert.strictEqual(hostToCount[update.host] > hostToCount[host], false);
            });

            hostToCount[update.host] = (hostToCount[update.host] || 0) + 1;
        });
        /* jshint loopfunc: false */

        assert.strictEqual(length < bytesAvailable, true);
        numberOfBuffers += buffers.length;

        bytesAvailable = Math.ceil(Math.random() * (maxDgramSize - minDgramSize)) + minDgramSize;
        buffers = disseminator.getUpdatesUpTo(bytesAvailable);
    }

    assert.strictEqual(numberOfBuffers, numberOfUpdates *
        Disseminator.Default.disseminationFormula(Disseminator.Default.disseminationFactor, numberOfMembers));
    disseminator.stop();
    assert.end();
});

test('Disseminator removes over disseminated updates on decrease of dissemination limit', function t(assert) {
    var membership = new events.EventEmitter();
    var disseminator = new Disseminator({
        swim: {
            codec: codec,
            membership: membership
        },
        disseminationFactor: 1,
        disseminationFormula: function disseminationFormula(factor, size) {
            return factor * size;
        }
    });
    var numberOfMembers = 10;
    var buffers;

    membership.size = function size() {
        return numberOfMembers;
    };

    disseminator.start();

    membership.emit(Membership.EventType.Update, {
        host: 'localhost:22'
    });

    buffers = disseminator.getUpdatesUpTo(Infinity);
    assert.strictEqual(buffers.length, 1);

    numberOfMembers = 1;
    membership.emit(Membership.EventType.Change);

    buffers = disseminator.getUpdatesUpTo(Infinity);
    assert.strictEqual(buffers.length, 0);

    disseminator.stop();
    assert.end();
});


================================================
FILE: test/failure-detector.js
================================================
'use strict';
var events = require('events');
var test = require('tape');

var FailureDetector = require('../lib/failure-detector');
var Member = require('../lib/member');
var MessageType = require('../lib/message-type');
var Net = require('../lib/net');

var INTERVAL = 100;
var MAX_TICKS = 3;
var TICKS = Math.ceil(Math.random() * MAX_TICKS);
var TICK_OFFSET = -0.5;
var MEMBER_A = new Member({
    host: 'localhost:1111'
});
var MEMBER_B = new Member({
    host: 'localhost:2222'
});

test('FailureDetector ticks/pings at defined interval', function t(assert) {
    var pingCalled = 0;
    var membership = {
        next: function next() {
            pingCalled++;
        }
    };
    var net = new events.EventEmitter();
    var failureDetector = new FailureDetector({
        swim: {
            membership: membership,
            net: net
        },
        interval: INTERVAL
    });

    failureDetector.start();

    setTimeout(function verify() {
        verifyInternal(failureDetector, assert);
        failureDetector.stop();

        assert.strictEqual(pingCalled, TICKS);
        assert.end();
    }, INTERVAL * (TICKS + TICK_OFFSET));
});

test('FailureDetector does not send PingReq when receives Ack of Ping in time', function t(assert) {
    var pingCalled = 0;
    var membership = {
        next: function next() {
            pingCalled++;
            return MEMBER_A;
        },
        random: function random() {
            assert.fail();
        }
    };
    var net = new events.EventEmitter();
    var failureDetector = new FailureDetector({
        swim: {
            membership: membership,
            net: net
        },
        interval: INTERVAL
    });

    net.sendMessage = function sendMessage(message, host) {
        net.emit(Net.EventType.Ack, {
            seq: message.data.seq
        }, host);
    };

    failureDetector.start();

    setTimeout(function verify() {
        verifyInternal(failureDetector, assert);
        failureDetector.stop();

        assert.strictEqual(pingCalled, TICKS);
        assert.end();
    }, INTERVAL * (TICKS + TICK_OFFSET));
});

test('FailureDetector sends PingReq when does not receive Ack of Ping in time', function t(assert) {
    var pingTimeout = 1;
    var pingReqCalled = 0;
    var membership = {
        next: function next() {
            return MEMBER_A;
        },
        random: function random() {
            pingReqCalled++;
            return [];
        }
    };
    var net = new events.EventEmitter();
    var failureDetector = new FailureDetector({
        swim: {
            membership: membership,
            net: net
        },
        interval: INTERVAL,
        pingTimeout: pingTimeout
    });

    net.sendMessage = function sendMessage() {};

    failureDetector.start();

    setTimeout(function verify() {
        verifyInternal(failureDetector, assert);
        failureDetector.stop();

        assert.strictEqual(pingReqCalled, TICKS);
        assert.end();
    }, INTERVAL * (TICKS + TICK_OFFSET));
});

test('FailureDetector does not emit suspect event when receives Ack of PingReq in time', function t(assert) {
    var pingTimeout = 1;
    var pingReqTimeout = 10;
    var pingReqCalled = 0;
    var membership = {
        next: function next() {
            return MEMBER_A;
        },
        random: function random() {
            pingReqCalled++;
            return [MEMBER_B];
        }
    };
    var net = new events.EventEmitter();
    var failureDetector = new FailureDetector({
        swim: {
            membership: membership,
            net: net
        },
        interval: INTERVAL,
        pingTimeout: pingTimeout,
        pingReqTimeout: pingReqTimeout
    });

    net.sendMessage = function sendMessage(message, host) {
        if (message.type === MessageType.PingReq) {
            net.emit(Net.EventType.Ack, {
                seq: message.data.seq
            }, host);
        }
    };

    failureDetector.on(FailureDetector.EventType.Suspect, function onSuspect() {
        assert.fail();
    });

    failureDetector.start();

    setTimeout(function verify() {
        verifyInternal(failureDetector, assert);
        failureDetector.stop();

        assert.strictEqual(pingReqCalled, TICKS);
        assert.end();
    }, INTERVAL * (TICKS + TICK_OFFSET));
});

test('FailureDetector emits suspect event when does not receive Ack of PingReq in time', function t(assert) {
    var pingTimeout = 1;
    var pingReqTimeout = 1;
    var suspectEmitted = 0;
    var membership = {
        next: function next() {
            return MEMBER_A;
        },
        random: function random() {
            return [MEMBER_B];
        }
    };
    var net = new events.EventEmitter();
    var failureDetector = new FailureDetector({
        swim: {
            membership: membership,
            net: net
        },
        interval: INTERVAL,
        pingTimeout: pingTimeout,
        pingReqTimeout: pingReqTimeout
    });

    net.sendMessage = function sendMessage() {};

    failureDetector.on(FailureDetector.EventType.Suspect, function onSuspect(suspect) {
        suspectEmitted++;
        assert.strictEqual(suspect, MEMBER_A);
    });

    failureDetector.start();

    setTimeout(function verify() {
        verifyInternal(failureDetector, assert);
        failureDetector.stop();

        assert.strictEqual(suspectEmitted, TICKS);
        assert.end();
    }, INTERVAL * (TICKS + TICK_OFFSET));
});

test('FailureDetector sends Ack when receives Ping', function t(assert) {
    var membership = {
        next: function next() {}
    };
    var net = new events.EventEmitter();
    var failureDetector = new FailureDetector({
        swim: {
            membership: membership,
            net: net
        }
    });
    var seq = Math.random();

    net.sendMessage = function sendMessage(message, host) {
        verifyInternal(failureDetector, assert);
        failureDetector.stop();

        assert.strictEqual(message.type, MessageType.Ack);
        assert.strictEqual(message.data.seq, seq);
        assert.strictEqual(host, MEMBER_A.host);
        assert.end();
    };

    failureDetector.start();

    net.emit(Net.EventType.Ping, {
        seq: seq
    }, MEMBER_A.host);
});

test('FailureDetector sends Ping to destination when receives PingReq, ' +
     'then sends Ack to PingReq requester when receives Ack from destination', function t(assert) {
    var membership = {
        next: function next() {}
    };
    var net = new events.EventEmitter();
    var failureDetector = new FailureDetector({
        swim: {
            membership: membership,
            net: net
        }
    });
    var seq = Math.random();
    var sendMessageCalled = 0;

    net.sendMessage = function sendMessage(message, host) {
        switch (sendMessageCalled) {
            case 0:
                sendMessageCalled++;

                assert.strictEqual(message.type, MessageType.Ping);
                assert.strictEqual(host, MEMBER_B.host);

                net.emit(Net.EventType.Ack, {
                    seq: message.data.seq
                }, host);
                break;
            case 1:
                verifyInternal(failureDetector, assert);
                failureDetector.stop();

                assert.strictEqual(message.type, MessageType.Ack);
                assert.strictEqual(host, MEMBER_A.host);

                assert.end();
                break;
        }
    };

    failureDetector.start();

    net.emit(Net.EventType.PingReq, {
        seq: seq,
        destination: MEMBER_B.host
    }, MEMBER_A.host);
});

test('FailureDetector does not send Ack to PingReq requester ' +
     'when does not receive Ack from destination', function t(assert) {
    var pingTimeout = 1;
    var membership = {
        next: function next() {}
    };
    var net = new events.EventEmitter();
    var failureDetector = new FailureDetector({
        swim: {
            membership: membership,
            net: net
        },
        interval: INTERVAL,
        pingTimeout: pingTimeout
    });
    var seq = Math.random();
    var sendMessageCalled = 0;

    net.sendMessage = function sendMessage(message, host) {
        switch (sendMessageCalled) {
            case 0:
                sendMessageCalled++;

                assert.strictEqual(message.type, MessageType.Ping);
                assert.strictEqual(host, MEMBER_B.host);
                break;
            case 1:
                assert.fail();
                break;
        }
    };

    failureDetector.start();

    net.emit(Net.EventType.PingReq, {
        seq: seq,
        destination: MEMBER_B.host
    }, MEMBER_A.host);

    setTimeout(function verify() {
        verifyInternal(failureDetector, assert);
        failureDetector.stop();

        assert.end();
    }, INTERVAL);
});

function verifyInternal(failureDetector, assert) {
    assert.strictEqual(Object.keys(failureDetector.seqToTimeout).length, 0);
    assert.strictEqual(Object.keys(failureDetector.seqToCallback).length, 0);
}


================================================
FILE: test/index.js
================================================
'use strict';
require('./codec');
require('./disseminator');
require('./failure-detector');
require('./membership');
require('./net');


================================================
FILE: test/membership.js
================================================
'use strict';
var events = require('events');
var test = require('tape');

var FailureDetector = require('../lib/failure-detector');
var Member = require('../lib/member');
var Membership = require('../lib/membership');
var MessageType = require('../lib/message-type');
var Net = require('../lib/net');

var SUSPECT_TIMEOUT = 10;
var LOCAL = new Member({
    host: 'localhost:0000'
});
var MEMBER_A = new Member({
    host: 'localhost:1111'
});

test('Membership sends suspect update back when receives Ack from member in suspect state', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });

    net.sendMessage = function sendMessage(message, host) {
        membership.stop();

        assert.strictEqual(host, MEMBER_A.host);
        assert.strictEqual(message.type, MessageType.Update);
        assert.strictEqual(message.data.state, Member.State.Suspect);
        assert.deepEqual(message.data, membership.get(MEMBER_A.host).data());
        assert.end();
    };

    membership.start();
    failureDetector.emit(FailureDetector.EventType.Suspect, MEMBER_A);
    net.emit(Net.EventType.Ack, MEMBER_A, MEMBER_A.host);
});

test('Membership emits update event when member becomes suspect', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        membership.stop();

        assert.strictEqual(data.state, Member.State.Suspect);
        assert.deepEqual(data, membership.get(data.host).data());
        assert.end();
    });

    membership.start();
    failureDetector.emit(FailureDetector.EventType.Suspect, MEMBER_A);
});

test('Membership emits update event when member becomes faulty', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        },
        suspectTimeout: SUSPECT_TIMEOUT
    });
    var updateEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                membership.stop();

                assert.strictEqual(data.state, Member.State.Faulty);
                assert.strictEqual(membership.get(data.host), undefined);
                assert.end();
                break;
        }
    });

    membership.start();
    failureDetector.emit(FailureDetector.EventType.Suspect, MEMBER_A);
});

test('Membership resumes suspect mechanism after restart', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        },
        suspectTimeout: SUSPECT_TIMEOUT
    });
    var updateEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                membership.stop();

                assert.strictEqual(data.state, Member.State.Faulty);
                assert.strictEqual(membership.get(data.host), undefined);
                assert.end();
                break;
        }
    });

    membership.start();
    failureDetector.emit(FailureDetector.EventType.Suspect, MEMBER_A);
    membership.stop();
    membership.start();
});

test('Membership accepts alive update for remote member not in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        membership.stop();

        assert.strictEqual(data.state, Member.State.Alive);
        assert.deepEqual(data, update);
        assert.deepEqual(data, membership.get(data.host).data());
        assert.end();
    });

    membership.start();
    net.emit(Net.EventType.Update, update);
});

test('Membership accepts alive update with greater incarnation ' +
     'for alive/suspect remote member in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                membership.stop();

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                assert.end();
                break;
        }
    });

    membership.start();

    net.emit(Net.EventType.Update, update);

    update.incarnation += 1;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops alive update with smaller or equal incarnation ' +
     'for alive/suspect remote member in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;
    var dropEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 2:
                assert.fail();
                break;
        }
    });

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        switch (dropEmitted) {
            case 0:
                dropEmitted++;

                assert.strictEqual(updateEmitted, 1);
                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                break;
            case 1:
                dropEmitted++;

                assert.strictEqual(updateEmitted, 1);
                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                break;
            case 2:
                dropEmitted++;

                assert.strictEqual(updateEmitted, 2);
                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                break;
            case 3:
                membership.stop();

                assert.strictEqual(updateEmitted, 2);
                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.end();
                break;
        }
    });

    membership.start();

    // accept
    net.emit(Net.EventType.Update, update);

    // drop
    net.emit(Net.EventType.Update, update);

    // drop
    update.incarnation -= 1;
    net.emit(Net.EventType.Update, update);

    // accept
    update.incarnation += 1;
    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);

    // drop
    update.state = Member.State.Alive;
    net.emit(Net.EventType.Update, update);

    // drop
    update.incarnation -= 1;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops alive update with smaller or equal incarnation ' +
     'for faulty member', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;
    var dropEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Faulty);
                assert.deepEqual(data, update);
                assert.deepEqual(membership.get(data.host), undefined);
                break;
            case 2:
                assert.fail();
                break;
        }
    });

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        switch (dropEmitted) {
            case 0:
                dropEmitted++;

                assert.strictEqual(updateEmitted, 2);
                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                break;
            case 1:
                membership.stop();

                assert.strictEqual(updateEmitted, 2);
                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.end();
                break;
        }
    });

    membership.start();

    // accept
    update.state = Member.State.Alive;
    net.emit(Net.EventType.Update, update);

    // accept
    update.state = Member.State.Faulty;
    net.emit(Net.EventType.Update, update);

    // drop
    update.state = Member.State.Alive;
    net.emit(Net.EventType.Update, update);

    // drop
    update.incarnation -= 1;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops alive update with smaller or equal incarnation for local member', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = LOCAL.data();
    var dropEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate() {
        assert.fail();
    });

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        switch (dropEmitted) {
            case 0:
                dropEmitted++;

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                break;
            case 1:
                membership.stop();

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.end();
                break;
        }
    });

    membership.start();

    net.emit(Net.EventType.Update, update);

    net.emit(Net.EventType.Update, update);
});

test('Membership accepts alive update with greater incarnation for local member ' +
     'and local member affirms itself with greater incarnation', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = LOCAL.data();

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        membership.stop();

        assert.strictEqual(data.state, Member.State.Alive);
        assert.strictEqual(data.incarnation > update.incarnation, true);
        assert.end();
    });

    membership.start();

    update.incarnation += 1;
    net.emit(Net.EventType.Update, update);
});

test('Membership accepts suspect update for remote member not in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        membership.stop();

        assert.strictEqual(data.state, Member.State.Suspect);
        assert.deepEqual(data, update);
        assert.deepEqual(data, membership.get(data.host).data());
        assert.end();
    });

    membership.start();

    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);
});

test('Membership accepts suspect update with greater incarnation ' +
     'for alive/suspect remote member in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 2:
                membership.stop();

                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                assert.end();
                break;
        }
    });

    membership.start();

    net.emit(Net.EventType.Update, update);

    update.incarnation += 1;
    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);

    update.incarnation += 1;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops suspect update with smaller or equal incarnation ' +
     'for suspect remote member in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;
    var dropEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                assert.fail();
                break;
        }
    });

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        switch (dropEmitted) {
            case 0:
                dropEmitted++;

                assert.strictEqual(updateEmitted, 1);
                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                break;
            case 1:
                membership.stop();

                assert.strictEqual(updateEmitted, 1);
                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                assert.end();
                break;
        }
    });

    membership.start();

    // accept
    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);

    // drop
    net.emit(Net.EventType.Update, update);

    // drop
    update.incarnation -= 1;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops suspect update with smaller or equal incarnation ' +
     'for faulty member', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;
    var dropEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Faulty);
                assert.deepEqual(data, update);
                assert.deepEqual(membership.get(data.host), undefined);
                break;
            case 2:
                assert.fail();
                break;
        }
    });

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        switch (dropEmitted) {
            case 0:
                dropEmitted++;

                assert.strictEqual(updateEmitted, 2);
                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                break;
            case 1:
                membership.stop();

                assert.strictEqual(updateEmitted, 2);
                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                assert.end();
                break;
        }
    });

    membership.start();

    // accept
    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);

    // accept
    update.state = Member.State.Faulty;
    net.emit(Net.EventType.Update, update);

    // drop
    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);

    // drop
    update.incarnation -= 1;
    net.emit(Net.EventType.Update, update);
});

test('Membership accepts suspect update with equal incarnation ' +
     'for alive remote member in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                membership.stop();

                assert.strictEqual(data.state, Member.State.Suspect);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                assert.end();
                break;
        }
    });

    membership.start();

    net.emit(Net.EventType.Update, update);

    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops suspect update with smaller incarnation ' +
     'for alive remote member in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        updateEmitted++;

        assert.strictEqual(data.state, Member.State.Alive);
        assert.deepEqual(data, update);
        assert.deepEqual(data, membership.get(data.host).data());
    });

    membership.on(Membership.EventType.Drop, function onUpdate(data) {
        membership.stop();

        assert.strictEqual(updateEmitted, 1);
        assert.strictEqual(data.state, Member.State.Suspect);
        assert.deepEqual(data, update);
        assert.end();
    });

    membership.start();

    net.emit(Net.EventType.Update, update);

    update.incarnation -= 1;
    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops suspect update with for local member ' +
     'and local member affirms itself with greater incarnation', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = LOCAL.data();
    var dropEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        membership.stop();

        assert.strictEqual(dropEmitted, 1);
        assert.strictEqual(data.state, Member.State.Alive);
        assert.strictEqual(data.incarnation > update.incarnation, true);
        assert.end();
    });

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        dropEmitted++;

        assert.strictEqual(data.state, Member.State.Suspect);
        assert.deepEqual(data, update);
    });

    membership.start();

    update.state = Member.State.Suspect;
    net.emit(Net.EventType.Update, update);
});

test('Membership accepts faulty update with greater or equal incarnation ' +
     'for remote member in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        switch (updateEmitted) {
            case 0:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 1:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Faulty);
                assert.deepEqual(data, update);
                assert.strictEqual(membership.get(data.host), undefined);
                break;
            case 2:
                updateEmitted++;

                assert.strictEqual(data.state, Member.State.Alive);
                assert.deepEqual(data, update);
                assert.deepEqual(data, membership.get(data.host).data());
                break;
            case 3:
                membership.stop();

                assert.strictEqual(data.state, Member.State.Faulty);
                assert.deepEqual(data, update);
                assert.strictEqual(membership.get(data.host), undefined);
                assert.end();
                break;
        }
    });

    membership.start();

    net.emit(Net.EventType.Update, update);

    update.state = Member.State.Faulty;
    net.emit(Net.EventType.Update, update);

    update.incarnation += 1;
    update.state = Member.State.Alive;
    net.emit(Net.EventType.Update, update);

    update.incarnation += 1;
    update.state = Member.State.Faulty;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops faulty update with for remote member not in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        membership.stop();

        assert.strictEqual(data.state, Member.State.Faulty);
        assert.deepEqual(data, update);
        assert.end();
    });

    membership.start();

    update.state = Member.State.Faulty;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops faulty update with smaller incarnation for remote member in membership', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = MEMBER_A.data();
    var updateEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        updateEmitted++;

        assert.strictEqual(data.state, Member.State.Alive);
        assert.deepEqual(data, update);
        assert.deepEqual(data, membership.get(data.host).data());
    });

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        membership.stop();

        assert.strictEqual(updateEmitted, 1);
        assert.strictEqual(data.state, Member.State.Faulty);
        assert.deepEqual(data, update);
        assert.end();
    });

    membership.start();

    net.emit(Net.EventType.Update, update);

    update.incarnation -= 1;
    update.state = Member.State.Faulty;
    net.emit(Net.EventType.Update, update);
});

test('Membership drops faulty update with for local member ' +
     'and local member affirms itself with greater incarnation', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var update = LOCAL.data();
    var dropEmitted = 0;

    membership.on(Membership.EventType.Update, function onUpdate(data) {
        membership.stop();

        assert.strictEqual(dropEmitted, 1);
        assert.strictEqual(data.state, Member.State.Alive);
        assert.strictEqual(data.incarnation > update.incarnation, true);
        assert.end();
    });

    membership.on(Membership.EventType.Drop, function onDrop(data) {
        assert.strictEqual(dropEmitted, 0);
        assert.strictEqual(data.state, Member.State.Faulty);
        assert.deepEqual(data, update);

        dropEmitted++;
    });

    membership.start();

    update.state = Member.State.Faulty;
    net.emit(Net.EventType.Update, update);
});

test('Membership is a infinite round robin iterator', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var numberOfMembers = Math.ceil(Math.random() * 10);
    var numberOfIterations = Math.ceil(Math.random() * 5);
    var hostToCount = Object.create(null);
    var member;
    var i;
    var j;

    membership.start();

    for (i = 0; i < numberOfMembers; i++) {
        net.emit(Net.EventType.Update, new Member({
            host: 'localhost:' + i
        }).data());
    }

    membership.stop();

    for (i = 0; i < numberOfMembers; i++) {
        member = membership.next();
        hostToCount[member.host] = (hostToCount[member.host] || 0) + 1;
    }

    Object.keys(hostToCount).forEach(function verify(host) {
        assert.strictEqual(hostToCount[host], 1);
    });

    for (j = 0; j < numberOfIterations; j++) {
        for (i = 0; i < numberOfMembers; i++) {
            member = membership.next();
            hostToCount[member.host] = (hostToCount[member.host] || 0) + 1;
        }
    }

    Object.keys(hostToCount).forEach(function verify(host) {
        assert.strictEqual(hostToCount[host], numberOfIterations + 1);
    });

    assert.strictEqual(hostToCount[LOCAL.host], undefined);
    assert.end();
});

test('Membership next to undefined when there is only local member', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });

    assert.strictEqual(membership.next(), undefined);
    assert.end();
});

test('Membership can randomly select up to n unique remote members', function t(assert) {
    var failureDetector = new events.EventEmitter();
    var net = new events.EventEmitter();
    var membership = new Membership({
        local: LOCAL.data(),
        swim: {
            failureDetector: failureDetector,
            net: net
        }
    });
    var numberOfMembers = Math.ceil(Math.random() * 10);
    var numberOfIterations = Math.ceil(Math.random() * 5);
    var selected;
    var n;
    var i;
    var j;

    assert.strictEqual(membership.random(1).length, 0);

    membership.start();

    for (i = 0; i < numberOfMembers; i++) {
        net.emit(Net.EventType.Update, new Member({
            host: 'localhost:' + i
        }).data());
    }

    membership.stop();

    for (j = 0; j < numberOfIterations; j++) {
        n = Math.floor(Math.random() * numberOfMembers * 2);
        selected = membership.random(n);

        assert.strictEqual(Array.isArray(selected), true);
        assert.strictEqual(selected.length <= n, true);
        /* jshint loopfunc: true */
        assert.strictEqual(Object.keys(selected.reduce(function mark(result, curr) {
            result[curr.host] = true;
            return result;
        }, Object.create(null))).length, selected.length);
        /* jshint loopfunc: false */
    }

    assert.end();
});


================================================
FILE: test/net.js
================================================
'use strict';
var async = require('async');
var test = require('tape');

var Codec = require('../lib/codec');
var MessageType = require('../lib/message-type');
var Net = require('../lib/net');

var codec = new Codec();

test('Net can send and receive primitive messages', function t(assert) {
    var senderOpts = {
        swim: {
            codec: codec,
            disseminator: {
                getUpdatesUpTo: function getUpdatesUpTo() {
                    return [];
                }
            }
        },
        udp: {
            port: 0
        }
    };
    var receiverOpts = {
        swim: {
            codec: codec
        },
        udp: {
            port: 0
        }
    };
    var sender = new Net(senderOpts);
    var receiver = new Net(receiverOpts);
    var tests = [{
        type: MessageType.Ping,
        data: {
            seq: Math.random()
        },
        event: Net.EventType.Ping
    }, {
        type: MessageType.PingReq,
        data: {
            seq: Math.random()
        },
        event: Net.EventType.PingReq
    }, {
        type: MessageType.Sync,
        data: {
            seq: Math.random()
        },
        event: Net.EventType.Sync
    }, {
        type: MessageType.Ack,
        data: {
            seq: Math.random()
        },
        event: Net.EventType.Ack
    }, {
        type: MessageType.Update,
        data: {
            seq: Math.random()
        },
        event: Net.EventType.Update
    }];
    var senderPort;
    var receiverPort;

    async.parallel([
        sender.listen.bind(sender),
        receiver.listen.bind(receiver)
    ], function parallelCallback(err) {
        assert.notOk(err);

        senderPort = sender.udpSocket.address().port;
        receiverPort = receiver.udpSocket.address().port;

        async.each(tests, function runTest(test, callback) {
            receiver.on(test.event, function onEvent(data, host) {
                assert.deepEqual(data, test.data);
                assert.strictEqual(parseInt(host.split(':')[1]), senderPort);

                receiver.removeAllListeners(test.event);
                callback();
            });

            sender.sendMessage({
                type: test.type,
                data: test.data
            }, 'localhost:' + receiverPort);
        }, function eachCallback() {
            sender.close();
            receiver.close();

            assert.end();
        });
    });
});

test('Net can send and receive primitive messages with piggybacked updates', function t(assert) {
    var randomPingData = {
        seq: Math.random()
    };
    var randomUpdateData = {
        seq: Math.random()
    };
    var senderOpts = {
        swim: {
            codec: codec,
            disseminator: {
                getUpdatesUpTo: function getUpdatesUpTo(bytesAvailable) {
                    var expectedBytesAvailable = Net.Default.udp.maxDgramSize - Net.MessageTypeSize -
                        Net.LengthSize * 2 - Net.MessageTypeSize - codec.encode(randomPingData).length;
                    var header;

                    assert.strictEqual(bytesAvailable, expectedBytesAvailable);

                    header = new Buffer(Net.MessageTypeSize);
                    Net.WriteMessageType.call(header, MessageType.Update, 0);

                    return [Buffer.concat([header, codec.encode(randomUpdateData)])];
                }
            }
        },
        udp: {
            port: 0
        }
    };
    var receiverOpts = {
        swim: {
            codec: codec
        },
        udp: {
            port: 0
        }
    };
    var sender = new Net(senderOpts);
    var receiver = new Net(receiverOpts);
    var senderPort;
    var receiverPort;

    async.parallel([
        sender.listen.bind(sender),
        receiver.listen.bind(receiver)
    ], function parallelCallback(err) {
        assert.notOk(err);

        senderPort = sender.udpSocket.address().port;
        receiverPort = receiver.udpSocket.address().port;

        async.parallel([
            function receivePing(callback) {
                receiver.on(Net.EventType.Ping, function onPing(data, host) {
                    assert.deepEqual(data, randomPingData);
                    assert.strictEqual(parseInt(host.split(':')[1]), senderPort);
                    callback();
                });
            },
            function receiveUpdate(callback) {
                receiver.on(Net.EventType.Update, function onUpdate(data, host) {
                    assert.deepEqual(data, randomUpdateData);
                    assert.strictEqual(parseInt(host.split(':')[1]), senderPort);
                    callback();
                });
            },
            function send(callback) {
                sender.sendMessage({
                    type: MessageType.Ping,
                    data: randomPingData
                }, 'localhost:' + receiverPort);
                callback();
            }
        ], function parallelCallback() {
            receiver.removeAllListeners();

            sender.close();
            receiver.close();

            assert.end();
        });
    });
});

test('Net can send and receive multiple messages batched over packets', function t(assert) {
    var senderOpts = {
        swim: {
            codec: codec
        },
        udp: {
            port: 0
        }
    };
    var receiverOpts = {
        swim: {
            codec: codec
        },
        udp: {
            port: 0
        }
    };
    var sender = new Net(senderOpts);
    var receiver = new Net(receiverOpts);
    var messages = [];
    var received = [];
    var messageCount = 100;
    var packetCount = 0;
    var senderPort;
    var receiverPort;
    var i;

    for (i = 0; i < messageCount; i++) {
        messages.push({
            type: MessageType.Update,
            data: {
                seq: Math.random()
            }
        });
    }

    sender.sendBuffer = function sendBuffer(buffer, host) {
        packetCount++;
        Net.prototype.sendBuffer.call(sender, buffer, host);
    };

    async.parallel([
        sender.listen.bind(sender),
        receiver.listen.bind(receiver)
    ], function parallelCallback(err) {
        assert.notOk(err);

        senderPort = sender.udpSocket.address().port;
        receiverPort = receiver.udpSocket.address().port;

        async.parallel([
            function receiveUpdate(callback) {
                receiver.on(Net.EventType.Update, function onUpdate(data) {
                    received.push({
                        type: MessageType.Update,
                        data: data
                    });

                    if (received.length === messages.length) {
                        callback();
                    }
                });
            },
            function send(callback) {
                sender.sendMessages(messages, 'localhost:' + receiverPort);
                callback();
            }
        ], function parallelCallback() {
            assert.strictEqual(packetCount < messageCount, true);
            assert.deepEqual(received.length, messages.length);
            assert.deepEqual(sum(received), sum(messages));

            receiver.removeAllListeners();

            sender.close();
            receiver.close();

            assert.end();
        });
    });
});

function sum(messages) {
    messages.reduce(function sum(acc, m) {
        return acc += m.seq;
    }, 0);
}
Download .txt
gitextract_5lnnmlc_/

├── .gitignore
├── .jscsrc
├── .jshintrc
├── .npmignore
├── .travis.yml
├── README.md
├── bench/
│   ├── index.js
│   ├── lib/
│   │   └── runner.js
│   ├── scenario/
│   │   └── single-node-failure.js
│   └── script/
│       ├── convergence-time.js
│       ├── dumb.js
│       └── worker.js
├── docs/
│   └── api.md
├── index.js
├── lib/
│   ├── codec.js
│   ├── disseminator.js
│   ├── error.js
│   ├── failure-detector.js
│   ├── member.js
│   ├── membership.js
│   ├── message-type.js
│   ├── net.js
│   └── swim.js
├── package.json
└── test/
    ├── codec.js
    ├── disseminator.js
    ├── failure-detector.js
    ├── index.js
    ├── membership.js
    └── net.js
Download .txt
SYMBOL INDEX (29 symbols across 13 files)

FILE: bench/lib/runner.js
  function Runner (line 6) | function Runner(opts) {
  function noop (line 60) | function noop(callback) {

FILE: bench/script/convergence-time.js
  function parseArgs (line 17) | function parseArgs() {
  function main (line 44) | function main() {
  function setup (line 92) | function setup(context, callback) {
  function fork (line 116) | function fork(context, onMessage) {
  function getHostsToJoin (line 157) | function getHostsToJoin(n) {
  function waitForConvergence (line 168) | function waitForConvergence(context, callback) {
  function teardown (line 188) | function teardown(context, callback) {
  function before (line 197) | function before(context, callback) {
  function fn (line 201) | function fn(context, callback) {
  function after (line 205) | function after(context, callback) {

FILE: bench/script/worker.js
  function parseArgs (line 20) | function parseArgs() {
  function handleMessage (line 40) | function handleMessage(swim) {
  function bootstrap (line 84) | function bootstrap(callback) {

FILE: lib/codec.js
  function Codec (line 5) | function Codec(opts) {

FILE: lib/disseminator.js
  function Disseminator (line 11) | function Disseminator(opts) {

FILE: lib/error.js
  function JoinFailedError (line 4) | function JoinFailedError(meta) {
  function InvalidStateError (line 16) | function InvalidStateError(meta) {
  function ListenFailedError (line 28) | function ListenFailedError(meta) {

FILE: lib/failure-detector.js
  function FailureDetector (line 9) | function FailureDetector(opts) {

FILE: lib/member.js
  function Member (line 4) | function Member(opts) {

FILE: lib/membership.js
  function Membership (line 12) | function Membership(opts) {

FILE: lib/net.js
  function Net (line 9) | function Net(opts) {
  function formatRinfo (line 356) | function formatRinfo(rinfo) {

FILE: lib/swim.js
  function Swim (line 16) | function Swim(opts) {
  function checkJoin (line 168) | function checkJoin() {

FILE: test/failure-detector.js
  function verifyInternal (line 332) | function verifyInternal(failureDetector, assert) {

FILE: test/net.js
  function sum (line 266) | function sum(messages) {
Condensed preview — 30 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (125K chars).
[
  {
    "path": ".gitignore",
    "chars": 14,
    "preview": "node_modules/\n"
  },
  {
    "path": ".jscsrc",
    "chars": 2235,
    "preview": "{\n    \"fileExtensions\": [\".js\"],\n\n    \"maximumLineLength\": 120,\n    \"validateIndentation\": 4,\n    \"validateLineBreaks\": "
  },
  {
    "path": ".jshintrc",
    "chars": 350,
    "preview": "{\n  \"bitwise\": true,\n  \"camelcase\": true,\n  \"curly\": true,\n  \"eqeqeq\": true,\n  \"esnext\": true,\n  \"freeze\": true,\n  \"imme"
  },
  {
    "path": ".npmignore",
    "chars": 126,
    "preview": "# Do not package non-functional code when importing this package via\n# npm.\ndocs/\nbench/\ntest/\n.travis.yml\n.jscsrc\n.jshi"
  },
  {
    "path": ".travis.yml",
    "chars": 157,
    "preview": "language: node_js\nnode_js:\n  - \"node\"\n  - \"lts/*\"\nenv:\n  - CXX=g++-4.8\naddons:\n  apt:\n    sources:\n    - ubuntu-toolchai"
  },
  {
    "path": "README.md",
    "chars": 3450,
    "preview": "# swim-js [![Build Status](https://travis-ci.org/mrhooray/swim-js.svg?branch=master)](https://travis-ci.org/mrhooray/swi"
  },
  {
    "path": "bench/index.js",
    "chars": 320,
    "preview": "'use strict';\n// var cycles = [10, 20];\n// var workers = [4, 16, 32, 64];\n// var codec = ['json', 'msgpack'];\n// var dis"
  },
  {
    "path": "bench/lib/runner.js",
    "chars": 1782,
    "preview": "'use strict';\nvar async = require('async');\nvar events = require('events');\nvar util = require('util');\n\nfunction Runner"
  },
  {
    "path": "bench/scenario/single-node-failure.js",
    "chars": 468,
    "preview": "'use strict';\n\nmodule.exports = function singleNodeFailure(context, callback) {\n    var hosts = Object.keys(context.host"
  },
  {
    "path": "bench/script/convergence-time.js",
    "chars": 7131,
    "preview": "#!/usr/bin/env node\n'use strict';\nvar cp = require('child_process');\nvar metrics = require('metrics');\nvar program = req"
  },
  {
    "path": "bench/script/dumb.js",
    "chars": 1674,
    "preview": "#!/usr/bin/env node\n'use strict';\nvar Swim = require('../../lib/swim');\n\nvar hosts = [\n    '127.0.0.1:11111',\n    '127.0"
  },
  {
    "path": "bench/script/worker.js",
    "chars": 3738,
    "preview": "#!/usr/bin/env node\n'use strict';\nvar assert = require('assert');\nvar program = require('commander');\n\nvar Swim = requir"
  },
  {
    "path": "docs/api.md",
    "chars": 2757,
    "preview": "# API Documentation\n\n## Swim object\n\n### Constructor(options)\nSee Options Object.\n\n### boostrap(hosts, callback)\nBootstr"
  },
  {
    "path": "index.js",
    "chars": 54,
    "preview": "'use strict';\nmodule.exports = require('./lib/swim');\n"
  },
  {
    "path": "lib/codec.js",
    "chars": 722,
    "preview": "'use strict';\nvar assert = require('assert');\nvar msgpack = require('msgpack');\n\nfunction Codec(opts) {\n    opts = opts "
  },
  {
    "path": "lib/disseminator.js",
    "chars": 4901,
    "preview": "'use strict';\nvar debug = require('debug');\nvar clone = require('clone');\nvar events = require('events');\nvar util = req"
  },
  {
    "path": "lib/error.js",
    "chars": 990,
    "preview": "'use strict';\nvar util = require('util');\n\nfunction JoinFailedError(meta) {\n    Error.captureStackTrace(this, this.const"
  },
  {
    "path": "lib/failure-detector.js",
    "chars": 5361,
    "preview": "'use strict';\nvar debug = require('debug');\nvar events = require('events');\nvar util = require('util');\n\nvar MessageType"
  },
  {
    "path": "lib/member.js",
    "chars": 1032,
    "preview": "'use strict';\nvar clone = require('clone');\n\nfunction Member(opts) {\n    this.meta = opts.meta || undefined;\n    this.ho"
  },
  {
    "path": "lib/membership.js",
    "chars": 10254,
    "preview": "'use strict';\nvar debug = require('debug');\nvar events = require('events');\nvar farmhash = require('farmhash');\nvar util"
  },
  {
    "path": "lib/message-type.js",
    "chars": 164,
    "preview": "'use strict';\n\nmodule.exports = {\n    Compound: 0,\n    Compressed: 1,\n    Encrypted: 2,\n    Ping: 10,\n    PingReq: 11,\n "
  },
  {
    "path": "lib/net.js",
    "chars": 10142,
    "preview": "'use strict';\nvar debug = require('debug');\nvar dgram = require('dgram');\nvar events = require('events');\nvar util = req"
  },
  {
    "path": "lib/swim.js",
    "chars": 6572,
    "preview": "'use strict';\nvar debug = require('debug');\nvar events = require('events');\nvar util = require('util');\n\nvar Codec = req"
  },
  {
    "path": "package.json",
    "chars": 1012,
    "preview": "{\n  \"name\": \"swim\",\n  \"version\": \"0.6.0\",\n  \"description\": \"Gossip protocol based on SWIM\",\n  \"main\": \"index.js\",\n  \"scr"
  },
  {
    "path": "test/codec.js",
    "chars": 858,
    "preview": "'use strict';\nvar test = require('tape');\n\nvar Codec = require('../lib/codec');\n\ntest('Codec constructs a codec instance"
  },
  {
    "path": "test/disseminator.js",
    "chars": 3606,
    "preview": "'use strict';\nvar events = require('events');\nvar test = require('tape');\n\nvar Codec = require('../lib/codec');\nvar Diss"
  },
  {
    "path": "test/failure-detector.js",
    "chars": 9046,
    "preview": "'use strict';\nvar events = require('events');\nvar test = require('tape');\n\nvar FailureDetector = require('../lib/failure"
  },
  {
    "path": "test/index.js",
    "chars": 135,
    "preview": "'use strict';\nrequire('./codec');\nrequire('./disseminator');\nrequire('./failure-detector');\nrequire('./membership');\nreq"
  },
  {
    "path": "test/membership.js",
    "chars": 32236,
    "preview": "'use strict';\nvar events = require('events');\nvar test = require('tape');\n\nvar FailureDetector = require('../lib/failure"
  },
  {
    "path": "test/net.js",
    "chars": 7449,
    "preview": "'use strict';\nvar async = require('async');\nvar test = require('tape');\n\nvar Codec = require('../lib/codec');\nvar Messag"
  }
]

About this extraction

This page contains the full source code of the mrhooray/swim-js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 30 files (116.0 KB), approximately 26.8k tokens, and a symbol index with 29 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!