UNPKG

olio

Version:
227 lines (192 loc) 7.82 kB
// Node.js API. var assert = require('assert') var events = require('events') var Procession = require('procession') // An evented semaphore. var Signal = require('signal') // Return the first not null-like value. var coalesce = require('extant') // Asynchronous control flow. var cadence = require('cadence') // Do nothing. var noop = require('nop') // Generate a unique, canonical string key from a JSON object. var Keyify = require('keyify') // Controlled demolition of asynchronous operations. var Destructible = require('destructible') var Cubbyhole = require('cubbyhole') var Interrupt = require('interrupt').createInterrupter('olio') var Sender = require('./sender') var http = require('http') var Downgrader = require('downgrader') var delta = require('delta') var Conduit = require('conduit') var Staccato = require('staccato') var Socket = require('procession/socket')(require('./hangup')) var Turnstile = require('turnstile') var restrictor = require('restrictor') var Keyify = require('keyify') var events = require('events') var util = require('util') function Olio (destructible, dispatcher, message) { this.destroyed = false this._destructible = destructible this._destructible.destruct.wait(this, function () { this.destroyed = true }) this._ready = dispatcher.ready this._registered = dispatcher.registered require('assert')(message.program) this.program = message.program this.name = message.name this.index = message.index this.address = message.address this.socket = message.socket this.siblings = message.siblings this._transmitter = dispatcher.transmitter this.turnstile = new Turnstile this.turnstile.listen(destructible.durable('turnstile')) destructible.destruct.wait(this.turnstile, 'destroy') destructible.destruct.wait(this, function () { this._ready.destroy(new Error('canceled')) this._registered.destroy(new Error('canceled')) }) events.EventEmitter.call(this) } util.inherits(Olio, events.EventEmitter) Olio.prototype.send = restrictor.push(cadence(function (async, envelope) { var to = { name: envelope.body.shift(), index: envelope.body.shift() } var name = envelope.body.shift() var message = envelope.body.shift() var handle = coalesce(envelope.body.shift()) if (envelope.canceled) { if (handle != null) { handle.destroy() } } else { async(function () { this._registered.wait(Keyify.stringify([ to.name, to.index ]), async()) }, function (registered) { this._transmitter.kibitz(registered.address, { name: name, body: message }, coalesce(handle)) }) } })) Olio.prototype.broadcast = restrictor.push(cadence(function (async, envelope) { if (!envelope.canceled) { var to = { name: envelope.body.shift() } var name = envelope.body.shift() var message = envelope.body.shift() async.loop([ 0 ], function (index) { if (index == this.siblings[to.name].count) { return [ async.break ] } async(function () { this._registered.wait(Keyify.stringify([ to.name, index ]), async()) }, function (registered) { this._transmitter.kibitz(registered.address, { name: name, body: message }, null) return index + 1 }) }) } })) Olio.prototype.ready = cadence(function (async, name) { this._ready.wait(name, async()) }) Olio.prototype._createSender = cadence(function (async, destructible, Receiver, message, index) { async(function () { var request = http.request({ socketPath: this.socket, url: 'http://olio/', headers: Downgrader.headers({ 'x-olio-program-name': this.program.name, 'x-olio-program-index': this.program.index, 'x-olio-to-name': message.name, 'x-olio-to-index': index, 'x-olio-from-name': this.name, 'x-olio-from-index': this.index }) }) delta(async()).ee(request).on('upgrade') request.end() }, function (request, socket, head) { async(function () { var readable = new Staccato.Readable(socket) var writable = new Staccato.Writable(socket) destructible.durable('socket', Socket, { label: 'sender', from: { name: this.name, index: this.index }, to: { name: message.name, index: index } }, readable, writable, head, async()) }, function (inbox, outbox) { var shifter = inbox.shifter() async(function () { destructible.durable('receiver', Receiver, inbox, outbox, async()) }, function (conduit) { async(function () { shifter.dequeue(async()) }, function (header) { Interrupt.assert(header.module == 'olio' && header.method == 'connect', 'failed to start middleware') Interrupt.assert(shifter.shift() == null, 'unexpected traffic on connect') // Do we do this or do we register an end to pumping instead? It // would depend on how we feal about ending a Conduit. I do // believe it pushed out a `null` to indicate that the stream // has closed. A Window would look for this and wait for // restart. The Window needs to be closed explicity. return [ conduit ] }) }) }) }) }) Olio.prototype.sender = cadence(function (async, name) { var vargs = Array.prototype.slice.call(arguments, 2) async(function () { this.ready(name, async()) }, function (sibling) { var i = 0, receivers = [] async(function () { async.loop([], function () { if (i == sibling.count) { return [ async.break ] } // TODO `message.name` instead. this._destructible.durable([ 'created', sibling.name, i ], this, '_createSender', vargs, sibling, i, async()) }, function (receiver) { receivers[i++] = receiver }) }, function () { return new Sender(receivers, sibling.addresses, sibling.count) }) }) }) // TODO You're working through this right now. // // If one of our sender creators raises an error, we are ready with the error. // Our destructible will gather up any other waiting initialzers, so that we // will still get a full bouquet of errors through the destructible. We're // turnign the corner here, you see, with an initialization stack that is // waiting on the Olio constructor and multiple senders. Ready is not waiting on // receivers, it waiting for all senders to be ready. Thus, we go forward when // we have all our senders. We can still error building a receiver, though. // (Let's put this in our unit tests.) // // Ordered thoughts. // // * We are waiting for all our senders to be ready because we cannot use the // Olio until all the senders are ready. // * Waiting on receivers is probably a function of having a countdow latch // elsewhere that is waiting for incoming signals. Not a use case I've // encountered yet though. // * An error initializing a sender will cause us to be ready, but we report // ready by passing the error. // module.exports = cadence(function (async, destructible, dispatcher, message) { return new Olio(destructible, dispatcher, message) })