Skip to content

Commit

Permalink
refactored Acceptor to use Proposal objects for state.
Browse files Browse the repository at this point in the history
It's probably worth while to not share proposal.js for
Acceptor state *and* as a message between actors.
  • Loading branch information
dannycoates committed Jan 1, 2013
1 parent aa38d9e commit a661b62
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 46 deletions.
45 changes: 19 additions & 26 deletions acceptor/acceptor.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,46 @@
module.exports = function (assert, inherits, EventEmitter) {
module.exports = function (assert, inherits, EventEmitter, Proposal) {

function Acceptor(id, learner, storage) {
EventEmitter.call(this)
this.id = id
this.learner = learner || nilLearner
this.learner = learner
this.learner.on('data', onlearnerData.bind(this))
this.storage = storage || nilStorage
this.highmark = this.learner.highmark()
this.instances = {}
}
inherits(Acceptor, EventEmitter)

Acceptor.prototype.prepare = function (prepare) {
assert.equal(prepare.valueBallot, undefined)
Acceptor.prototype.instance = function (id) {
var instance = this.instances[id] || Proposal.create(id, this.id)
this.instances[id] = instance
return instance
}

Acceptor.prototype.prepare = function (prepare) {
if (prepare.instance <= this.highmark) {
return this.lookup(prepare.instance, 'promised')
}

var state = this.instances[prepare.instance] || prepare

if (state.ballot <= prepare.ballot && !state.valueBallot) {
// Promise to reject lower ballots, but
// if a proposal has already been accepted don't touch it
state = prepare
}
state.acceptor = this.id
this.instances[prepare.instance] = state
this.storage.set(state, this.emit.bind(this, 'promised'))
var proposal = this.instance(prepare.instance).prepare(prepare)
this.storage.set(proposal, this.emit.bind(this, 'promised'))
}

Acceptor.prototype.accept = function (proposal) {
var state = this.instances[proposal.instance] || proposal

if (proposal.instance <= this.highmark) {
return this.lookup(proposal.instance, 'accepted')
return this.lookup(proposal.instance, 'rejected')
}
if (state.ballot > proposal.ballot) {
// refuse the proposal by replying with the higher ballot proposal
return this.emit('rejected', state)

var instance = this.instance(proposal.instance)
var accepted = instance.accept(proposal)
if (accepted) {
return this.storage.set(accepted, this.emit.bind(this, 'accepted'))
}
proposal.acceptor = this.id
this.instances[proposal.instance] = proposal
this.storage.set(proposal, this.emit.bind(this, 'accepted'))
this.emit('rejected', instance)
}

Acceptor.prototype.lookup = function (instance, event) {
this.storage.get(instance, this.emit.bind(this, event || 'lookup'))
Acceptor.prototype.lookup = function (instanceId, event) {
this.storage.get(instanceId, this.emit.bind(this, event || 'lookup'))
}

function onlearnerData(proposal) {
Expand All @@ -55,7 +49,6 @@ module.exports = function (assert, inherits, EventEmitter) {
delete this.instances[proposal.instance]
}

var nilLearner = { highmark: function () { return 0 }, on: function () {}}
var nilStorage = { get: function (i) { }, set: function (p, cb) { cb(p) }}

return Acceptor
Expand Down
3 changes: 2 additions & 1 deletion acceptor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var assert = require('assert')
var EventEmitter = require('events').EventEmitter
var inherits = require('util').inherits

var Acceptor = require('./acceptor')(assert, inherits, EventEmitter)
var Proposal = require('../lib/proposal')()
var Acceptor = require('./acceptor')(assert, inherits, EventEmitter, Proposal)

module.exports = Acceptor
19 changes: 19 additions & 0 deletions lib/proposal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ module.exports = function () {
this.acceptor = acceptor
}

Proposal.create = function (instance, acceptor) {
return new Proposal(instance, 0, null, 0, acceptor)
}

Proposal.prototype.prepare = function (prepare) {
this.ballot = Math.max(prepare.ballot, this.ballot)
return this
}

Proposal.prototype.accept = function (proposal) {
if (this.ballot > proposal.ballot) {
return
}
this.ballot = proposal.ballot
this.valueBallot = proposal.ballot
this.value = proposal.value
return this
}

Proposal.prototype.copy = function () {
return new Proposal(this.instance, this.ballot, this.value, this.valueBallot, this.acceptor)
}
Expand Down
1 change: 0 additions & 1 deletion proposer/propose-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ module.exports = function (assert, inherits, EventEmitter, Prepare, Proposal) {
this.value = highest.value
// otherwise there were no accepted proposals
}
this.valueBallot = this.ballot
return this.proposal()
}
}
Expand Down
37 changes: 19 additions & 18 deletions test/acceptor-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ var inherits = require('util').inherits
var EventEmitter = require('events').EventEmitter
var Proposal = require('../lib/proposal')()
var Prepare = require('../lib/prepare')()
var Acceptor = require('../acceptor/acceptor')(assert, inherits, EventEmitter)
var Acceptor = require('../acceptor/acceptor')(assert, inherits, EventEmitter, Proposal)

describe('Acceptor', function () {

var acceptor = null
var fakeLearner = {on: function () {}, highmark: function () { return 0 }}

beforeEach(function () {
acceptor = new Acceptor(1)
acceptor = new Acceptor(1, fakeLearner)
})

describe('prepare', function () {
describe('prepare()', function () {

it('stores the highest ballot',
function () {
Expand All @@ -22,20 +23,20 @@ describe('Acceptor', function () {
var prep3 = new Prepare(1, 3)

acceptor.prepare(prep2)
assert.equal(acceptor.instances[1], prep2)
assert.equal(acceptor.instance(1).ballot, prep2.ballot)
acceptor.prepare(prep1)
assert.equal(acceptor.instances[1], prep2)
assert.equal(acceptor.instance(1).ballot, prep2.ballot)
acceptor.prepare(prep3)
assert.equal(acceptor.instances[1], prep3)
assert.equal(acceptor.instance(1).ballot, prep3.ballot)
}
)

it('emits the accepted proposal if one exists',
function (done) {
var proposal1 = new Proposal(4, 3, 'x', 2, 1)
var proposal1 = new Proposal(4, 3, 'x')
acceptor.accept(proposal1)
acceptor.once('promised', function (proposal) {
assert.equal(proposal, proposal1)
assert.equal(proposal.value, proposal1.value)
done()
})

Expand All @@ -44,16 +45,16 @@ describe('Acceptor', function () {
)
})

describe('accept', function () {
describe('accept()', function () {

it('does not accept a proposal with a lower ballot',
function (done) {
var proposal1 = new Proposal(4, 5, 'x', 2, 1)
var proposal2 = new Proposal(4, 3, 'x', 2, 1)
var proposal1 = new Proposal(4, 5, 'x')
var proposal2 = new Proposal(4, 3, 'x')

acceptor.accept(proposal1)
acceptor.once('rejected', function (proposal) {
assert.equal(proposal, proposal1)
assert.equal(proposal.ballot, proposal1.ballot)
done()
})
acceptor.accept(proposal2)
Expand All @@ -63,20 +64,20 @@ describe('Acceptor', function () {
it('emits the accepted proposal',
function (done) {
acceptor.once('accepted', function (proposal) {
assert.equal(proposal, proposal1)
assert.equal(proposal.ballot, proposal1.ballot)
done()
})

var proposal1 = new Proposal(4, 3, 'x', 2, 1)
var proposal1 = new Proposal(4, 3, 'x')
acceptor.accept(proposal1)
}
)

it('accepts the first proposal if no prepare requests have been made',
function (done) {
var proposal1 = new Proposal(4, 3, 'x', 2, 1)
var proposal1 = new Proposal(4, 3, 'x')
acceptor.once('accepted', function (proposal) {
assert.equal(proposal, proposal1)
assert.equal(proposal.ballot, proposal1.ballot)
done()
})
acceptor.accept(proposal1)
Expand All @@ -86,9 +87,9 @@ describe('Acceptor', function () {
it('refuses a proposal if a prepare request had a higher ballot',
function (done) {
var prep1 = new Prepare(4, 5)
var proposal1 = new Proposal(4, 3, 'x', 2, 1)
var proposal1 = new Proposal(4, 3, 'x')
acceptor.once('rejected', function (proposal) {
assert.equal(proposal, prep1)
assert.equal(proposal.ballot, prep1.ballot)
done()
})
acceptor.prepare(prep1)
Expand Down

0 comments on commit a661b62

Please sign in to comment.