├── .npmignore ├── lib ├── compat │ ├── methods │ │ ├── fibers.js │ │ ├── streaming_method.js │ │ ├── promises_shim.js │ │ ├── promises.js │ │ └── callback.js │ ├── services │ │ ├── oneoff.js │ │ └── urlfetch.js │ ├── util.js │ ├── index.js │ ├── test │ │ ├── oneoff.js │ │ ├── simple.js │ │ └── client.js │ ├── client.js │ └── service.js ├── session.js ├── worker │ ├── http.js │ ├── handles.js │ ├── http1.js │ ├── http2.js │ ├── worker.js │ ├── handles1.js │ └── handles2.js ├── protocol.js ├── channel │ ├── mp.js │ └── channel.js ├── error_emitter.js ├── client │ ├── graph.js │ ├── session.js │ ├── logger.js │ ├── service.js │ ├── base_service.js │ ├── locator.js │ └── channel.js ├── util.js ├── session2.js ├── session1.js ├── fsm.js └── errno.js ├── AUTHORS ├── .gitignore ├── test ├── test.sh ├── locator.js ├── locator_endpoints.js ├── service.js └── worker.js ├── index.js ├── sample ├── logging.js └── lingeringShutdown.js ├── package.json ├── benchmark ├── net.js ├── msgpack.js └── memcp.js ├── README.md └── COPYING /.npmignore: -------------------------------------------------------------------------------- 1 | debian 2 | -------------------------------------------------------------------------------- /lib/compat/methods/fibers.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/compat/methods/streaming_method.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 2 | Oleg Kutkov 3 | Dmitry Unkovsky 4 | 5 | -------------------------------------------------------------------------------- /lib/compat/services/oneoff.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(methods){ 3 | return { 4 | binary: false, 5 | defaultMethod: methods.oneoffStreamed, 6 | methods:{ 7 | } 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | build 16 | node_modules 17 | 18 | -------------------------------------------------------------------------------- /lib/compat/util.js: -------------------------------------------------------------------------------- 1 | 2 | exports.makeError = function(category, code, descr, stack) { 3 | var e = new Error(descr) 4 | e.category = category 5 | e.code = code 6 | if(stack){ 7 | e.stack = e.stack + stack 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | #set -x 5 | 6 | 7 | NVM=/dvl/nvm/nvm.sh 8 | VERSIONS="0.10 0.12 iojs-v1 iojs-v2 iojs-v3 4.0 4.1" 9 | 10 | source $NVM 11 | 12 | for V in $VERSIONS; do 13 | echo "using node version $V" 14 | rm -rf node_modules 15 | nvm use $V 16 | npm install 17 | npm run test 18 | done 19 | -------------------------------------------------------------------------------- /lib/session.js: -------------------------------------------------------------------------------- 1 | 2 | var v = process.version.slice(1).split('.') 3 | 4 | if(0 < parseInt(v[0]) || v[1] === '10' || v[1] === '12'){ 5 | module.exports = require('./session2') 6 | } else if(v[1] === '8'){ 7 | module.exports = require('./session1') 8 | } else { 9 | throw new Error('engine not supported: ' + process.version) 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/worker/http.js: -------------------------------------------------------------------------------- 1 | 2 | var v = process.version.slice(1).split('.') 3 | 4 | if(0 < parseInt(v[0]) || v[1] === '12'){ 5 | 6 | module.exports = require('./http2') 7 | 8 | } else if(v[1] === '10'){ 9 | 10 | module.exports = require('./http1') 11 | 12 | } else { 13 | throw new Error('engine not supported: ' + process.version) 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/worker/handles.js: -------------------------------------------------------------------------------- 1 | 2 | var v = process.version.slice(1).split('.') 3 | 4 | if(0 < parseInt(v[0]) || v[1] === '12'){ 5 | 6 | module.exports = require('./handles2') 7 | 8 | } else if(v[1] === '10'){ 9 | 10 | module.exports = require('./handles1') 11 | 12 | } else { 13 | throw new Error('engine not supported: ' + process.version) 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/protocol.js: -------------------------------------------------------------------------------- 1 | 2 | var _RPC = [ 3 | 'handshake', 4 | 'heartbeat', 5 | 'terminate', 6 | 'invoke', 7 | 'chunk', 8 | 'error', 9 | 'choke' 10 | ] 11 | 12 | var RPC = {} 13 | 14 | _RPC.map(function(m,id){ 15 | RPC[m] = id 16 | }) 17 | 18 | var TERMINATE = { 19 | normal:1, 20 | abnormal:2 21 | } 22 | 23 | var ERROR_CATEGORY = { 24 | application_error: 42 25 | } 26 | 27 | module.exports = { 28 | RPC:RPC, 29 | _RPC:_RPC, 30 | TERMINATE:TERMINATE 31 | } 32 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var argv = require('optimist').argv 3 | 4 | exports.Service = require('./lib/client/service').Service 5 | 6 | exports.Locator = require('./lib/client/locator').Locator 7 | 8 | exports.Worker = require('./lib/worker/worker').Worker 9 | 10 | Object.defineProperty(exports,'http',{ 11 | get:function(){return require('./lib/worker/http')} 12 | }) 13 | 14 | exports.spawnedBy = function(){ 15 | return argv.uuid && argv.app && argv.locator && argv.endpoint 16 | } 17 | 18 | exports.getWorkerAttrs = function(){ 19 | return { 20 | uuid: argv.uuid, 21 | app: argv.app, 22 | locator: argv.locator, 23 | endpoint: argv.endpoint 24 | } 25 | } 26 | 27 | exports.compat = require('./lib/compat') 28 | -------------------------------------------------------------------------------- /lib/compat/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports.Client = require('./client').Client 4 | 5 | module.exports.Service = require('./service').Service 6 | 7 | var argv = require('optimist').argv 8 | 9 | exports.Locator = require('../client/locator').Locator 10 | 11 | exports.Worker = require('../worker/worker').Worker 12 | 13 | Object.defineProperty(exports,'http',{ 14 | get:function(){return require('../worker/http')} 15 | }) 16 | 17 | exports.spawnedBy = function(){ 18 | return argv.uuid && argv.app && argv.locator && argv.endpoint 19 | } 20 | 21 | exports.getWorkerAttrs = function(){ 22 | return { 23 | uuid: argv.uuid, 24 | app: argv.app, 25 | locator: argv.locator, 26 | endpoint: argv.endpoint 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /sample/logging.js: -------------------------------------------------------------------------------- 1 | 2 | var Service = require('../lib/client/service').Service 3 | var locatorEndpoint = ['coke-r04-6-1.haze.yandex.net', 10053] 4 | 5 | 6 | function smoke(){ 7 | 8 | var Logger = require('../lib/client/logger').Logger 9 | 10 | var log = new Logger('some/app/poijpisdf', {locator: locatorEndpoint}) 11 | 12 | //var log = Service('logging', {locator: locatorEndpoint}) 13 | 14 | log.connect() 15 | 16 | log.on('connect', function(){ 17 | console.log('log._connected') 18 | 19 | log.emit(0, 'bla', 'blabla', [['aaapoijp-pdsoifjpaosdif', 'bbb']]) 20 | //log.emit(0, 'bla', 'blabla') 21 | 22 | setTimeout(function(){ 23 | console.log('closing') 24 | log.close() 25 | }, 1000000) 26 | 27 | }) 28 | 29 | } 30 | 31 | smoke() 32 | 33 | -------------------------------------------------------------------------------- /lib/compat/methods/promises_shim.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Q: function(Q){ 3 | return { 4 | defer: function(){ 5 | return Q.defer() 6 | }, 7 | fulfill: function(deferred, result){ 8 | return deferred.resolve(result) 9 | }, 10 | reject: function(deferred, error){ 11 | return deferred.reject(error) 12 | }, 13 | promise: function(deferred){ 14 | return deferred.promise 15 | } 16 | } 17 | }, 18 | 19 | Vow: function(Vow){ 20 | return { 21 | defer: function(){ 22 | return Vow.promise() 23 | }, 24 | fulfill: function(deferred, result){ 25 | return deferred.fulfill(result) 26 | }, 27 | reject: function(deferred, error){ 28 | return deferred.reject(error) 29 | }, 30 | promise: function(deferred){ 31 | return deferred 32 | } 33 | } 34 | } 35 | 36 | } 37 | 38 | module.exports.bluebird = module.exports.Q 39 | -------------------------------------------------------------------------------- /sample/lingeringShutdown.js: -------------------------------------------------------------------------------- 1 | 2 | var argv = require("optimist").argv 3 | var cocaine = require("cocaine").compat 4 | 5 | var worker = new cocaine.Worker(argv) 6 | var handle = worker.getListenHandle("http") 7 | var http = cocaine.http // monkey-patches http, so should be done 8 | // before require("express") 9 | 10 | var app = require('express')() 11 | var format = require('util').format 12 | var server = http.createServer(app) 13 | 14 | app.get('/linger', function(req, res){ 15 | var t = parseInt(req.query.t) 16 | var len = 0 17 | req.on("data", function(chunk){ 18 | len += chunk.length 19 | }) 20 | req.on("end", function(){ 21 | if(t){ 22 | var h = res.cocaineLingeringShutdown() 23 | setTimeout(function(){ 24 | h.close() 25 | }, t) 26 | } 27 | 28 | var m = format('got body of length %s. lingering...\n', len) 29 | res.end(m) 30 | }) 31 | }) 32 | 33 | server.listen(handle, function(){ 34 | console.log('listening on cocaine handle') 35 | }) 36 | 37 | -------------------------------------------------------------------------------- /lib/compat/test/oneoff.js: -------------------------------------------------------------------------------- 1 | 2 | var Client = require('cocaine').compat.Client 3 | 4 | //var locatorEndpoint = ['coke-r04-6-3.haze.yandex.net', 10053] 5 | //var locatorEndpoint = ['coke-r04-6-1.haze.yandex.net', 10053] 6 | var locatorEndpoint = ['localhost', 10053] 7 | 8 | var Q = require('q') 9 | 10 | var mp = require('msgpack-bin') 11 | 12 | // var promises = Client.methods.promises_shim.Q(Q) 13 | // var methods = Client.methods.promises(promises) 14 | 15 | var methods = Client.methods.callback 16 | 17 | //var client = new Client(locatorEndpoint, methods) 18 | var client = new Client(locatorEndpoint) 19 | 20 | 21 | //var log = new cocaine.Logger() 22 | 23 | var APPNAME = 'myapp' 24 | 25 | var app = client.Service(APPNAME, 'oneoff') 26 | 27 | app.connect() 28 | 29 | app.on('connect', function(){ 30 | 31 | console.log('connected!') 32 | 33 | var s = app.enqueue('alive', '{}', function(){ 34 | 35 | console.log(arguments) 36 | 37 | }) 38 | 39 | // app.enqueue('alive', '{}', function(){ 40 | // console.log(arguments) 41 | // }) 42 | 43 | }) 44 | 45 | app.on('error', function(err){ 46 | console.log('socket error', err) 47 | }) 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cocaine", 3 | "version": "0.12.1-r18", 4 | "description": "Node.js framework for Cocaine platform", 5 | "author": "Cocaine Project ", 6 | "contributors": [ 7 | "Oleg Kutkov ", 8 | "Dmitry Unkovsky " 9 | ], 10 | "homepage": "https://github.com/cocaine/cocaine-framework-nodejs", 11 | "repository": "git://github.com/cocaine/cocaine-framework-nodejs.git", 12 | "engines": { 13 | "node": "0.8.0 - 4.*" 14 | }, 15 | "dependencies": { 16 | "debug": "*", 17 | "hexy": "^0.2.6", 18 | "msgpack-bin": "0.3.0-1", 19 | "optimist": "~0.4", 20 | "q": "1.*" 21 | }, 22 | "devDependencies": { 23 | "mocha": "1.20.x", 24 | "node-uuid": "1.4.x", 25 | "co": "*", 26 | "chai": "*", 27 | "msgpack-socket": "0.0.8-a2", 28 | "babel": "*" 29 | }, 30 | "scripts": { 31 | "_postinstall": "rm -f node_modules/cocaine && ln -s .. node_modules/cocaine", 32 | "__test": "node_modules/.bin/mocha --compilers js:babel/register", 33 | "test": "ln -s .. node_modules/cocaine; mocha --compilers js:babel/register --reporter spec --timeout 600000", 34 | "_test": "mocha --compilers js:babel/register --timeout 600000 --debug-brk" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/compat/services/urlfetch.js: -------------------------------------------------------------------------------- 1 | 2 | // var jp = require('@nojs/jampack') 3 | 4 | // var P = jp([ 5 | // jp.bool, //success 6 | // jp.binary, //data 7 | // jp.int, //code 8 | // jp.map( //headers 9 | // jp.string, 10 | // jp.string) 11 | // ]) 12 | 13 | // function unpackUrlFetchChunk(buf){ 14 | // return P.parse(jp.Stream(buf)) 15 | // } 16 | 17 | function decoder(result){ 18 | var ok = result[0] 19 | var data = result[1] 20 | var code = result[2] 21 | var headers0 = result[3] 22 | 23 | var headers1 = {} 24 | for(var k in headers0){ 25 | headers1[k] = headers0[k].toString('ascii') 26 | } 27 | 28 | return [ok, data, code, headers1] 29 | } 30 | 31 | 32 | module.exports = function(methods){ 33 | return { 34 | binary: true, 35 | defaultMethod: methods.oneoff, 36 | methods:{ 37 | 38 | get: methods.oneoffWithDecoder(decoder), 39 | //get: methods.oneoff, 40 | //get: methods.unpackWith(unpackUrlFetchChunk), 41 | // (url string, 42 | // timeout int, 43 | // cookies {}, 44 | // headers {}, 45 | // follow_location bool) 46 | 47 | post: methods.oneoffWithDecoder(decoder) 48 | //post: methods.oneoff 49 | //post: methods.unpackWith(unpackUrlFetchChunk) 50 | // (url string, 51 | // body Buffer|string 52 | // timeout int, 53 | // cookies {}, 54 | // headers {}, 55 | // follow_location bool) 56 | 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /lib/channel/mp.js: -------------------------------------------------------------------------------- 1 | 2 | var __assert = require('assert') 3 | var hexy = require('hexy') 4 | var RPC = require('../protocol').RPC 5 | var mp = require('msgpack-bin') 6 | 7 | var fail = {v:'fail'} 8 | 9 | var trace = 0 10 | var debug = require('debug')('co:mp') 11 | 12 | var _ = require('util') 13 | var inspect = _.inspect 14 | var fmt = _.format 15 | 16 | 17 | function unpackMessage(buf, _RPC, invokeBoundary){ 18 | debug('function unpackMessage(buf, _RPC, invokeBoundary){', buf.slice(0,10), '{..}', invokeBoundary) 19 | if(buf.length === 0){ 20 | return null 21 | } 22 | 23 | if(!((buf[0] & 0xF0) === 0x90)){ 24 | return fail 25 | } 26 | 27 | var m = mp.unpack(buf, true) 28 | 29 | if(m === null || m === undefined){ 30 | debug('not enough bytes to unpack') 31 | return null 32 | } 33 | 34 | debug('message unpacked', m) 35 | 36 | var sessionId = m[0], methodId = m[1], args = m[2] 37 | 38 | if(invokeBoundary <= sessionId){ 39 | // RPC.invoke 40 | 41 | __assert(methodId === 0, 42 | fmt('when invokeBoundary[%s] <= sessionId[%s], methodId[%s] should be 0', 43 | invokeBoundary, sessionId, methodId)) 44 | 45 | __assert(typeof args === 'object' && args.length === 1, 46 | "typeof args === 'object' && args.length === 1") 47 | 48 | 49 | args[0] = args[0].toString('utf8') 50 | 51 | } 52 | 53 | unpackMessage.bytesParsed = buf.length - mp.unpack.bytes_remaining 54 | return m 55 | 56 | } 57 | 58 | 59 | module.exports.unpackMessage = unpackMessage 60 | module.exports.fail = fail 61 | 62 | -------------------------------------------------------------------------------- /lib/error_emitter.js: -------------------------------------------------------------------------------- 1 | 2 | var EventEmitter = require('events').EventEmitter 3 | var util = require('util') 4 | 5 | var linkProtoProps = require('./util').linkProtoProps 6 | 7 | var __assert = require('assert') 8 | 9 | module.exports.ErrorEmitter = ErrorEmitter 10 | 11 | function ErrorEmitter(){ 12 | EventEmitter.apply(this, arguments) 13 | this._errorHandler = null 14 | linkProtoProps(this, 'errorHandler') 15 | } 16 | 17 | 18 | ErrorEmitter.prototype = { 19 | __proto__: EventEmitter.prototype, 20 | 21 | beforeError: function(err){ 22 | 23 | if (typeof this._events.beforeError === 'function' 24 | || (typeof this._events.beforeError === 'object' && 25 | 0 < this._events.beforeError.length)) { 26 | 27 | var handlers = this._events.beforeError 28 | 29 | if(this.domain && this!==process){ 30 | this.domain.enter() 31 | } 32 | 33 | if(typeof handlers === 'function'){ 34 | return handlers.call(this, err) 35 | } else { 36 | __assert(typeof handlers === 'object' && 0 < handlers.length, 37 | "typeof handlers === 'object' && 0 < handlers.length") 38 | 39 | handlers0 = handlers.slice() 40 | 41 | for(var i=0; i < handlers0.length; i++){ 42 | var r = handlers0[i].call(this, err) 43 | if(r === true){ 44 | return true 45 | } 46 | } 47 | } 48 | 49 | } 50 | 51 | }, 52 | 53 | _setErrorHandler: function(hdl){ 54 | __assert(this._errorHandler === null, 'this._errorHandler === null') 55 | this._errorHandler = hdl 56 | }, 57 | 58 | _popErrorHandler: function(){ 59 | var hdl = this._errorHandler 60 | hdl && __assert(typeof hdl === 'object' && typeof hdl.length === 'number') 61 | this._errorHandler = null 62 | return hdl 63 | }, 64 | 65 | _callErrorHandler: function(hdl){ 66 | __assert(typeof hdl === 'object' && typeof hdl.length === 'number') 67 | var name = hdl.shift() 68 | var args = hdl 69 | 70 | __assert(typeof name === 'string') 71 | 72 | var errorHandler = this.errorHandlers[name] 73 | 74 | __assert(typeof errorHandler === 'function') 75 | 76 | return errorHandler.apply(this, args) 77 | 78 | } 79 | 80 | } 81 | 82 | -------------------------------------------------------------------------------- /lib/compat/methods/promises.js: -------------------------------------------------------------------------------- 1 | 2 | var CALL_TIMEOUT = 30000 3 | 4 | var __assert = require('assert') 5 | var util = require('util') 6 | 7 | var __uid = require('../../util').__uid 8 | 9 | //var streamingMethod = require('./streaming_method') 10 | 11 | var slice = Array.prototype.slice 12 | 13 | var trace = 0 14 | 15 | var debug = require('debug')('co:compat:methods:promises') 16 | 17 | module.exports = function(Promise) { 18 | 19 | function oneoff(methodName, decoder){ 20 | return function(){ 21 | var id = __uid() 22 | var args = slice.call(arguments) 23 | 24 | var debugMessage = util.format('::methodImpl[%s][%s](%s)', this._name, methodName, id, args) 25 | debug(debugMessage) 26 | 27 | var s = this._service._call(methodName, args) 28 | 29 | var d = Promise.defer() 30 | var invokationStack = new Error(debugMessage).stack 31 | 32 | s.recv({ 33 | value: function(){ 34 | var result = slice.call(arguments) 35 | 36 | debug('::methodImpl[%s][%s](%s): done -> `%s` ', this._name, methodName, id, args, result) 37 | if(decoder){ 38 | result = decoder(result) 39 | debug('using decoded result', result) 40 | } 41 | 42 | Promise.fulfill(d, result) 43 | }, 44 | error: function(ec, message){ 45 | var descr = util.format('Error at ::methodImpl[%s][%s](%s): `%s`', this._name, methodName, id, args, message) 46 | 47 | var e = new Error(descr) 48 | e.category = ec[0] 49 | e.code = ec[1] 50 | e.stack = e.stack + util.format('\n invoked from\n %s', invokationStack) 51 | 52 | debug('::methodImpl[%s][%s]: error -> `%s` ', this._name, methodName, id, message) 53 | 54 | Promise.reject(d, e) 55 | } 56 | }) 57 | 58 | return Promise.promise(d) 59 | } 60 | } 61 | 62 | 63 | return { 64 | 65 | oneoffWithDecoder: function withDecoder(decoder){ 66 | return function oneoffDecoded(methodName){ 67 | return oneoff(methodName, decoder) 68 | } 69 | }, 70 | 71 | oneoff: oneoff, 72 | streaming: function() { 73 | throw Error('Not Implemented') 74 | } 75 | } 76 | } 77 | 78 | 79 | -------------------------------------------------------------------------------- /lib/compat/test/simple.js: -------------------------------------------------------------------------------- 1 | 2 | var Client = require('cocaine').compat.Client 3 | 4 | //var locatorEndpoint = ['coke-r04-6-3.haze.yandex.net', 10053] 5 | //var locatorEndpoint = ['coke-r04-6-1.haze.yandex.net', 10053] 6 | var locatorEndpoint = ['cocs01h.tst12.ape.yandex.net', 10053] 7 | 8 | 9 | var Q = require('q') 10 | 11 | var promises = Client.methods.promises_shim.Q(Q) 12 | var methods = Client.methods.promises(promises) 13 | 14 | var client = new Client(locatorEndpoint, methods) 15 | 16 | 17 | //var log = new cocaine.Logger() 18 | 19 | 20 | var urlfetch = client.Service('urlfetch') 21 | 22 | 23 | urlfetch.connect() 24 | 25 | urlfetch.on('connect', function(){ 26 | 27 | console.log('connected!') 28 | 29 | var url = 'http://yastatic.net/encyc/articles_ng/pharma/7/71/paratsetamol.xml'; 30 | 31 | var rr = [] 32 | 33 | for(var i=0;i<10;i++){ 34 | rr[i] = urlfetch.get(url, 2000, {}, {}, true) 35 | } 36 | 37 | rr.map(function(r,i){ 38 | r.then(function(){ 39 | console.log('done', i) 40 | }) 41 | }) 42 | 43 | var t0 = Date.now(), t1=0 44 | 45 | Q.all(rr) 46 | .then(function(results){ 47 | 48 | results.map(function(result){ 49 | 50 | var [ok, data, code, headers] = result 51 | 52 | if(!ok){ 53 | console.log('failed to fetch url %s', url, result) 54 | return 55 | } 56 | 57 | console.log('reply: ', data) 58 | 59 | }) 60 | 61 | }) 62 | .then(function(){ 63 | t1 = Date.now() 64 | 65 | console.log('================================') 66 | console.log('total time %sms', t1-t0) 67 | 68 | urlfetch.close() 69 | 70 | }) 71 | .fail(function(err){ 72 | console.log('fail', err) 73 | console.log(err.stack) 74 | }) 75 | 76 | }) 77 | 78 | urlfetch.on('error', function(err){ 79 | console.log('urlfetch error', err) 80 | }) 81 | 82 | 0 && setInterval(function(){ 83 | 84 | console.log('================================================================') 85 | console.log('gc run') 86 | console.log(process.memoryUsage()) 87 | console.log('----------------------------------------------------------------') 88 | global.gc() 89 | console.log(process.memoryUsage()) 90 | console.log('----------------------------------------------------------------') 91 | 92 | }, 10000) 93 | 94 | -------------------------------------------------------------------------------- /lib/compat/test/client.js: -------------------------------------------------------------------------------- 1 | 2 | var Client = require('cocaine').compat.Client 3 | 4 | //var locatorEndpoint = ['coke-r04-6-3.haze.yandex.net', 10053] 5 | var locatorEndpoint = ['coke-r04-6-1.haze.yandex.net', 10053] 6 | 7 | var Q = require('q') 8 | 9 | var promises = Client.methods.promises_shim.Q(Q) 10 | var methods = Client.methods.promises(promises) 11 | 12 | var client = new Client(locatorEndpoint, methods) 13 | 14 | 15 | //var log = new cocaine.Logger() 16 | 17 | client.getServices(['urlfetch', 'logging'], function(err, urlfetch, log){ 18 | 19 | if(err){ 20 | console.log('error connecting to some of the services', err) 21 | return 22 | } 23 | 24 | console.log('connected!') 25 | 26 | log.error({any: 'thing', some: 'place', a: 10}, 'message %s ololo %s',18,47) 27 | 28 | var url = 'http://yastatic.net/encyc/articles_ng/pharma/7/71/paratsetamol.xml'; 29 | 30 | var rr = [] 31 | 32 | for(var i=0;i<100;i++){ 33 | rr[i] = urlfetch.get(url, 2000, {}, {}, true) 34 | } 35 | 36 | rr.map(function(r,i){ 37 | r.then(function(){ 38 | console.log('done', i) 39 | }) 40 | }) 41 | 42 | var t0 = Date.now(), t1=0 43 | 44 | Q.all(rr) 45 | .then(function(results){ 46 | 47 | results.map(function(result){ 48 | 49 | var [ok, data, code, headers] = result 50 | 51 | if(!ok){ 52 | console.log('failed to fetch url %s', url, result) 53 | return 54 | } 55 | 56 | console.log('reply: ', data) 57 | 58 | }) 59 | 60 | }) 61 | .then(function(){ 62 | t1 = Date.now() 63 | 64 | console.log('================================') 65 | console.log('total time %sms', t1-t0) 66 | 67 | //urlfetch.close() 68 | 69 | }) 70 | .fail(function(err){ 71 | console.log('fail', err) 72 | console.log(err.stack) 73 | }) 74 | 75 | urlfetch.on('error', function(err){ 76 | console.log('urlfetch error', err) 77 | }) 78 | 79 | // setInterval(function(){ 80 | 81 | // console.log('================================================================') 82 | // console.log('gc run') 83 | // console.log(process.memoryUsage()) 84 | // console.log('----------------------------------------------------------------') 85 | // global.gc() 86 | // console.log(process.memoryUsage()) 87 | // console.log('----------------------------------------------------------------') 88 | 89 | // }, 10000) 90 | 91 | 92 | }) 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /lib/client/graph.js: -------------------------------------------------------------------------------- 1 | 2 | var util = require('util') 3 | 4 | var __stop = {value:''} 5 | 6 | 7 | function txFacets(graph){ 8 | 9 | var method = {} 10 | var transition = {} 11 | 12 | for(var idx in graph){ 13 | var idx0 = parseInt(idx) 14 | var methodName = graph[idx][0] 15 | var graph1 = graph[idx][1] 16 | 17 | method[methodName] = idx0 18 | 19 | if(graph1 === null){ 20 | transition[methodName] = null 21 | } else if(Object.keys(graph1).length === 0) { 22 | transition[methodName] = __stop 23 | } else { 24 | transition[methodName] = txFacets(graph1) 25 | } 26 | 27 | } 28 | 29 | return {method: method, transition: transition} 30 | 31 | } 32 | 33 | function rxFacets(graph){ 34 | 35 | var method = {} 36 | var transition = [] 37 | 38 | for(var idx in graph){ 39 | var idx0 = parseInt(idx) 40 | var methodName = graph[idx][0] 41 | var graph1 = graph[idx][1] 42 | 43 | method[methodName] = idx0 44 | 45 | if(graph1 === null){ 46 | transition[idx0] = null 47 | } else if(Object.keys(graph1).length === 0) { 48 | transition[idx0] = __stop 49 | } else { 50 | transition[idx0] = rxFacets(graph1) 51 | } 52 | 53 | } 54 | 55 | return {method: method, transition: transition} 56 | 57 | } 58 | 59 | 60 | function smoke(){ 61 | 62 | var input = { 63 | 0:['write',null], 64 | 1:['error',{}], 65 | 2:['close',{}], 66 | 3:['flow1',{ 67 | 0:['cancel', {}], 68 | 1:['error', {}], 69 | 2:['next', null], 70 | 3:['switch1', { 71 | 0:['next1',null], 72 | 1:['cancel1',{}] 73 | }], 74 | 4:['switch2', { 75 | 0:['next2', null], 76 | 1:['cancel2', {}] 77 | }] 78 | }], 79 | 4:['flow2',{ 80 | 0:['cancel', {}], 81 | 1:['error', {}], 82 | 2:['next', null] 83 | }] 84 | } 85 | 86 | var out = txFacets(input) 87 | 88 | console.log('==== txFacets ================') 89 | console.log(util.inspect(out, {depth: null})) 90 | 91 | var out = rxFacets(input) 92 | console.log('==== rxFacets ================') 93 | console.log(util.inspect(out, {depth: null})) 94 | 95 | return out 96 | 97 | } 98 | 99 | module.exports = { 100 | facets: txFacets, 101 | rxFacets: rxFacets, 102 | txFacets: txFacets, 103 | smoke: smoke, 104 | __stop: __stop 105 | } 106 | 107 | if(!module.parent){ 108 | 109 | smoke() 110 | } 111 | 112 | 113 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 2 | var __assert = require('assert') 3 | 4 | var _ERRNO = require('./errno').code 5 | var ERRNO = require('./errno').errno 6 | var format = require('util').format 7 | 8 | 9 | module.exports = { 10 | __uid: function(){ 11 | return Math.floor(Math.random()*0x100000000).toString(36) 12 | }, 13 | makeError: function(errno, message){ 14 | message = message || _ERRNO[errno] || 'unknown' 15 | var e = new Error(message) 16 | e.code = _ERRNO[errno] 17 | return e 18 | }, 19 | bindHandlers:function(handlers,_handlers,self){ 20 | for(var fn in handlers){ 21 | if(typeof handlers[fn] === 'function'){ 22 | _handlers[fn] = handlers[fn].bind(self) 23 | } 24 | } 25 | }, 26 | setHandlers:function(wrap,handlers){ 27 | for(var fn in handlers){ 28 | wrap[fn] = handlers[fn] 29 | } 30 | }, 31 | unsetHandlers:function(wrap,handlers){ 32 | for(var fn in handlers){ 33 | delete wrap[fn] 34 | } 35 | }, 36 | debug:function(subsys){ 37 | var re = new RegExp(subsys) 38 | if(process.env.NODE_DEBUG && re.test(process.env.NODE_DEBUG)){ 39 | return function(){ 40 | var entry = format.apply(null, arguments) 41 | console.log('%s:', subsys, entry) 42 | } 43 | } else { 44 | return function(){} 45 | } 46 | }, 47 | 48 | // link in proto chain `.prop` subobjects in 49 | // `self` and all it's proto-predecessors 50 | linkProtoProps: function(self, prop){ 51 | __assert(typeof self === 'object' && typeof prop === 'string', 52 | "typeof self === 'object' && typeof prop === 'string'") 53 | 54 | var p0, p1 55 | 56 | p0 = self 57 | // find first proto with prop 58 | while(!p0.hasOwnProperty(prop)){ 59 | // we need to go deeper: 60 | p0 = p0.__proto__ 61 | if(p0 === null){ 62 | return 63 | } 64 | } 65 | 66 | propVal0 = p0[prop] 67 | if(typeof propVal0 !== 'object' || propVal0 === null){ 68 | return 69 | } 70 | 71 | p1 = p0.__proto__ 72 | 73 | while(p1 !== null){ 74 | 75 | if(p1.hasOwnProperty(prop)){ 76 | var propVal1 = p1[prop] 77 | if(typeof propVal1 === 'object' && propVal1 !== null){ 78 | // link 79 | propVal0.__proto__ = propVal1 80 | 81 | // advance state 82 | p0 = p1 83 | propVal0 = propVal1 84 | 85 | // go deeper 86 | p1 = p0.__proto__ 87 | 88 | } else { 89 | break 90 | } 91 | } else { 92 | 93 | p1 = p1.__proto__ 94 | } 95 | } 96 | }, 97 | 98 | fmt: format 99 | } 100 | 101 | -------------------------------------------------------------------------------- /lib/session2.js: -------------------------------------------------------------------------------- 1 | 2 | var __assert = require('assert') 3 | var mp = require('msgpack-bin') 4 | var Duplex = require('stream').Duplex 5 | 6 | var protocol = require('./protocol') 7 | var RPC = protocol.RPC 8 | var ERROR_CATEGORY = protocol.ERROR_CATEGORY 9 | 10 | var util = require('./util') 11 | 12 | var dbg = 0 13 | var debug = require('debug')('co:session2') 14 | 15 | function Session(){ 16 | this._id = null 17 | this.owner = null 18 | this._hdl = {} 19 | this._RPC = RPC 20 | Duplex.call(this) 21 | util.bindHandlers(this.hdl,this._hdl,this) 22 | } 23 | 24 | Session.prototype = { 25 | __proto__:Duplex.prototype, 26 | 27 | _setProtocol: function(RPC){ 28 | this._RPC = RPC 29 | }, 30 | // using .push() provided by Duplex 31 | 32 | pushChunk:function(chunk){ 33 | debug('pushChunk', chunk) 34 | __assert(Buffer.isBuffer(chunk), 'Buffer.isBuffer(chunk)') 35 | this.push(chunk) 36 | }, 37 | pushChoke:function(){ 38 | debug('pushChoke') 39 | __assert(!this.choked) 40 | this.choked = true 41 | this.push(null) 42 | }, 43 | pushError:function(code,message){ 44 | debug('pushError', code, message) 45 | var e = new Error(message) 46 | e.code = code 47 | this.emit('error',e) 48 | this.close() 49 | }, 50 | 51 | _read:function(n){ 52 | // start reading. a no-op, in fact 53 | }, 54 | 55 | _write:function(chunk,encoding,cb){ 56 | debug('session._write', chunk) 57 | if(Buffer.isBuffer(chunk)){ 58 | //var msg = mp.pack([this._id,this._RPC.chunk,[chunk]]) 59 | var msg = mp.pack([this._id, 0, [chunk]]) 60 | } else { 61 | __assert(typeof chunk === 'string' 62 | && typeof encoding === 'string') 63 | //var msg = mp.pack([this._id,this._RPC.chunk,[new Buffer(chunk,encoding)]]) 64 | var msg = mp.pack([this._id, 0, [new Buffer(chunk,encoding)]]) 65 | } 66 | this.owner._handle.send(msg) 67 | cb() 68 | }, 69 | 70 | end:function(){ 71 | debug('session.end') 72 | var r = Duplex.prototype.end.apply(this,arguments) 73 | //this.owner._handle.send(mp.pack([this._id,this._RPC.choke,[]])) 74 | this.owner._handle.send(mp.pack([this._id, 2, []])) 75 | return r 76 | }, 77 | 78 | error:function(code,message){ 79 | var hdl = this.owner._handle 80 | //hdl.send(mp.pack([this._id,this._RPC.error,[code,message]])) 81 | hdl.send(mp.pack([this._id, 1, [[ERROR_CATEGORY.application_error, code], message]])) 82 | this.close() 83 | }, 84 | 85 | close:function(){ 86 | this._closed = true 87 | this.emit('close') 88 | }, 89 | 90 | destroy:function(){ 91 | if(this.owner){ 92 | delete this.owner._sessions[this._id] 93 | !this._closed && this.close() 94 | } 95 | } 96 | 97 | } 98 | 99 | module.exports = { 100 | Session:Session 101 | } 102 | 103 | 104 | -------------------------------------------------------------------------------- /lib/session1.js: -------------------------------------------------------------------------------- 1 | 2 | var __assert = require('assert') 3 | var mp = require('msgpack-bin') 4 | var Stream = require('stream') 5 | 6 | var protocol = require('./protocol') 7 | var RPC = protocol.RPC 8 | 9 | var dbg = 0 10 | 11 | function Session(){ 12 | this._id = null 13 | this.owner = null 14 | this.chunks = [] 15 | this.readable = this.writable = true 16 | this.paused = false 17 | this.choked = false 18 | this._RPC = RPC 19 | } 20 | 21 | 22 | Session.prototype = { 23 | __proto__:Stream.prototype, 24 | 25 | _setProtocol: function(RPC){ 26 | this._RPC = RPC 27 | }, 28 | 29 | pause:function(){ 30 | this.paused = true 31 | }, 32 | resume:function(){ 33 | if(!this.paused) return; 34 | var c 35 | this.paused = false; 36 | while(!this.paused && c = this.chunks.shift()){ 37 | if(c === null){ 38 | this.readable = false 39 | this.emit('end') 40 | } else { 41 | __assert(this.readable) 42 | this.emit('data',c) 43 | } 44 | } 45 | }, 46 | 47 | get closed() { 48 | return !this.readable && !this.writable 49 | }, 50 | 51 | pushChunk:function(chunk){ 52 | this.push(chunk) 53 | }, 54 | pushChoke:function(){ 55 | __assert(this.readable && !this.choked) 56 | this.choked = true 57 | if(this.paused){ 58 | this.chunks.push(null) 59 | } else { 60 | this.readable = false 61 | this.emit('end') 62 | } 63 | }, 64 | pushError:function(code,message){ 65 | var e = new Error(message) 66 | e.code = code 67 | this.emit('error',e) 68 | this.close() 69 | }, 70 | 71 | push:function(chunk){ 72 | __assert(this.readable && !this.choked) 73 | dbg && console.log('chunk:',chunk, typeof chunk) 74 | if(this.paused){ 75 | this.chunks.push(chunk) 76 | } else { 77 | this.emit('data',chunk) 78 | } 79 | }, 80 | 81 | write:function(data){ 82 | __assert(!this.closed) 83 | if(Buffer.isBuffer(data)){ 84 | //var msg = mp.pack([this._id,this._RPC.chunk,[data]]) 85 | var msg = mp.pack([this._id, 0, [data]]) 86 | } else { 87 | __assert(typeof data === 'string') 88 | //var msg = mp.pack([this._id,this._RPC.chunk,[Buffer(data)]]) 89 | var msg = mp.pack([this._id, 0, [Buffer(data)]]) 90 | } 91 | var hdl = this.owner._handle 92 | hdl.send(msg) 93 | }, 94 | end:function(data){ 95 | __assert(!this.closed) 96 | if(data){ 97 | this.write(data) 98 | } 99 | this.writable = false 100 | var hdl = this.owner._handle 101 | //hdl.send(mp.pack([this._id,this._RPC.choke,[]])) 102 | hdl.send(mp.pack([this._id, 2, []])) 103 | this.close() 104 | }, 105 | error:function(code,message){ 106 | var hdl = this.owner._handle 107 | __assert(typeof code === 'number' && 108 | typeof message === 'string') 109 | //hdl.send(mp.pack([this._id,this._RPC.error,[code,message]])) 110 | hdl.send(mp.pack([this._id, 1, [[ERROR_CATEGORY.application_error, code], message]])) 111 | this.close() 112 | }, 113 | 114 | close:function(){ 115 | this.readable = this.writable = false 116 | this.emit('close') 117 | }, 118 | 119 | destroy:function(){ 120 | if(this.owner){ 121 | delete this.owner._sessions[this._id] 122 | !this.closed && this.close() 123 | } 124 | } 125 | 126 | } 127 | 128 | module.exports = { 129 | Session:Session 130 | } 131 | 132 | 133 | -------------------------------------------------------------------------------- /lib/fsm.js: -------------------------------------------------------------------------------- 1 | 2 | var __assert = require('assert') 3 | var format = require('util').format 4 | var ErrorEmitter = require('./error_emitter').ErrorEmitter 5 | 6 | var util = require('./util') 7 | 8 | var trace = 0 9 | 10 | function define(def){ 11 | function FSM(){ 12 | ErrorEmitter.apply(this, arguments) 13 | def.methods.__init__ && def.methods.__init__.apply(this, arguments) 14 | this._state = def.startState || 'start' 15 | this._handle = null 16 | this._error = null 17 | this.__sid = 1 18 | } 19 | 20 | var proto = FSM.prototype = { 21 | __proto__: FSMPrototype, 22 | __fsmdef: def.states, 23 | _emit: ErrorEmitter.prototype.emit, 24 | _beforeError: ErrorEmitter.prototype.beforeError 25 | } 26 | 27 | if(def.handlers){ 28 | proto.handlers = def.handlers 29 | } 30 | 31 | if(def.errorHandlers){ 32 | proto.errorHandlers = def.errorHandlers 33 | } 34 | 35 | for(var methodName in def.methods){ 36 | proto[methodName] = def.methods[methodName] 37 | } 38 | 39 | for(var state in def.states){ 40 | var stateDef = def.states[state] 41 | for(var methodName in stateDef.methods){ 42 | if(!(methodName in proto)){ 43 | proto[methodName] = makeMethod(methodName) 44 | } 45 | } 46 | } 47 | 48 | return FSM 49 | 50 | function makeMethod(name){ 51 | return function method(){ 52 | trace && console.log(': calling method %s', this._state, name) 53 | var stateDef = this.__fsmdef[this._state] 54 | if(trace){ 55 | __assert(stateDef, format('current is not defined', this._state)) 56 | if(stateDef.invariant){ 57 | __assert(stateDef.invariant.call(this), 58 | format( 59 | ' invariant doesn\'t hold: %s', 60 | this._state, stateDef.invariant))} 61 | __assert(typeof stateDef.methods[name] === 'function', 62 | format( 63 | ': no method %s ', 64 | this._state, name))} 65 | var method = stateDef.methods[name] 66 | __assert(typeof method === 'function', 67 | format(': no method %s for state', 68 | this._state, name)) 69 | return method.apply(this, arguments) 70 | } 71 | } 72 | 73 | } 74 | 75 | var FSMPrototype = { 76 | __proto__: ErrorEmitter.prototype, 77 | 78 | _makeError: function(errno, message){ 79 | var e = util.makeError(errno, message) 80 | e.state = this._state 81 | return e 82 | }, 83 | 84 | _setState: function(state){ 85 | trace && console.log('state: %s->%s', this._state, state) 86 | if(this._state === state) return 87 | var stateDef0 = this.__fsmdef[this._state] 88 | var stateDef1 = this.__fsmdef[state] 89 | __assert(stateDef0, format('current is not defined', this._state)) 90 | __assert(stateDef1, format('new is not defined', state)) 91 | if(!this._handle){ 92 | trace && console.log('this._handle false-ish') 93 | __assert(!stateDef1.handlers, 94 | format(' -> : handlers defined for null handle',this._state, state)) 95 | this._state = state 96 | } else { 97 | trace && console.log('this._handle true-ish') 98 | this._state = state 99 | trace && console.log('unsetting handlers', stateDef0.handlers) 100 | util.unsetHandlers(this._handle, stateDef0.handlers) 101 | trace && console.log('setting handlers', stateDef1.handlers) 102 | util.setHandlers(this._handle, stateDef1.handlers) 103 | } 104 | } 105 | 106 | } 107 | 108 | module.exports = { 109 | define: define 110 | } 111 | 112 | -------------------------------------------------------------------------------- /benchmark/net.js: -------------------------------------------------------------------------------- 1 | 2 | var co = require('co') 3 | var Q = require('q') 4 | 5 | var assert = require('assert') 6 | var fmt = require('util').format 7 | 8 | var fs = require('fs') 9 | 10 | function ntime(){ 11 | var sn = process.hrtime() 12 | return sn[0]*1e9+sn[1] 13 | } 14 | 15 | 16 | var mkSocketPair = require('@nojs/msgpack-socket/pair').mkSocketPair 17 | 18 | function *copy_over_unix_socket(){ 19 | 20 | var result = Q.defer() 21 | 22 | var cs = yield mkSocketPair() 23 | 24 | console.log('pair ready') 25 | 26 | var b = new Buffer(1024*1024) 27 | 28 | var receivedBytes = 0 29 | 30 | 31 | cs[1].on('data', function(data){ 32 | console.log('received', data.length) 33 | receivedBytes += data.length 34 | }) 35 | 36 | cs[1].on('end', function(){ 37 | var t1 = ntime() 38 | 39 | console.log('transferred %s Mb in %s, %sMb/s', receivedBytes/1024/1024, (t1-t0)/1e9, receivedBytes/(t1-t0)*(1e9/1024/1024)) 40 | 41 | result.resolve(['transferred %s Mb in %s, %sMb/s', receivedBytes/1024/1024, (t1-t0)/1e9, receivedBytes/(t1-t0)*(1e9/1024/1024)]) 42 | 43 | }) 44 | 45 | cs[1]._readableState.highWaterMark = 1024*1024 46 | 47 | var t0 = ntime() 48 | 49 | var N = 800 50 | 51 | cs[0].write(b) 52 | 53 | cs[0].on('drain', function(){ 54 | var r = false 55 | if(0::methodImpl[%s][%s](%s)', this._name, methodName, id, args) 32 | debug(debugMessage) 33 | 34 | var s = this._service._call(methodName, args) 35 | s.recv({ 36 | value: function(){ 37 | var result = slice.call(arguments) 38 | debug('::methodImpl[%s][%s](%s): done -> `%s` ', this._name, methodName, id, args, result) 39 | if(decoder){ 40 | result = decoder(result) 41 | debug('using decoded result', result) 42 | } 43 | 44 | result.unshift(null) // error 45 | cb && cb.apply(null, result) 46 | }, 47 | error: function(ec, message){ 48 | var descr = util.format('::methodImpl[%s][%s](%s): `%s`', this._name, methodName, id, args, message) 49 | 50 | var e = new Error(descr) 51 | e.category = ec[0] 52 | e.code = ec[1] 53 | e.stack = e.stack + util.format('\n invoked from\n %s', invokationStack) 54 | 55 | debug('::methodImpl[%s][%s](%s): error -> `%s` ', this._name, methodName, id, args, message) 56 | 57 | cb && cb(e) 58 | } 59 | }) 60 | } 61 | } 62 | 63 | function oneoffStreamed(methodName, decoder){ 64 | return function(){ 65 | var id = __uid() 66 | var args = slice.call(arguments) 67 | 68 | if(0 < args.length && typeof args[args.length-1] === 'function'){ 69 | var cb = args.pop() 70 | } 71 | 72 | var debugMessage = util.format('::methodImpl[%s][%s](%s)', this._name, methodName, id, args) 73 | debug(debugMessage) 74 | 75 | var chunk = args.pop() 76 | 77 | __assert(typeof chunk === 'string' || Buffer.isBuffer(chunk), 'typeof chunk === string || Buffer.isBuffer(chunk)') 78 | 79 | var done = false 80 | var result 81 | 82 | var s = this._service._call(methodName, args) 83 | 84 | var serviceName = this._service._name 85 | 86 | s.once('error', function(err){ 87 | done = true 88 | cb(err) 89 | }) 90 | 91 | s.recv({ 92 | write: function(chunk){ 93 | if(done){ 94 | var message = 'more than one chunk in response' 95 | var descr = util.format('::methodImpl[%s][%s](%s): `%s`', this._name, methodName, id, args, '') 96 | 97 | var e = _util.makeError(0, errno.errno.EBADMSG, descr, util.format('\n invoked from\n %s', invokationStack)) 98 | 99 | debug('::methodImpl[%s][%s](%s): error -> `%s` ', serviceName, methodName, id, args, message) 100 | 101 | cb && cb.apply(null, e) 102 | 103 | } else { 104 | done = true 105 | result = [chunk] 106 | } 107 | }, 108 | close: function(){ 109 | debug('::methodImpl[%s][%s](%s): done -> `%s` ', serviceName, methodName, id, args, result) 110 | if(decoder){ 111 | result = decoder(result) 112 | debug('using decoded result', result) 113 | } 114 | 115 | result.unshift(null) // error 116 | cb && cb.apply(null, result) 117 | }, 118 | error: function(ec, message){ 119 | var descr = util.format('::methodImpl[%s][%s](%s): `%s`', serviceName, methodName, id, args, message) 120 | 121 | var e = _util.makeError(ec[0], ec[1], descr, util.format('\n invoked from\n %s', invokationStack)) 122 | 123 | DEBUG('::methodImpl[%s][%s](%s): error -> `%s` ', serviceName, methodName, id, args, message) 124 | 125 | cb && cb(e) 126 | } 127 | }) 128 | 129 | s.send.write(chunk) 130 | s.send.close() 131 | } 132 | } 133 | 134 | 135 | module.exports = { 136 | oneoffWithDecoder: function withDecoder(decoder){ 137 | return function oneoffDecoded(methodName){ 138 | return oneoff(methodName, decoder) 139 | } 140 | }, 141 | 142 | oneoff: oneoff, 143 | 144 | oneoffStreamed: oneoffStreamed, 145 | 146 | oneoffStreamedWithDecoder: function withDecoder(decoder){ 147 | return function oneoffStreamedDecoded(methodName){ 148 | return oneoffStreamed(methodName, decoder) 149 | } 150 | }, 151 | 152 | streaming: function() { 153 | throw Error('Not Implemented') 154 | } 155 | } 156 | 157 | 158 | -------------------------------------------------------------------------------- /benchmark/msgpack.js: -------------------------------------------------------------------------------- 1 | 2 | var mp = require('msgpack-bin') 3 | 4 | var assert = require('assert') 5 | var fmt = require('util').format 6 | 7 | function ntime(){ 8 | var sn = process.hrtime() 9 | return sn[0]*1e9+sn[1] 10 | } 11 | 12 | 13 | 14 | 15 | function read_js_1_branches(storage, chunk, N){ 16 | 17 | assert(storage.length % chunk.length === 0, fmt('storage.length`%s` % chunk.length`%s` === 0', storage.length, chunk.length)) 18 | 19 | var rr = [0,1,2,3,4,5,6,7] 20 | 21 | var m = 0 22 | 23 | while(0::methodImpl[%s](%s)', this._name, name, arguments) 168 | 169 | var args = slice.call(arguments) 170 | var s = this._service._call(name, args) 171 | 172 | var d = Q.defer() 173 | 174 | s.recv({ 175 | value: function(){ 176 | var args = slice.call(arguments) 177 | d.resolve(args) 178 | }, 179 | error: function(ec, message){ 180 | var e = new Error(message) 181 | e.code = ec[1] 182 | d.reject(e) 183 | } 184 | }) 185 | 186 | return d.promise 187 | } 188 | 189 | return methodImpl 190 | } 191 | 192 | 193 | exports.Service = ServiceFactory 194 | 195 | -------------------------------------------------------------------------------- /lib/worker/http1.js: -------------------------------------------------------------------------------- 1 | 2 | var mp = require('msgpack-bin') 3 | 4 | var http = module.exports = require('http') 5 | 6 | var __assert = require("assert") 7 | 8 | var debug = require('debug')('co:http') 9 | 10 | var httpServer = http.Server 11 | 12 | function Server(){ 13 | httpServer.apply(this,arguments) 14 | this.httpAllowHalfOpen = true 15 | } 16 | 17 | var X_COCAINE_HTTP_PROTO_VERSION = "X-Cocaine-HTTP-Proto-Version"; 18 | 19 | module.exports.Server = Server 20 | module.exports.createServer = function(requestListener){ 21 | return new Server(requestListener) 22 | } 23 | 24 | Server.prototype = httpServer.prototype 25 | 26 | var ServerResponse = http.ServerResponse 27 | 28 | if(!http.ServerResponse.__cocaine_patched){ 29 | http.ServerResponse.__cocaine_patched = true; 30 | 31 | // var write = ServerResponse.prototype.write 32 | // var end = ServerResponse.prototype.end 33 | var SR_writeHead = ServerResponse.prototype.writeHead 34 | // var _send = ServerResponse.prototype._send 35 | // var _writeRaw = ServerResponse.prototype._writeRaw 36 | // var _buffer = ServerResponse.prototype._buffer 37 | 38 | // var storeHeader = ServerResponse.prototype.storeHeader 39 | var SR__storeHeader = ServerResponse.prototype._storeHeader 40 | 41 | 42 | ServerResponse.prototype.write = function (chunk,encoding){ 43 | if(!this._header){ 44 | this._implicitHeader(); 45 | } 46 | 47 | if(!this._hasBody){ 48 | debug('This type of response MUST NOT have a body. ' + 49 | 'Ignoring write() calls.'); 50 | return true; 51 | } 52 | 53 | if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)){ 54 | throw new TypeError('first argument must be a string or Buffer'); 55 | } 56 | 57 | if(chunk.length === 0){ 58 | return true; 59 | } 60 | 61 | var len, ret; 62 | this._send(chunk, encoding); 63 | 64 | debug('write ret = ' + ret); 65 | return ret; 66 | 67 | } 68 | 69 | ServerResponse.prototype.end = function(data, encoding) { 70 | debug('SR.end') 71 | var res = true 72 | if(this.finished){ 73 | debug('-- SR.finished', this.finished) 74 | return false; 75 | } 76 | if(!this._header){ 77 | debug('-- SR.implicit header') 78 | this._implicitHeader(); 79 | } 80 | 81 | if(data && !this._hasBody){ 82 | debug('This type of response MUST NOT have a body. '+ 83 | 'Ignoring data passed to end().'); 84 | data = false; 85 | } 86 | 87 | if(data){ 88 | debug('-- data present, so writing data') 89 | res = this.write(data, encoding); 90 | } else { 91 | debug('-- no data present, triggering send') 92 | this._send(''); 93 | } 94 | 95 | this.finished = true; 96 | this._last = true; 97 | 98 | debug('this.output.length === 0 && this.connection._httpMessage === this', this.output, this.connection._httpMessage === this) 99 | if(this.output.length === 0 && this.connection._httpMessage === this){ 100 | this._finish() 101 | } 102 | 103 | return res 104 | 105 | } 106 | 107 | ServerResponse.prototype.writeHead = function(statusCode) { 108 | this.__statusCode = statusCode; 109 | return SR_writeHead.apply(this,arguments); 110 | } 111 | 112 | ServerResponse.prototype._storeHeader = function(firstLine, headers) { 113 | SR__storeHeader.apply(this,arguments) 114 | var code = this.__statusCode || this.statusCode; 115 | headers = headers || { date : new Date().toUTCString()} 116 | var hh = Object.keys(headers).map(function(k){ 117 | return [k,headers[k].toString()]; 118 | }); 119 | this._header = mp.pack([code,hh]); 120 | this._headerSent = false; 121 | } 122 | 123 | ServerResponse.prototype._send = function(data, encoding, oncomplete){ 124 | if(!this._headerSent){ 125 | this._writeRaw(this._header); 126 | this._headerSent = true; 127 | } 128 | this._writeRaw(data,encoding); 129 | } 130 | 131 | ServerResponse.prototype.cocaineLingeringShutdown = function(){ 132 | 133 | var h = this.socket._handle; 134 | __assert(h && h.cocaineLingeringShutdownClose, "should be called on active request with attached cocaine handle"); 135 | 136 | this.setHeader(X_COCAINE_HTTP_PROTO_VERSION, "1.1"); 137 | 138 | h.setCocaineLingeringShutdown(true); 139 | return { 140 | close: function(){ 141 | h.cocaineLingeringShutdownClose(); 142 | } 143 | }; 144 | } 145 | 146 | //ServerResponse.prototype._writeRaw 147 | //ServerResponse.prototype._implicitHeader 148 | 149 | 150 | ServerResponse.prototype.addTrailers = function(headers){ 151 | throw new Error('.addTrailers not supported'); 152 | } 153 | 154 | ServerResponse.prototype.writeContinue = function(){ 155 | //be a nop: 156 | //throw new Error('.writeContinue not supported'); 157 | } 158 | 159 | //OutgoingMessage.prototype._buffer = function(data, encoding) 160 | //OutgoingMessage.prototype.setHeader = function(name, value) 161 | //OutgoingMessage.prototype.getHeader = function(name) 162 | //OutgoingMessage.prototype.removeHeader = function(name) 163 | //OutgoingMessage.prototype._renderHeaders = function() 164 | 165 | //OutgoingMessage.prototype._finish = function() 166 | //OutgoingMessage.prototype._flush = function() 167 | 168 | //ServerResponse.prototype.statusCode = 200; 169 | //ServerResponse.prototype.assignSocket = function(socket) 170 | //ServerResponse.prototype.detachSocket = function(socket) 171 | } 172 | 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Cocaine NodeJS Framework 3 | 4 | ## Examples of usage 5 | 6 | ### Create NodeJS app for Cocaine cloud 7 | 8 | Let's start with simple NodeJS http application. 9 | 10 | ```js 11 | var http = require('http') 12 | 13 | var server = new http.Server(function(req, res){ 14 | var body = [] 15 | req.on('data', function(data){ 16 | body.push(data) 17 | }) 18 | req.on('end', function(){ 19 | res.writeHead(200, { 20 | 'x-any-header': 'x-any-value', 21 | 'content-type': 'text/plain' 22 | }) 23 | res.end('hello, Cocaine!') 24 | }) 25 | }) 26 | 27 | server.listen(8080) 28 | ``` 29 | 30 | To get our app working in Cocaine cloud, let's add just a couple of things. 31 | 32 | ```js 33 | #!/path/to/node 34 | var cocaine = require('cocaine') 35 | var http = cocaine.http // monkey-patches node's original http server 36 | 37 | var argv = require('optimist').argv //which is actually a hash 38 | // looking like { opt: 'value'} 39 | 40 | var worker = new cocaine.Worker(argv) 41 | 42 | var handle = worker.getListenHandle("http") // the handle implements a 43 | // low-level nodejs' listening tcp socket, and it makes nodejs 44 | // understand cocaine streams. 45 | 46 | var server = new http.Server(...) // the same thing as above 47 | 48 | server.listen(handle) // as per [1], start listening on cocaine handle 49 | ``` 50 | 51 | To let the cocaine-runtime know what to run in our app, we put 52 | manifest.json: 53 | 54 | ```js 55 | { "slave":"app.js" } 56 | ``` 57 | 58 | Since the app.js has to be an executable, we put shebang on first line 59 | and don't forget about setting an executable bit. 60 | 61 | See the complete app here [2]. 62 | 63 | ### Deploy app to the cloud 64 | 65 | ```bash 66 | git clone url/the_app 67 | cd the_app 68 | npm install 69 | tar -czf ../the_app.tgz 70 | cocaine-tool app upload -n the_app --package ../the_app.tgz --manifest manifest.json 71 | >app the_app has been successfully uploaded 72 | ``` 73 | 74 | then, 75 | 76 | ```bash 77 | cocaine-tool app start -n the_app -r default 78 | >app the_app started 79 | curl -v http:///the_app/http/ 80 | >... 81 | ``` 82 | 83 | ### Make use of Cocaine services 84 | 85 | ```js 86 | 87 | var cocaine = require("cocaine") 88 | 89 | var cli = new cocaine.Client(["localhost", 10053]) 90 | 91 | var log = new cli.Logger("myprefix") // logs lines like "myprefix/..." 92 | 93 | cli.on('error', function(err){ 94 | console.log('client error', err) 95 | }) 96 | 97 | log.on('error', function(err){ 98 | console.log('logger error', err) 99 | }) 100 | 101 | 102 | log.connect() 103 | 104 | log.on("connect", function() { 105 | 106 | cli.getServices(['geobase'], function(err, geo, ua){ 107 | var names 108 | 109 | log.info("looking up regionId for ip 1.2.3.4") 110 | 111 | geo.region_id("1.2.3.4", function(err, regionId) { 112 | if(err) return _handleError(err) 113 | 114 | log.debug("found region %d for %s", regionId, "1.2.3.4") 115 | 116 | geo.names(regionId, function(err, names){ 117 | if(err) return _handleError(err) 118 | 119 | log.debug("names for region %d are %s", regionId, names.join()) 120 | 121 | geo.coordinates(regionId, function(coords){ 122 | if(err) return _handleError(err) 123 | 124 | log.debug('coordinates for region %d are %s', regionId, coords.join()) 125 | 126 | }) 127 | }) 128 | }) 129 | }) 130 | }) 131 | 132 | function _handleError(err){ 133 | console.log('service error', err) 134 | } 135 | ``` 136 | 137 | See 138 | [client-simple](http://github.com/cocaine/cocaine-framework-nodejs/blob/master/sample/client.0.js) 139 | for complete source of the simplest cocaine client app. 140 | 141 | ### Use Cocaine services from the outside of the cloud 142 | 143 | To fully control a client to services, you can use 144 | Client. It resolves services for you, keeps services cache, and resets 145 | resolved services cache on locator disconnect. 146 | 147 | 148 | ```js 149 | var cli = new require('cocaine').Client() 150 | 151 | var storage = cli.Service('storage') 152 | 153 | storage.on('error', function(err){ 154 | // reconnect on network error 155 | }) 156 | 157 | storage.connect() 158 | 159 | storage.on('connect', function(){ 160 | storage0.write('collection','key','value', function(err){ 161 | if(err){ 162 | console.log('error writing to storage', err) 163 | return 164 | } 165 | 166 | console.log(done 'writing to storage') 167 | }) 168 | }) 169 | ``` 170 | 171 | See [client-reconnect](http://github.com/cocaine/cocaine-framework-nodejs/sample/client.1.js) 172 | for example of handling various socket-level failures when connecting 173 | and communicating to locator and target services. 174 | 175 | ### Access your application as a Cocaine service 176 | 177 | ```js 178 | var cli = new require('cocaine').Client() 179 | var app = cli.Service('the_app') 180 | 181 | app.connect() 182 | 183 | app.on('connect', function(){ 184 | app.enqueue('handle','anydata', function(err, result){ 185 | if(err) { 186 | console.log('app error', err) 187 | } else { 188 | console.log('app response is', result) 189 | } 190 | }) 191 | }) 192 | 193 | ``` 194 | 195 | ## References 196 | 197 | [1] http://nodejs.org/api/net.html#net_server_listen_handle_callback 198 | 199 | -------------------------------------------------------------------------------- /lib/client/service.js: -------------------------------------------------------------------------------- 1 | 2 | var __assert = require('assert') 3 | var EventEmitter = require('events').EventEmitter 4 | var util = require('util') 5 | var fmt = util.format 6 | 7 | var Locator = require('./locator').Locator 8 | var BaseService = require('./base_service').BaseService 9 | var debug = require('debug')('co:client:service') 10 | 11 | var slice = Array.prototype.slice 12 | 13 | 14 | function resolve(name, locator, cb) { 15 | __assert(typeof name === 'string' && (arguments.length === 2 || arguments.length === 3)) 16 | if(arguments.length === 2) { 17 | cb = locator 18 | locator = undefined 19 | } 20 | __assert(typeof cb === 'function') 21 | 22 | if(locator instanceof Locator) { 23 | var L = locator 24 | } else { 25 | if(locator){ 26 | var endpoints = Locator.normalizeEndpoints(locator) 27 | var L = new Locator(endpoints) 28 | } else { 29 | var L = new Locator() 30 | } 31 | } 32 | 33 | var done = false 34 | 35 | L.connect() 36 | 37 | L.once('connect', _onConnect) 38 | function _onConnect(){ 39 | L.resolve(name, function(err, endpoints, version, graph){ 40 | L.removeListener('error', _onError) 41 | L.close() 42 | if(!done){ 43 | done = true 44 | if(err){ 45 | cb(err) 46 | } else { 47 | cb(null, endpoints, version, graph) 48 | } 49 | } 50 | }) 51 | } 52 | 53 | L.on('error', _onError) 54 | function _onError(err){ 55 | L.removeListener('error', _onError) 56 | L.removeListener('connect', _onConnect) 57 | L.close() 58 | if(!done){ 59 | cb(err) 60 | } 61 | } 62 | } 63 | 64 | function bakeMethods(service, graph){ 65 | var __methods = service.__methods 66 | 67 | Object.keys(graph).some(function(k){ 68 | var methodName = graph[k][0] 69 | __methods[methodName] = function(){ 70 | var args = slice.apply(arguments) 71 | return this._service._call(methodName, args) 72 | } 73 | }) 74 | 75 | } 76 | 77 | function Service(name, options){ 78 | 79 | debug('constructing service %s with options %s', name, options) 80 | 81 | function ServiceClient(){ 82 | this._name = name 83 | this._options = options || {} 84 | this._service = new BaseService({binary: this._options.binary}) 85 | debug('using binary decoding', this._options.binary) 86 | this._onSocketError = this._onSocketError.bind(this) 87 | this._connected = false 88 | this._connecting = false 89 | } 90 | 91 | var methods = ServiceClient.prototype = { 92 | __proto__: serviceClientPrototype, 93 | __methods: null 94 | } 95 | 96 | methods.__methods = methods 97 | 98 | return new ServiceClient() 99 | 100 | } 101 | 102 | 103 | var serviceClientPrototype = { 104 | __proto__: EventEmitter.prototype, 105 | 106 | _onSocketError: function(err){ 107 | debug('on socket error', err) 108 | __assert(this._connected) 109 | this._connected = false 110 | this._service.close() 111 | this._emit('error', err) 112 | }, 113 | 114 | _emit: EventEmitter.prototype.emit, 115 | 116 | connect: function(){ 117 | if(!this._connecting && !this._connected){ 118 | this._connecting = true 119 | 120 | var self = this 121 | if(this._options.locator){ 122 | debug('resolving with specified locator', this._options.locator) 123 | resolve(this._name, this._options.locator, _onResolve) 124 | } else { 125 | debug('resolving with default locator') 126 | resolve(this._name, _onResolve) 127 | } 128 | 129 | function _onResolve(err, endpoints, version, graph){ 130 | if(err){ 131 | self._emit('error', err) 132 | } else { 133 | self.__graph = graph 134 | self._service._setGraph(graph) 135 | 136 | bakeMethods(self, graph) 137 | 138 | self._setEndpoints(endpoints) 139 | self._service.connect(self._endpoints) 140 | self._service.on('connect', _onConnect) 141 | self._service.on('error', _onConnectError) 142 | 143 | function _onConnect(){ 144 | self._service.removeListener('connect', _onConnect) 145 | self._service.removeListener('error', _onConnectError) 146 | self._service.on('error', self._onSocketError) 147 | self._connecting = false 148 | self._connected = true 149 | self._emit('connect') 150 | } 151 | 152 | function _onConnectError(err){ 153 | self._service.removeListener('connect', _onConnect) 154 | self._service.removeListener('error', _onConnectError) 155 | self._service.close() 156 | self._connecting = false 157 | self._emit('error', err) 158 | } 159 | } 160 | } 161 | } 162 | }, 163 | 164 | _call: function(){ 165 | __assert(this._connected) 166 | 167 | return this._service._call.apply(this._service, arguments) 168 | 169 | }, 170 | 171 | close: function(){ 172 | if(this._connected){ 173 | this._connected = false 174 | this._service.close() 175 | } else { 176 | debug('not connected') 177 | } 178 | }, 179 | 180 | _setEndpoints: function(endpoints){ 181 | this._endpoints = endpoints 182 | }, 183 | 184 | _getMethods: function(){ 185 | __assert(this._connected) 186 | 187 | var graph = this.__graph 188 | 189 | return Object.keys(graph).map(function(k){ 190 | return graph[k][0] 191 | }) 192 | 193 | } 194 | 195 | 196 | } 197 | 198 | 199 | 200 | module.exports.Service = Service 201 | 202 | 203 | -------------------------------------------------------------------------------- /lib/client/base_service.js: -------------------------------------------------------------------------------- 1 | 2 | var EventEmitter = require('events').EventEmitter 3 | var __assert = require('assert') 4 | var util = require('util') 5 | var inspect = util.inspect 6 | var makeError = require('../util').makeError 7 | 8 | var Session = require('./session').Session 9 | var Channel = require('./channel').Channel 10 | 11 | var __stop = require('./graph').__stop 12 | 13 | var mp = require('msgpack-bin') 14 | 15 | var debug = require('debug')('co:base_service') 16 | 17 | function BaseService(options){ 18 | this._options = options || {} 19 | this.__sid = 1 20 | this._sessions = {} 21 | this.__graph = null 22 | 23 | this._channel = null 24 | this._connecting = false 25 | // the invariant is: 26 | // (!this._channel && !this._connecting) 27 | // || (this._channel && this._connecting) 28 | // || (this._channel && !this._connecting) 29 | this._effectiveEndpoint = undefined 30 | var self = this 31 | this._hdl = { 32 | message: function(){ 33 | self.hdl.message.apply(self, arguments) 34 | } 35 | } 36 | 37 | } 38 | 39 | 40 | BaseService.prototype = { 41 | 42 | __proto__: EventEmitter.prototype, 43 | 44 | connect: function(endpoints){ 45 | // consecutively connect to endpoints 46 | debug('connect to endpoints ', endpoints) 47 | 48 | __assert(!this._channel, 'should not connect with existing channel') 49 | 50 | var self = this 51 | 52 | var i = 0 53 | _nextEndpoint() 54 | 55 | function _nextEndpoint(){ 56 | if(i < endpoints.length){ 57 | var endpoint = endpoints[i++] 58 | 59 | self._effectiveEndpoint = endpoint 60 | debug('connecting to ', self._effectiveEndpoint) 61 | 62 | self._channel = new Channel(endpoint[0], endpoint[1], !!self._options.binary) 63 | self._channel.owner = this 64 | self._channel.on_connect = _on_connect 65 | self._channel.on_socket_error = _on_connect_error 66 | 67 | //self._channel.connect(endpoint) 68 | } else { 69 | var err = new Error('can not connect to any of the endpoints: '+util.inspect(endpoints)) 70 | err.code = 'ECONNREFUSED' 71 | self.emit('error', err) 72 | } 73 | } 74 | 75 | function _on_connect(){ 76 | self._channel.on_connect = null 77 | self._channel.on_socket_error = _on_socket_error 78 | self._channel.on_message = self._hdl.message 79 | self.emit('connect') 80 | } 81 | 82 | function _on_connect_error(){ 83 | var ch = self._channel 84 | self._channel = null 85 | ch.owner = null 86 | ch.on_connect = null 87 | ch.on_socket_error = null 88 | 89 | _nextEndpoint() 90 | } 91 | 92 | function _on_socket_error(errno){ 93 | var ch = self._channel 94 | self._channel = null 95 | ch.owner = null 96 | ch.on_connect = null 97 | ch.on_socket_error = null 98 | var err = makeError(errno) 99 | self.resetSessions(err) 100 | self.emit('error', err) 101 | } 102 | 103 | }, 104 | 105 | hdl: { 106 | message: function(m){ 107 | var sid = m[0], mid = m[1], args = m[2] 108 | var s = this._sessions[sid] 109 | if(s){ 110 | s.push(m) 111 | } 112 | } 113 | }, 114 | 115 | close: function(err){ 116 | if(this._channel){ 117 | if(this._connecting){ 118 | // cancel connect 119 | var ch = this._channel 120 | this._channel = null 121 | ch.owner = null 122 | ch.close() 123 | } else { 124 | // or close connection and reset sessions 125 | var ch = this._channel 126 | this._channel = null 127 | ch.owner = null 128 | ch.close() 129 | if(!err){ 130 | var err = new Error('connection closed by application') 131 | err.code = 'ECONNRESET' 132 | } 133 | this.resetSessions(err) 134 | } 135 | } 136 | }, 137 | 138 | _setGraph: function(_graph){ 139 | var graph_ = {} 140 | for(var k in _graph){ 141 | var idx = parseInt(k) 142 | var methodGraph = _graph[k] 143 | var methodName = methodGraph[0] 144 | var txGraph = methodGraph[1] 145 | var rxGraph = methodGraph[2] 146 | graph_[methodName] = [idx, txGraph, rxGraph] 147 | } 148 | this.__graph = graph_ 149 | }, 150 | 151 | _send: function(m){ 152 | debug('._send( %s )', inspect(m)) 153 | this._channel.send(mp.pack(m)) 154 | }, 155 | 156 | _call: function(methodName, args){ 157 | 158 | __assert(methodName in this.__graph && typeof args === 'object' && typeof args.length === 'number') 159 | 160 | debug('._call method', methodName) 161 | 162 | var methodDef = this.__graph[methodName] 163 | var mid = methodDef[0] 164 | var txGraph = methodDef[1] 165 | var rxGraph = methodDef[2] 166 | 167 | debug('txGraph', txGraph) 168 | debug('rxGraph', txGraph) 169 | 170 | var s = new Session(this.__sid++, txGraph, rxGraph) 171 | s._owner = this 172 | 173 | this._send([s._id, mid, args]) 174 | 175 | if(Object.keys(rxGraph).length !== 0){ 176 | this._sessions[s._id] = s 177 | } else { 178 | debug('method not found', methodName) 179 | } 180 | 181 | return s 182 | }, 183 | 184 | clearSession: function(id){ 185 | if(id in this._sessions){ 186 | delete this._sessions[id] 187 | } 188 | }, 189 | 190 | resetSessions: function(err){ 191 | var ss = this._sessions 192 | this._sessions = {} 193 | 194 | for(var id in ss){ 195 | ss[id].error(err) 196 | } 197 | 198 | } 199 | 200 | } 201 | 202 | module.exports.BaseService = BaseService -------------------------------------------------------------------------------- /benchmark/memcp.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var assert = require('assert') 4 | var fmt = require('util').format 5 | 6 | function ntime(){ 7 | var sn = process.hrtime() 8 | return sn[0]*1e9+sn[1] 9 | } 10 | 11 | 12 | 13 | function cp_js_1(storage, chunk, N){ 14 | 15 | assert(storage.length % chunk.length === 0) 16 | 17 | while(0= 3', m) 170 | this._hdl.error({code: 'EBADMSG'}) 171 | return false 172 | } 173 | var sid = m[0], mid = m[1], args = m[2] 174 | if(!(typeof mid === 'number' 175 | && typeof sid === 'number' 176 | && (typeof args === 'object' && typeof args.length === 'number'))){ 177 | debug('bad message tuple') 178 | this._hdl.error({code: 'EBADMSG'}) 179 | return false 180 | } 181 | 182 | this.on_message(m) 183 | 184 | return true 185 | }, 186 | 187 | data: function(buf){ 188 | debug('channel got data', buf) 189 | __assert(Buffer.isBuffer(buf), 'Buffer.isBuffer(buf)') 190 | 191 | if(this._inBuffer){ 192 | debug('previous data present') 193 | this._inBuffer = Buffer.concat([this._inBuffer, buf]) 194 | } else { 195 | debug('no previous data present') 196 | this._inBuffer = buf 197 | } 198 | var m 199 | while(true){ 200 | m = unpackMessage(this._inBuffer, this._binary) 201 | debug('unpacked message', inspect(m, {depth:null})) 202 | if(!m){ 203 | break 204 | } 205 | 206 | debug('channel got message', m) 207 | 208 | if(m === fail){ 209 | debug('bad message framing') 210 | //we close it just as if it is of some unexpected form 211 | //this.close() 212 | } else { 213 | 214 | var remaining = this._inBuffer.length - unpackMessage.bytesParsed 215 | debug('unpackMessage.bytesParsed, remaining', unpackMessage.bytesParsed, remaining) 216 | 217 | } 218 | 219 | // this is a bit cryptic. 220 | // what we do here is, if the message is of some very unexpected form, 221 | // don't do anything more. Error handling is done elsewhere, 222 | // here we have to just bail out of this handler 223 | if(!this._hdl.message(m)){ 224 | return 225 | } 226 | 227 | if(0 < remaining){ 228 | debug('remaining buffer', this._inBuffer.slice(unpackMessage.bytesParsed)) 229 | this._inBuffer = this._inBuffer.slice(this._inBuffer.length - remaining) 230 | } else { 231 | this._inBuffer = null 232 | return 233 | } 234 | } 235 | } 236 | } 237 | 238 | } 239 | 240 | module.exports.Channel = Channel 241 | 242 | -------------------------------------------------------------------------------- /test/service.js: -------------------------------------------------------------------------------- 1 | 2 | require("babel/register")({ 3 | // This will override `node_modules` ignoring - you can alternatively pass 4 | // an array of strings to be explicitly matched or a regex / glob 5 | ignore: false 6 | }); 7 | 8 | var debug = require('debug')('test:binary_service') 9 | 10 | var assert = require('assert') 11 | //var assert = require('chai').assert 12 | 13 | var fmt = require('util').format 14 | 15 | var mp = require('msgpack-socket') 16 | 17 | var cocaine = require('cocaine') 18 | var Locator = cocaine.Locator 19 | var Service = cocaine.Service 20 | 21 | var co = require('co') 22 | 23 | var pair = require('msgpack-socket/pair') 24 | var mkTempPath = pair.mkTempPath 25 | var mkSocketPair = pair.mkSocketPair 26 | 27 | var protocol = require('../lib/protocol') 28 | 29 | var inspect1 = require('util').inspect 30 | 31 | function inspect(obj){ 32 | return inspect1(obj, {depth: null}) 33 | } 34 | 35 | var Q = require('q') 36 | 37 | var msgpack = require('msgpack-bin') 38 | 39 | function sleep(ms){ 40 | 41 | var result = Q.defer() 42 | 43 | var uid = (Math.random()*0x100000000).toString(36) 44 | 45 | debug(uid, 'sleeping for', ms) 46 | setTimeout(function(){ 47 | debug(uid, 'awake') 48 | result.resolve() 49 | }, ms) 50 | 51 | return result.promise 52 | } 53 | 54 | describe('service client', function(){ 55 | 56 | var mockLocator, mockService 57 | 58 | var locatorEndpoint, serviceEndpoint 59 | 60 | beforeEach(function(done){ 61 | 62 | co(function *setup(){ 63 | 64 | //locatorEndpoint = ['::1', Math.floor(Math.random()*32000+32000)] 65 | locatorEndpoint = ['::1', 10053] 66 | serviceEndpoint = ['::1', Math.floor(Math.random()*32000+32000)] 67 | 68 | mockLocator = new mp.Server() 69 | mockLocator.listen(locatorEndpoint[1], locatorEndpoint[0]) 70 | 71 | mockService = new mp.Server() 72 | mockService.listen(serviceEndpoint[1], serviceEndpoint[0]) 73 | 74 | yield [mockLocator.await('listening'), mockService.await('listening')] 75 | 76 | debug('listening') 77 | 78 | done() 79 | 80 | }).catch(function(err){ 81 | process.nextTick(function(){ 82 | throw err 83 | }) 84 | }) 85 | }) 86 | 87 | afterEach(function(){ 88 | mockLocator.close() 89 | mockService.close() 90 | }) 91 | 92 | it('should resolve, connect, call method, handle response', function(done){ 93 | 94 | co(function *test(){ 95 | 96 | var S = new Service('svcname') 97 | 98 | S.connect() 99 | 100 | var lc = yield mockLocator.await('connection') 101 | 102 | debug('connected to locator') 103 | 104 | var m = yield lc.recvmsg(msgpack.unpack) 105 | debug('locator got message', m) 106 | 107 | var [sid, method, [name]] = m 108 | assert.deepEqual(m, [sid, 0, ['svcname']]) 109 | assert(typeof sid === 'number' && 0 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /lib/channel/channel.js: -------------------------------------------------------------------------------- 1 | 2 | var __assert = require('assert') 3 | var net = require('net') 4 | var mp = require('msgpack-bin') 5 | 6 | 7 | var errno = require('../errno').errno 8 | var util = require('../util') 9 | 10 | var _ = require('util') 11 | var fmt = _.format 12 | var inspect = _.inspect 13 | 14 | var _mp = require('./mp') 15 | var unpackMessage = _mp.unpackMessage 16 | var _mpFail = _mp.fail 17 | 18 | var RPC = require('../protocol').RPC 19 | 20 | var debug = require('debug')('co:channel') 21 | 22 | function notImplemented(){ 23 | throw new Error('method not implemented') 24 | } 25 | 26 | function Channel(host, port){ 27 | debug('creating channel') 28 | this.owner = null 29 | this._socket = null 30 | this._host = null 31 | this._port = null 32 | this._socketPath = null 33 | this._inBuffer = null 34 | this._hdl = {} 35 | ;['on_socket_error', 'on_connect', 36 | 'on_invoke', 'on_chunk', 'on_choke', 'on_error', 37 | 'on_heartbeat', 'on_terminate'].some(function(event){ 38 | this[event] = notImplemented 39 | }, this) 40 | util.bindHandlers(this.hdl, this._hdl, this) 41 | this._parseArguments.apply(this, arguments) 42 | this.connect() 43 | this._RPC = RPC 44 | } 45 | 46 | Channel.prototype = { 47 | connect: function(){ 48 | debug('connecting channel') 49 | __assert(!this._socket, '!this._socket') 50 | this._makeSocket() 51 | __assert(this._socketPath || this._host && this._port, 52 | 'this._socketPath || this._host && this._port') 53 | if(this._socketPath){ 54 | debug('connecting channel, path:', this._socketPath) 55 | this._socket.connect(this._socketPath) 56 | } else if(this._host){ 57 | debug('connecting channel, [host,port]:', [this._host, this._port]) 58 | this._socket.connect(this._port, this._host) 59 | } 60 | }, 61 | 62 | _setProtocol: function(RPC){ 63 | this._RPC = RPC 64 | }, 65 | 66 | send: function(buf){ 67 | debug('send', buf) 68 | __assert(this._socket && Buffer.isBuffer(buf), 'this._socket && Buffer.isBuffer(buf)') 69 | this._socket.write(buf) 70 | }, 71 | 72 | sendTerminate: function(sid, code, message){ 73 | debug('sendTerminate', sid, code, message) 74 | var t = true 75 | __assert(t = (arguments.length === 3 76 | && typeof sid === 1 77 | && typeof code === 'number' 78 | && typeof message === 'string'), 79 | t || fmt("arguments.length`%s` === 3 && sid`%s` === 1 && typeof code`%s` === 'number' && typeof message`%s` === 'string'", 80 | arguments.length, sid, code, message)) 81 | this._socket.send(mp.pack([sid, this._RPC.terminate, [code, message]])) 82 | }, 83 | 84 | close: function(){ 85 | debug('close') 86 | if(this._socket){ 87 | this._socket.end() 88 | this._destroySocket() 89 | } 90 | }, 91 | 92 | _parseArguments: function(){ 93 | if(arguments.length === 1){ 94 | var path = arguments[0] 95 | __assert(typeof path === 'string', 96 | "typeof path === 'string'") 97 | this._socketPath = path 98 | } else if (arguments.length === 2){ 99 | var host = arguments[0] 100 | var port = arguments[1] 101 | __assert(typeof host === 'string' && typeof port === 'number', 102 | "typeof host === 'string' && typeof port === 'number'") 103 | this._host = host 104 | this._port = port 105 | } else { 106 | throw new Error('bad match: '+JSON.stringify(arguments)) 107 | } 108 | }, 109 | 110 | // a hook method, needed for testing only. 111 | // don't touch! don't use! 112 | // it's only usage is in test/channel['on socket close just on write'] 113 | _injectSocket: function(sock){ 114 | __assert(!this._socket, '!this._socket') 115 | this._socket = sock 116 | sock.on('error', this._hdl.error) 117 | sock.on('data', this._hdl.data) 118 | sock.on('end', this._hdl.end) 119 | sock.on('close', this._hdl.close) 120 | }, 121 | 122 | _destroySocket: function(){ 123 | debug('_destroySocket',(new Error('sample')).stack) 124 | if(this._socket){ 125 | var s = this._socket 126 | if(0 < s.bufferSize){ 127 | debug('destroying socket with non-sent data') 128 | } 129 | this._socket = null 130 | s.removeListener('connect', this._hdl.connect) 131 | s.removeListener('error', this._hdl.error) 132 | s.removeListener('close', this._hdl.close) 133 | s.removeListener('data', this._hdl.data) 134 | s.destroy() 135 | } 136 | }, 137 | 138 | _makeSocket: function(){ 139 | __assert(!this._socket, '!this._socket') 140 | this._socket = new net.Socket({allowHalfOpen: true}) 141 | this._socket.on('connect', this._hdl.connect) 142 | this._socket.on('error', this._hdl.error) 143 | }, 144 | 145 | hdl:{ 146 | connect: function(){ 147 | debug('channel.hdl.connect') 148 | this._socket.removeListener('connect', this._hdl.connect) 149 | this._socket.on('data', this._hdl.data) 150 | this._socket.on('end', this._hdl.end) 151 | this.on_connect() 152 | }, 153 | 154 | end: function(){ 155 | debug('channel.hdl.end') 156 | if(!this._errorFired){ 157 | this._errorFired = true 158 | this.on_socket_error(errno.ESHUTDOWN) 159 | } 160 | }, 161 | 162 | close: function(){ 163 | debug('channel.hdl.close') 164 | if(!this._errorFired){ 165 | this._errorFired = true 166 | this.on_socket_error(errno.EPIPE) 167 | } 168 | this._destroySocket() 169 | }, 170 | 171 | error: function(err){ 172 | debug('channel.hdl.error', err) 173 | var errno0 = errno[err.code] || errno.EBADF 174 | this._errorFired = true 175 | this.on_socket_error(errno0) 176 | this._destroySocket() 177 | }, 178 | 179 | message: function(m){ 180 | debug('channel.hdl.message', m) 181 | if(!(typeof m === 'object' && typeof m.length === 'number' 182 | && 3<= m.length)){ 183 | debug('RPC message is not a tuple', m) 184 | this._hdl.error({code: 'EBADMSG'}) 185 | return false 186 | } 187 | var sid = m[0], mid = m[1], args = m[2] 188 | if(!(typeof mid === 'number' 189 | && typeof sid === 'number' 190 | && (typeof args === 'object' && typeof args.length === 'number'))){ 191 | debug('bad RPC message tuple') 192 | this._hdl.error({code: 'EBADMSG'}) 193 | return false 194 | } 195 | var owner = this.owner 196 | if(!owner && sid !== 1){ 197 | debug('discarding message `%s` on channel with no owner', m) 198 | return false 199 | } 200 | 201 | if(owner.__sid < sid){ 202 | owner.__sid = sid 203 | 204 | debug('case this._RPC.invoke') 205 | var event = args[0] 206 | if(!(typeof event === 'string', 207 | "typeof event === 'string'")){ 208 | debug('bad RPC.invoke message') 209 | } else { 210 | this.on_invoke(sid, event) 211 | } 212 | 213 | } else if (sid === 1) { 214 | // system session 215 | if(mid === 0){ 216 | // heartbeat 217 | debug('case this._RPC.heartbeat') 218 | if(!(args.length === 0)){ 219 | debug('bad RPC.heartbeat message') 220 | } else { 221 | this.on_heartbeat() 222 | } 223 | } else if (mid === 1){ 224 | // terminate 225 | debug('case this._RPC.terminate') 226 | var code = args[0], reason = args[1] 227 | if(!(typeof code === 'number' && typeof reason === 'string')){ 228 | debug('bad RPC.terminate message', m) 229 | } else { 230 | this.on_terminate(code, reason) 231 | } 232 | } 233 | 234 | } else if (sid <= owner.__sid){ 235 | 236 | if(mid === 0){ 237 | // write 238 | debug('case this._RPC.chunk') 239 | var buf = args[0] 240 | if(!(Buffer.isBuffer(buf))){ 241 | debug('bad RPC.chunk message') 242 | } else { 243 | this.on_chunk(sid, buf) 244 | } 245 | 246 | } else if (mid === 1){ 247 | // error 248 | debug('case this._RPC.error') 249 | var error = args[0], message = args[1] 250 | if(!(typeof error === 'object' && error.length === 2 251 | && typeof error[0] === 'number' && typeof error[1] === 'number')){ 252 | debug('bad RPC.error message', m) 253 | } else { 254 | var error_category = error[0] 255 | var errno0 = error[1] 256 | this.on_error(sid, error_category, errno0, message) 257 | } 258 | 259 | } else if (mid === 2){ 260 | // close 261 | debug('case this._RPC.choke') 262 | if(!(args.length === 0)){ 263 | debug('bad RPC.choke message') 264 | } else { 265 | this.on_choke(sid) 266 | } 267 | 268 | } else { 269 | debug('discarding a message of unknown type', m) 270 | } 271 | 272 | } 273 | 274 | return true 275 | }, 276 | 277 | data: function(buf){ 278 | __assert(Buffer.isBuffer(buf), 'Buffer.isBuffer(buf)') 279 | debug('channel got data', buf) 280 | 281 | if(this._inBuffer){ 282 | debug('previous data present') 283 | this._inBuffer = Buffer.concat([this._inBuffer, buf]) 284 | } else { 285 | debug('no previous data present') 286 | this._inBuffer = buf 287 | } 288 | 289 | var owner = this.owner 290 | if(!owner){ 291 | debug("won't decode message on channel with no owner, buffering") 292 | return 293 | } 294 | 295 | while(true){ 296 | var m = unpackMessage(this._inBuffer, this._RPC, owner.__sid+1) 297 | if(!m){ 298 | debug('not complete/falsish message `%s`', m) 299 | break 300 | } 301 | 302 | debug('channel got message', inspect(m, {depth:null})) 303 | 304 | if(m === _mpFail){ 305 | debug('bad message framing') 306 | //we close it just as if is of some unexpected form 307 | //this.close() 308 | } else { 309 | var remaining = this._inBuffer.length - unpackMessage.bytesParsed 310 | debug('unpackMessage.bytesParsed`%s`, remaining`%s`', unpackMessage.bytesParsed, remaining) 311 | 312 | } 313 | 314 | // this is a bit cryptic. 315 | // what we do here is, if the message is of some very unexpected form, 316 | // don't do anything more. Error handling is done elsewhere, 317 | // here we have to just bail out of this handler 318 | if(!this._hdl.message(m)){ 319 | return 320 | } 321 | 322 | if(0 < remaining){ 323 | debug('remaining buffer `%s`...', this._inBuffer.slice(unpackMessage.bytesParsed, unpackMessage.bytesParsed+10)) 324 | this._inBuffer = this._inBuffer.slice(this._inBuffer.length - remaining) 325 | } else { 326 | this._inBuffer = null 327 | return 328 | } 329 | } 330 | } 331 | } 332 | 333 | } 334 | 335 | module.exports.Channel = Channel 336 | 337 | //compatibility 338 | module.exports.communicator = Channel 339 | 340 | -------------------------------------------------------------------------------- /test/worker.js: -------------------------------------------------------------------------------- 1 | 2 | require("babel/register")({ 3 | // This will override `node_modules` ignoring - you can alternatively pass 4 | // an array of strings to be explicitly matched or a regex / glob 5 | ignore: false 6 | }); 7 | 8 | var assert = require('assert') 9 | 10 | var mp = require('msgpack-socket') 11 | 12 | var Worker = require('cocaine').Worker 13 | var co = require('co') 14 | 15 | var mkTempPath = require('msgpack-socket/pair').mkTempPath 16 | 17 | var protocol = require('../lib/protocol') 18 | 19 | var Q = require('q') 20 | 21 | var msgpack = require('msgpack-bin') 22 | 23 | var RPC = { 24 | invoke: 0, 25 | chunk: 0, 26 | error: 1, 27 | choke: 2 28 | } 29 | 30 | var debug = require('debug')('co:test:worker') 31 | 32 | function sleep(ms){ 33 | 34 | var result = Q.defer() 35 | 36 | var uid = (Math.random()*0x100000000).toString(36) 37 | 38 | console.log(uid, 'sleeping for', ms) 39 | setTimeout(function(){ 40 | console.log(uid, 'awake') 41 | result.resolve() 42 | }, ms) 43 | 44 | return result.promise 45 | } 46 | 47 | describe('worker', function(){ 48 | 49 | var Locator, Node 50 | 51 | var locatorEndpoint, nodeEndpoint 52 | var W, options 53 | 54 | beforeEach(function(done){ 55 | 56 | co(function *setup(){ 57 | 58 | [locatorEndpoint, nodeEndpoint] = [mkTempPath(), mkTempPath()] 59 | 60 | Locator = new mp.Server() 61 | Node = new mp.Server() 62 | 63 | Locator.listen(locatorEndpoint) 64 | Node.listen(nodeEndpoint) 65 | 66 | yield [Locator.await('listening'), Node.await('listening')] 67 | 68 | console.log('listening') 69 | 70 | options = {locator: locatorEndpoint, 71 | endpoint: nodeEndpoint, 72 | app: 'testapp', 73 | uuid: 'f5fe7be9-3dbf-4636-840f-f255cbb2f702'} 74 | 75 | W = new Worker(options) 76 | 77 | done() 78 | 79 | }).catch(function(err){ 80 | process.nextTick(function(){ 81 | throw err 82 | }) 83 | }) 84 | }) 85 | 86 | afterEach(function(){ 87 | Node.close() 88 | Locator.close() 89 | W.close() 90 | }) 91 | 92 | it('should handshake with uuid', function(done){ 93 | 94 | co(function *test(){ 95 | 96 | W.connect() 97 | 98 | var wc = yield Node.await('connection') 99 | 100 | console.log('worker connected') 101 | 102 | var m = yield wc.recvmsg(msgpack.unpack) 103 | 104 | console.log('node got message', m) 105 | 106 | var [sid, method, [uuid]] = m 107 | 108 | assert(uuid === options.uuid, "worker handshakes with it's uuid") 109 | 110 | done() 111 | 112 | }).catch(function(err){ 113 | process.nextTick(function(){ 114 | throw err 115 | }) 116 | }) 117 | 118 | }) 119 | 120 | 121 | it('should receive simple event', function(done){ 122 | 123 | co(function *test(){ 124 | 125 | W.connect() 126 | 127 | var wc = yield Node.await('connection') 128 | 129 | console.log('worker connected') 130 | 131 | var m = yield wc.recvmsg(msgpack.unpack) 132 | 133 | console.log('node got message', m) 134 | 135 | var [sid, method, [uuid]] = m 136 | 137 | assert(uuid === options.uuid, "worker handshakes with it's uuid") 138 | 139 | var h = W.await('handle1') 140 | 141 | wc.sendmsg([15, RPC.invoke, ['handle1']]) 142 | 143 | var s = yield h 144 | 145 | debug('worker received event') 146 | 147 | wc.sendmsg([15, RPC.chunk, ['body']]) 148 | 149 | var d = yield s.await('data') 150 | 151 | console.log('worker got data `%s`', d) 152 | 153 | wc.sendmsg([15, RPC.choke, []]) 154 | 155 | yield s.await('end') 156 | 157 | done() 158 | 159 | 160 | }).catch(function(err){ 161 | process.nextTick(function(){ 162 | throw err 163 | }) 164 | }) 165 | 166 | }) 167 | 168 | 169 | it('should respond to simple event', function(done){ 170 | 171 | co(function *test(){ 172 | 173 | W.connect() 174 | 175 | var wc = yield Node.await('connection') 176 | 177 | console.log('worker connected') 178 | 179 | var m = yield wc.recvmsg(msgpack.unpack) 180 | 181 | console.log('node got message', m) 182 | 183 | var [sid, method, [uuid]] = m 184 | 185 | assert(uuid === options.uuid, "worker handshakes with it's uuid") 186 | 187 | wc.sendmsg([15, RPC.invoke, ['handle1']]) 188 | 189 | var s = yield W.await('handle1') 190 | 191 | wc.sendmsg([15, RPC.chunk, ['body']]) 192 | 193 | var d = yield s.await('data') 194 | 195 | console.log('worker got data `%s`', d) 196 | 197 | var m = yield wc.recvmsg() 198 | 199 | console.log('response from worker, heartbeat', m) 200 | 201 | 202 | var r = s.write(Buffer('r1')) 203 | console.log('buffer consumed', r) 204 | var r = s.write(Buffer('r2')) 205 | console.log('buffer consumed', r) 206 | 207 | var m = yield wc.recvmsg() 208 | console.log('response from worker', m) 209 | 210 | var m = yield wc.recvmsg() 211 | console.log('response from worker', m) 212 | 213 | var r = s.end(Buffer('r3')) 214 | console.log('buffer consumed', r) 215 | 216 | var m = yield wc.recvmsg() 217 | console.log('response from worker', m) 218 | 219 | wc.sendmsg([15, RPC.choke, []]) 220 | 221 | yield s.await('end') 222 | console.log('end stream from worker') 223 | 224 | done() 225 | 226 | }).catch(function(err){ 227 | process.nextTick(function(){ 228 | throw err 229 | }) 230 | }) 231 | 232 | }) 233 | 234 | 235 | it('should receive input stream', function(done){ 236 | 237 | co(function *test(){ 238 | 239 | W.connect() 240 | var wc = yield Node.await('connection') 241 | 242 | console.log('worker connected') 243 | 244 | var m = yield wc.recvmsg(msgpack.unpack) 245 | 246 | console.log('node got message', m) 247 | 248 | var [method, sid, [uuid]] = m 249 | 250 | assert(uuid === options.uuid, "worker handshakes with it's uuid") 251 | 252 | wc.sendmsg([15, RPC.invoke, ['handle1']]) 253 | 254 | var s = yield W.await('handle1') 255 | 256 | var end = Q.defer() 257 | 258 | s.once('end', function(){ 259 | console.log('session end') 260 | end.fulfill(10) 261 | }) 262 | 263 | s.on('readable', function(){ 264 | console.log('channel readable') 265 | console.log('read data', s.read()) 266 | }) 267 | 268 | yield [ 269 | (function *sub1(){ 270 | 271 | for(var i=0;i<10;i++){ 272 | wc.sendmsg([15, RPC.chunk, ['body'+i]]) 273 | yield sleep(200) 274 | } 275 | })(), 276 | 277 | 1 || (function *sub2(){ 278 | var i=0 279 | while(i<10){ 280 | yield s.await('readable') 281 | 282 | var d 283 | while(d = s.read()){ 284 | i++ 285 | console.log('worker got data `%s`', d) 286 | } 287 | } 288 | })()] 289 | 290 | var m = yield wc.recvmsg() 291 | 292 | console.log('response from worker, heartbeat', m) 293 | 294 | console.log('closing session from service') 295 | wc.sendmsg([15, RPC.choke, []]) 296 | 297 | yield end.promise //s.await('end') 298 | console.log('worker: end of input stream') 299 | 300 | s.end() 301 | 302 | var m = yield wc.recvmsg() 303 | console.log('client: end of worker output stream', m) 304 | 305 | assert.deepEqual(m, [15, RPC.choke, []]) 306 | 307 | 308 | done() 309 | 310 | 311 | }).catch(function(err){ 312 | process.nextTick(function(){ 313 | throw err 314 | }) 315 | }) 316 | 317 | }) 318 | 319 | 320 | it.skip('should stream both ways', function(done){ 321 | 322 | co(function *test(){ 323 | 324 | W.connect() 325 | var wc = yield Node.await('connection') 326 | 327 | console.log('worker connected') 328 | 329 | var m = yield wc.recvmsg(msgpack.unpack) 330 | 331 | console.log('node got message', m) 332 | 333 | var [method, sid, [uuid]] = m 334 | 335 | assert(uuid === options.uuid, "worker handshakes with it's uuid") 336 | 337 | wc.sendmsg([15, RPC.invoke, ['handle1']]) 338 | 339 | var s = yield W.await('handle1') 340 | 341 | yield [ 342 | (function *sub1(){ 343 | 344 | for(var i=0;i<10;i++){ 345 | wc.sendmsg([15, RPC.chunk, ['body'+i]]) 346 | yield sleep(200) 347 | 348 | while(true){ 349 | var m = yield wc.recvmsg() 350 | if(m[0] === 15){ 351 | // accept only messages from this channel id 352 | break 353 | } 354 | } 355 | 356 | console.log('server got message back', m) 357 | 358 | } 359 | })(), 360 | 361 | (function *sub2(){ 362 | var i=0 363 | while(i<10){ 364 | yield s.await('readable') 365 | 366 | var d 367 | while(d = s.read()){ 368 | i++ 369 | console.log('worker got data `%s`', d) 370 | s.write(d) 371 | console.log('writing data back') 372 | } 373 | } 374 | })()] 375 | 376 | // var m = yield wc.recvmsg() 377 | 378 | // console.log('response from worker, heartbeat', m) 379 | 380 | s.end() 381 | 382 | wc.sendmsg([15, RPC.choke, []]) 383 | 384 | yield s.await('end') 385 | console.log('worker: end of input stream') 386 | 387 | var m = yield wc.recvmsg() 388 | console.log('client: end of worker output stream', m) 389 | 390 | assert.deepEqual(m, [15, RPC.choke, []]) 391 | 392 | 393 | done() 394 | 395 | 396 | }).catch(function(err){ 397 | process.nextTick(function(){ 398 | throw err 399 | }) 400 | }) 401 | 402 | }) 403 | 404 | 405 | it.skip('should stream really huge chunks both ways', function(done){ 406 | 407 | co(function *test(){ 408 | 409 | W.connect() 410 | var wc = yield Node.await('connection') 411 | 412 | console.log('worker connected') 413 | 414 | var m = yield wc.recvmsg(msgpack.unpack) 415 | 416 | console.log('node got message', m) 417 | 418 | var [method, sid, [uuid]] = m 419 | 420 | assert(uuid === options.uuid, "worker handshakes with it's uuid") 421 | 422 | wc.sendmsg([15, RPC.invoke, ['handle1']]) 423 | 424 | var s = yield W.await('handle1') 425 | 426 | var b = Buffer(1024*1024*16) 427 | 428 | yield [ 429 | (function *sub1(){ 430 | 431 | for(var i=0;i<10;i++){ 432 | wc.sendmsg([15, RPC.chunk, [b]]) 433 | yield sleep(200) 434 | 435 | while(true){ 436 | var m = yield wc.recvmsg() 437 | if(m[0] === 15){ 438 | // accept only messages from this channel id 439 | break 440 | } 441 | } 442 | 443 | var [sid, method, [buf]] = m 444 | 445 | console.log('node got message back', [sid, method, [buf.slice(0,10)]], buf.length) 446 | 447 | assert(buf.length === b.length, 'chunk should be of the same length as sent') 448 | 449 | } 450 | })(), 451 | 452 | (function *sub2(){ 453 | var i=0 454 | while(i<10){ 455 | yield s.await('readable') 456 | 457 | var d 458 | while(d = s.read()){ 459 | i++ 460 | console.log('worker got data `%s`, length %d', d.slice(0, 30), d.length) 461 | s.write(d) 462 | console.log('writing data back') 463 | } 464 | } 465 | })()] 466 | 467 | // var m = yield wc.recvmsg() 468 | 469 | // console.log('response from worker, heartbeat', m) 470 | 471 | s.end() 472 | 473 | wc.sendmsg([15, RPC.choke, []]) 474 | 475 | yield s.await('end') 476 | console.log('worker: end of input stream') 477 | 478 | var m = yield wc.recvmsg() 479 | console.log('client: end of worker output stream', m) 480 | 481 | assert.deepEqual(m, [15, RPC.choke, []]) 482 | 483 | 484 | done() 485 | 486 | 487 | }).catch(function(err){ 488 | process.nextTick(function(){ 489 | throw err 490 | }) 491 | }) 492 | 493 | }) 494 | 495 | }) 496 | 497 | 498 | 499 | 500 | -------------------------------------------------------------------------------- /lib/worker/worker.js: -------------------------------------------------------------------------------- 1 | 2 | var EventEmitter = require('events').EventEmitter 3 | var mp = require('msgpack-bin') 4 | var __assert = require('assert') 5 | 6 | var util = require('../util') 7 | var _ = require('../errno'), ERRNO = _.errno, _ERRNO = _.code 8 | var _ = require('../protocol'), RPC = _.RPC, _RPC = _._RPC, TERMINATE = _.TERMINATE 9 | 10 | var FSM = require('../fsm') 11 | 12 | var Session = require('../session').Session 13 | var ListenHandle = require('./handles').ListenHandle 14 | 15 | var channel_binding = require('../channel/channel').Channel 16 | 17 | var debug = require('debug')('co:worker') 18 | 19 | var Worker = FSM.define({ 20 | 21 | methods:{ 22 | __init__: function(options) { 23 | this._endpoint = options.endpoint // '/path/to/unix.sock' 24 | this._uuid = options.uuid 25 | this._app = options.app 26 | 27 | this._listenHandles = {} 28 | this._sessions = {} 29 | 30 | this.events = new EventEmitter() 31 | 32 | this._disownTimeout = options.disownTimeout || 30000 33 | this._heartbeatInterval = options.heartbeatInterval || 5000 34 | 35 | this._heartbeatTimer = 0 36 | this._disownTimer = 0 37 | 38 | this._handlers = {} 39 | util.bindHandlers(this.handlers, this._handlers, this) 40 | }, 41 | 42 | ListenHandle: function(event){ 43 | var lh = new ListenHandle(event, this) 44 | return lh 45 | }, 46 | 47 | Session: function(event){ 48 | var s = new Session() 49 | s.owner = this 50 | s._setProtocol(RPC) 51 | return s 52 | }, 53 | 54 | listen: function(){ 55 | if(this._state === 'closed'){ 56 | this.connect(this._endpoint) 57 | } 58 | }, 59 | 60 | getListenHandle:function(event){ 61 | __assert((typeof event === 'string' || typeof event === 'number') 62 | && !(event in this._listenHandles), 63 | "(typeof event === 'string' || typeof event === 'number')"+ 64 | " && !(event in this._listenHandles)") 65 | var lh = this.ListenHandle(event) 66 | return this._listenHandles[lh._id] = lh 67 | }, 68 | 69 | removeListenHandle:function(lh){ 70 | __assert(this._listenHandles[lh._id] === lh) 71 | delete this._listenHandles[lh._id] 72 | }, 73 | 74 | ref:function(){ 75 | //this._handle.ref() 76 | }, 77 | 78 | unref:function(){ 79 | //this._handle.unref() 80 | }, 81 | 82 | _resetDisownTimer: function(){ 83 | clearTimeout(this._disownTimer) 84 | }, 85 | 86 | _resetTimers: function(){ 87 | clearTimeout(this._disownTimer) 88 | clearTimeout(this._heartbeatTimer) 89 | }, 90 | 91 | _setHandle: function(handle){ 92 | this._handle = handle 93 | handle.owner = this 94 | this._handle._setProtocol(RPC) 95 | }, 96 | 97 | _closeHandle: function(){ 98 | this._handle.close() 99 | util.unsetHandlers(this._handle, this.__fsmdef[this._state].handlers) 100 | this._handle.owner = null 101 | this._handle = null 102 | }, 103 | 104 | _resetSessions: function(errno){ 105 | Object.keys(this._sessions).forEach(function(sid){ 106 | var s = this._sessions[sid] 107 | s.pushError(errno, _ERRNO[errno]) 108 | delete this._sessions[sid] 109 | },this) 110 | } 111 | 112 | }, 113 | 114 | handlers: { 115 | disown: function(){ 116 | __assert(this._state === 'connected') 117 | this.events.emit('disown') 118 | this.close() 119 | }, 120 | 121 | sendNextHeartbeat: function(){ 122 | __assert(this._state === 'connected', "this._state === 'connected'") 123 | var sid = 1 124 | // RPC.heartbeat 125 | this._handle.send(mp.pack([sid, 0, []])) 126 | this._heartbeatTimer = setTimeout(this._handlers.sendNextHeartbeat, this._heartbeatInterval) 127 | } 128 | }, 129 | 130 | startState: 'closed', 131 | states: { 132 | // we start in a `closed state 133 | closed: { 134 | methods: { 135 | // initiate connection to engine, state -> `connecting 136 | connect: function(endpoint){ 137 | debug('connect', arguments) 138 | endpoint = endpoint || this._endpoint 139 | if(Array.isArray(endpoint)){ 140 | __assert(typeof endpoint[0] === 'string' && typeof endpoint[1] === 'number', "endpoint is ['host|ip', port]") 141 | this._setHandle(new channel_binding(endpoint[0], endpoint[1])) 142 | } else { 143 | __assert(typeof endpoint === 'string', "assume endpoint is a string path to unix socket") 144 | this._setHandle(new channel_binding(endpoint)) 145 | } 146 | this._setState('connecting') 147 | } 148 | } 149 | }, 150 | 151 | // wait for the socket to connect 152 | connecting: { 153 | methods:{ 154 | // cancel initiated in-progress connection, state -> `closed 155 | close: function(){ 156 | this._closeHandle() 157 | this._resetTimers() 158 | this._setState('closed') 159 | } 160 | }, 161 | handlers:{ 162 | // connection established; send `handshake, state -> `connected 163 | on_connect: function(){ 164 | var _this = this.owner 165 | // RPC.handshake 166 | var sid = 1 167 | _this._handle.send(mp.pack([sid, 0, [_this._uuid]])) 168 | _this._setState('connected') 169 | _this._handlers.sendNextHeartbeat() 170 | _this.events.emit('connect') 171 | }, 172 | 173 | // connection attempt failed. state -> `closed 174 | on_socket_error: function(errno){ 175 | var _this = this.owner 176 | _this._closeHandle() 177 | _this._setState('closed') 178 | var e = util.makeError(errno) 179 | _this.events.emit('error',e) 180 | } 181 | } 182 | }, 183 | 184 | // connection established, handshaked, and first heartbeat sent. 185 | // wait for incoming requests messages 186 | connected:{ 187 | methods:{ 188 | // js app wants worker to shut down, for some good or bad reason 189 | terminate: function(abnormal, reason){ 190 | var state = abnormal? TERMINATE.abnormal: TERMINATE.normal 191 | if(typeof reason !== 'string'){ 192 | reason = 'self-terminating' 193 | } 194 | // RPC.terminate 195 | var sid = 1 196 | var msg = mp.pack([sid, 1, [state, reason]]) 197 | debug('sending terminate message', msg) 198 | this._handle.send(msg) 199 | this._setState('selfTerminated') 200 | }, 201 | 202 | // app just closes the engine socket. engine won't 203 | // accept any messages from thisworker instance (i.e. after 204 | // reconnect) anymore 205 | close: function(){ 206 | // TODO: shouldn't we just call .terminate here? 207 | this._closeHandle() 208 | this._setState('closed') 209 | this._resetTimers() 210 | this._resetSessions(ERRNO.ECONNRESET) 211 | } 212 | }, 213 | 214 | handlers:{ 215 | // unexpected socket-level failure 216 | on_socket_error: function(errno){ 217 | var _this = this.owner 218 | debug('socket error <%s>', _this._name, _ERRNO[errno]) 219 | _this._closeHandle() 220 | _this._setState('closed') 221 | _this._resetSessions(errno) 222 | _this.events.emit('error', util.makeError(errno)) 223 | }, 224 | 225 | // got a `heartbeat message from engine 226 | on_heartbeat: function(){ 227 | var _this = this.owner 228 | _this._resetDisownTimer() 229 | }, 230 | 231 | // `terminate message 232 | on_terminate: function(sid, code, reason){ 233 | var _this = this.owner 234 | _this._resetTimers() 235 | _this._setState('engineTerminated') 236 | }, 237 | 238 | // `invoke message for new session 239 | on_invoke: function(sid, event){ 240 | debug('on_invoke sss', sid, event) 241 | var _this = this.owner 242 | var lh = _this._listenHandles[event] 243 | if(lh){ 244 | debug('got listen handle') 245 | var s = lh.createStreamHandle(sid, event) 246 | _this._sessions[s._id] = s 247 | } else { 248 | var s = _this.Session() 249 | s._id = sid 250 | _this._sessions[s._id] = s 251 | _this.emit(event, s) 252 | } 253 | }, 254 | 255 | // `chunk message for some session 256 | on_chunk: function(sid, data){ 257 | debug('on_chunk', sid, data) 258 | var _this = this.owner 259 | var s = _this._sessions[sid] 260 | if(s){ 261 | s.pushChunk(data) 262 | } else { 263 | debug('session %s not found, dropping message', sid) 264 | } 265 | }, 266 | 267 | // `choke message for some session 268 | on_choke: function(sid){ 269 | debug('on_choke', sid) 270 | var _this = this.owner 271 | var s = _this._sessions[sid] 272 | if(s){ 273 | s.pushChoke() 274 | delete _this._sessions[sid] 275 | } 276 | }, 277 | 278 | // `error message for some session 279 | on_error: function(sid, category, code, message){ 280 | debug('on_error', sid, [category, code], message) 281 | var _this = this.owner 282 | var s = _this._sessions[sid] 283 | if(s){ 284 | s.pushError(code, message) 285 | delete s._sessions[sid] 286 | } 287 | } 288 | } 289 | }, 290 | 291 | // got `terminate from engine 292 | engineTerminated: { 293 | methods:{ 294 | // tell engine that js app shutdown is complete, state -> `terminated 295 | terminate: function(){ 296 | // RPC.terminate 297 | var sid = 1 298 | var msg = mp.pack([sid, 1, [TERMINATE.normal, 'worker shut down']]) 299 | this._handle.send(msg) 300 | this._setState('terminated') 301 | this._resetSessinos(ERRNO.ESHUTDOWN) 302 | }, 303 | 304 | // just drop the connection 305 | close: function(){ 306 | // XXX: shouldn we just call .terminate here? 307 | this._closeHandle() 308 | this._setState('closed') 309 | this._resetTimers() 310 | this._resetSessions(ERRNO.ECONNRESET) 311 | } 312 | }, 313 | handlers:{ 314 | // after `terminate message from engine we don't expect any 315 | // other messages 316 | 317 | // engine didn't make it to receive our 318 | // `terminate response and closed the socket beforehand 319 | on_socket_error: function(errno){ 320 | var _this = this.owner 321 | _this._closeHandle() 322 | _this._setState('closed') 323 | _this._resetSessions(errno) 324 | var e = util.makeError(errno) 325 | _this.events.emit('error', e) 326 | } 327 | } 328 | }, 329 | 330 | // .terminate() was called, because js app wants worker to shut down 331 | selfTerminated: { 332 | methods:{ 333 | // don't wait for `terminate reply, just drop the connection 334 | close:function(){ 335 | this._closeHandle() 336 | this._setState('closed') 337 | this._resetSessions(ERRNO.ECONNRESET) 338 | } 339 | }, 340 | handlers:{ 341 | // socket-level error 342 | on_socket_error: function(errno){ 343 | var _this = this.owner 344 | _this._closeHandle() 345 | _this._setState('closed') 346 | _this._resetSessions(errno) 347 | var e = util.makeError(errno) 348 | _this.events.emit('error', e) 349 | }, 350 | 351 | // engine responds with `treminate, state->`closed 352 | on_terminate: function(sid, code, reason){ 353 | var _this = this.owner 354 | _this._closeHandle() 355 | _this._setState('closed') 356 | _this._resetSessions(ERRNO.ESHUTDOWN) 357 | } 358 | } 359 | }, 360 | 361 | // final termination phase, when we got 'terminate' from engine, 362 | // sent it 'terminate' in response, and waiting for socket to 363 | // shutdown 364 | terminated: { 365 | methods:{ 366 | // drop the connection. In this case it may happen that our 367 | // `terminate message won't make it through to engine 368 | close: function(){ 369 | this._closeHandle() 370 | this._setState('closed') 371 | this._resetSessions(ERRNO.ECONNRESET) 372 | } 373 | }, 374 | handlers:{ 375 | // engine shut down the socket as expected 376 | on_socket_error: function(errno){ 377 | var _this = this.owner 378 | // TODO: if(errno === ERRNO.ESHUTDOWN) 379 | _this._closeHandle() 380 | _this._setState('closed') 381 | _this._resetSessions(errno) 382 | var e = util.makeError(errno) 383 | _this.events.emit('error', e) 384 | } 385 | } 386 | } 387 | 388 | 389 | } 390 | 391 | }) 392 | 393 | module.exports = { 394 | Worker: Worker 395 | } 396 | 397 | 398 | 399 | 400 | -------------------------------------------------------------------------------- /lib/worker/handles1.js: -------------------------------------------------------------------------------- 1 | 2 | var mp = require('msgpack-bin') 3 | var __assert = require('assert') 4 | 5 | //var jp = require('@nojs/jampack') 6 | 7 | var debug = require('debug')('co:handles1') 8 | 9 | var util = require('../util') 10 | 11 | var TCP = process.binding('tcp_wrap').TCP 12 | 13 | var protocol = require('../protocol') 14 | var RPC = protocol.RPC 15 | 16 | var trace = 0 17 | 18 | function Req(handle,cb){ 19 | this._handle = handle 20 | this.oncomplete = cb 21 | } 22 | 23 | function decodeRequest(req){ 24 | // [method, url, httpver, headers, body] 25 | req[0] = req[0].toString('ascii') 26 | req[1] = req[1].toString('ascii') 27 | req[2] = req[2].toString('ascii') 28 | 29 | var hh = req[3] 30 | for(var hi = 0; hi < hh.length; hi++){ 31 | var kv = hh[hi] 32 | kv[0] = kv[0].toString('ascii') 33 | kv[1] = kv[1].toString('ascii') 34 | } 35 | // req[4] = req[4] 36 | 37 | return req 38 | } 39 | 40 | // var requestParser = jp([ 41 | // jp.string, //method 42 | // jp.string, //uri 43 | // jp.string, //httpver 44 | // jp.list([ //headers 45 | // jp.string, 46 | // jp.string]), 47 | // jp.binary //binary-body 48 | // ]) 49 | 50 | function parseHttpVersion(httpVer){ 51 | if(httpVer[0] === 'H'){ 52 | __assert(httpVer[1] === 'T' 53 | && httpVer[2] === 'T' 54 | && httpVer[3] === 'P' 55 | && httpVer[4] === '/', 56 | 'HTTP/') 57 | httpVer = httpVer.slice(5) 58 | } 59 | 60 | var Mm = httpVer.split('.') 61 | __assert(Mm.length === 2) 62 | 63 | var M = parseInt(Mm[0]) 64 | var m = parseInt(Mm[1]) 65 | 66 | __assert(!isNaN(M) && !isNaN(m), '!isNaN(M) && !isNaN(m)') 67 | 68 | return [M,m] 69 | } 70 | 71 | function bakeRequest(chunk){ 72 | // ('PUT', '/', 'HTTP/1.1', 73 | // [('adsfasdf', 'qewrqwer'), 74 | // ('Content-Length', '11'), 75 | // ('Accept', '*/*'), 76 | // ('User-Agent', 'curl/7.29.0'), 77 | // ('Host', 'localhost:8080'), 78 | // ('Cookie', 'adsfasdf=qewrqwer'), 79 | // ('Content-Type', 'application/x-www-form-urlencoded')], 'binary-body') 80 | 81 | // which also could be (note the other version format): 82 | // ('PUT', '/', '1.1', 83 | // [('adsfasdf', 'qewrqwer'), 84 | // ('Content-Length', '11'), 85 | // ('Accept', '*/*'), 86 | // ('User-Agent', 'curl/7.29.0'), 87 | // ('Host', 'localhost:8080'), 88 | // ('Cookie', 'adsfasdf=qewrqwer'), 89 | // ('Content-Type', 'application/x-www-form-urlencoded')], 'binary-body') 90 | 91 | __assert(Buffer.isBuffer(chunk),'parsing request not from a buffer') 92 | var rq = mp.unpack(chunk, true) 93 | rq = decodeRequest(rq) 94 | 95 | var method = rq[0] 96 | var uri = rq[1] 97 | var httpVer = rq[2] 98 | var hh = rq[3].map(function(kv){ 99 | return kv[0]+': '+kv[1] 100 | }) 101 | var Mm = parseHttpVersion(httpVer) 102 | hh.unshift(method+' '+uri+' HTTP/'+Mm[0]+'.'+Mm[1]) 103 | hh.push('\r\n') 104 | var requestBuf = Buffer(hh.join('\r\n')) 105 | if(rq[4].length){ 106 | var body = rq[4] 107 | requestBuf = Buffer.concat([requestBuf,body]) 108 | // TODO: we could return [requestBuf,body 109 | } 110 | trace && console.log('baked request: ================================================================') 111 | trace && console.log(requestBuf.toString('ascii')) 112 | return requestBuf 113 | } 114 | 115 | Req.prototype = { 116 | done:function(){ 117 | this.oncomplete(0,this._handle,this) 118 | }, 119 | fail:function(status){ 120 | this.oncomplete(status,this._handle,this) 121 | } 122 | } 123 | 124 | function WriteReq(handle,chunk,cb){ 125 | Req.call(this,handle,cb) 126 | this.buffer = chunk 127 | this.bytes = chunk.length 128 | } 129 | 130 | WriteReq.prototype.__proto__ = Req.prototype 131 | 132 | function StreamHandle(id,worker,listenHandle){ 133 | this._id = id 134 | this._worker = worker 135 | this._listenHandle = listenHandle 136 | this._meta = null 137 | 138 | this._first_outgoing = null 139 | 140 | this._paused = true 141 | 142 | this._read_ended = false 143 | this._read_done = false 144 | this._read_chunks = [] 145 | 146 | this._write_ended = false 147 | this._write_done = false 148 | this._write_reqs = [] 149 | this._shutdown_req = null 150 | 151 | this._cocaine_lingering_shutdown_enabled = false 152 | 153 | this._closing = false 154 | this._close_done = false 155 | 156 | this.onread = null 157 | 158 | this._hdl = {} 159 | util.bindHandlers(this.hdl,this._hdl,this) 160 | } 161 | 162 | StreamHandle.prototype = { 163 | writeQueueSize:0, 164 | _peer_addr:{ 165 | address:'0.0.0.0', 166 | port:12345 167 | }, 168 | _sock_addr:{ 169 | address:'127.0.0.1', 170 | port:12345 171 | }, 172 | 173 | push:function(chunk){ 174 | __assert(!this._read_ended) 175 | 176 | if(!this._connected){ 177 | debug('!this._connected') 178 | 179 | if(this._listenHandle){ 180 | 181 | debug('this._listenHandle.push()') 182 | 183 | this._listenHandle.push(this) 184 | this._connected = true 185 | } 186 | } 187 | 188 | if(this._closing){ 189 | debug('this._closing') 190 | return 191 | } 192 | 193 | if(chunk === null){ 194 | debug('chunk === null, so set this._read_ended = true') 195 | this._read_ended = true 196 | } 197 | 198 | // HACK: transform the only request chunk to usual http request 199 | if(chunk){ 200 | debug('// HACK: transform the only request chunk to usual http request') 201 | __assert(!this._meta,'got two request chunks in http request, which is absolutely not in 0.10 cocaine fashion') 202 | this._meta = chunk 203 | chunk = bakeRequest(chunk) 204 | debug('and request is ----\n%s\n--------', chunk.toString()) 205 | } 206 | 207 | if(this._paused){ 208 | debug('this._paused, so this._read_chunks.push(chunk)') 209 | this._read_chunks.push(chunk) 210 | } else { 211 | if(!this._read_ended){ 212 | debug('!this._read_ended, so this.onread(chunk,0,chunk.length)', chunk,0,chunk.length) 213 | this.onread(chunk,0,chunk.length) 214 | } else { 215 | debug('this._pushClose()') 216 | this._pushClose() 217 | } 218 | } 219 | }, 220 | 221 | _pushClose:function(){ 222 | __assert(!this._read_done) 223 | process._errno = 'EOF' 224 | global.errno = 'EOF' // node 0.8 compatibility 225 | this.onread() 226 | this._read_done = true 227 | }, 228 | 229 | pushChunk:function(chunk){ 230 | __assert(Buffer.isBuffer(chunk)) 231 | debug('pushChunk', chunk) 232 | this.push(chunk) 233 | }, 234 | 235 | pushChoke:function(){ 236 | this.push(null) 237 | }, 238 | 239 | pushError:function(errno){ 240 | process._errno = errno 241 | this.onread() 242 | }, 243 | 244 | _emitReadChunks:function(){ 245 | while(!this._paused && this._read_chunks.length){ 246 | var c = this._read_chunks.shift() 247 | if(c === null){ 248 | this._pushClose() 249 | } else { 250 | this.onread(c,0,c.length) 251 | } 252 | } 253 | }, 254 | 255 | readStart:function(){ 256 | __assert(!this._closing) 257 | if(this._paused){ 258 | this._paused = false 259 | this._emitReadChunks() 260 | } 261 | }, 262 | readStop:function(){ 263 | __assert(!this.closing) 264 | this._paused = true 265 | }, 266 | writeBuffer:function(chunk){ 267 | __assert(!this._closing) 268 | 269 | // HACK: pack all but first outgoing chunks 270 | // HACK: prepare to remove the above hack: first outgoing is packed 271 | // elsewhere, and we don't pack any following chunks 272 | if(!this._first_outgoing){ 273 | this._first_outgoing = chunk 274 | } else { 275 | // HACK: don't pack. see note above. 276 | //chunk = mp.pack(chunk) 277 | } 278 | 279 | //this._worker._handle.send(mp.pack([this._id, RPC.chunk, [chunk]])) 280 | this._worker._handle.send(mp.pack([this._id, 0, [chunk]])) 281 | var req = new WriteReq(this,chunk) 282 | this._write_reqs.push(req) 283 | process.nextTick(this._hdl.afterWrite) 284 | return req 285 | }, 286 | shutdown:function(){ 287 | __assert(!this._closing && 288 | !this._write_ended && !this._write_done) 289 | this._write_ended = true 290 | 291 | this._meta && (this._meta = null) 292 | this._first_outgoing && (this._first_outgoing = null) 293 | 294 | var req = this._shutdown_req = new Req(this) 295 | process.nextTick(this._hdl.afterShutdown) 296 | return req 297 | }, 298 | close:function(cb){ 299 | __assert(!this._closing) 300 | this._closing = true 301 | 302 | this._meta && (this._meta = null) 303 | this._first_outgoing && (this._first_outgoing = null) 304 | 305 | if(this._cocaine_lingering_shutdown_enabled) { 306 | this._worker._handle.send(mp.pack([this._id, 0, [""]])) 307 | } else { 308 | //this._worker._handle.send(mp.pack([this._id, RPC.choke, []])) 309 | this._worker._handle.send(mp.pack([this._id, 2, []])) 310 | } 311 | 312 | if(typeof cb === 'function'){ 313 | this.close = cb // the exact behavior of node::HandleWrap::Close 314 | } 315 | process.nextTick(this._hdl.afterClose) 316 | }, 317 | ref:function(){ 318 | this._worker.ref() 319 | }, 320 | unref:function(){ 321 | this._worker.unref() 322 | }, 323 | writeString:function(s, encoding){ 324 | debug('writeString', s, encoding) 325 | __assert(typeof s === 'string' 326 | && typeof s === 'string') 327 | return this.writeBuffer(new Buffer(s, encoding)) 328 | }, 329 | writeAsciiString:function(s){ 330 | return this.writeString(s, 'ascii') 331 | }, 332 | writeUtf8String:function(s){ 333 | return this.writeString(s, 'utf8') 334 | }, 335 | writeUcs2String:function(s){ 336 | return this.writeString(s, 'ucs2') 337 | }, 338 | writeBinaryString:function(s){ 339 | return this.writeString(s, 'binary') 340 | }, 341 | getpeername:function(){ 342 | return this._peer_addr 343 | }, 344 | _setpeername:function(addr){ 345 | this._peer_addr = addr 346 | }, 347 | getsockname:function(){ 348 | return this._sock_addr 349 | }, 350 | 351 | setCocaineLingeringShutdown: function(v){ 352 | __assert(v === true, "setCocaineLingeringShutdownworks in enable-only fashion, "+ 353 | "so use it as setCocaineLingeringShutdownworks(true)") 354 | this._cocaine_lingering_shutdown_enabled = true 355 | }, 356 | 357 | cocaineLingeringShutdownClose: function(){ 358 | this._worker._handle.send(mp.pack([this._id, 2, []])) 359 | }, 360 | 361 | 362 | hdl:{ 363 | afterShutdown:function(){ 364 | __assert(this._write_ended && !this._write_done 365 | && this._shutdown_req) 366 | if(!this._closing){ 367 | this._write_done = true 368 | var req = this._shutdown_req 369 | this._shutdown_req = null 370 | req.done() 371 | } 372 | }, 373 | afterWrite:function(){ 374 | __assert(!this._write_done) 375 | if(!this._closing){ 376 | var req 377 | while(req = this._write_reqs.shift()){ 378 | req.done() 379 | } 380 | } 381 | }, 382 | afterClose:function(){ 383 | __assert(this._closing) 384 | if(!this._close_done){ 385 | this._close_done = true 386 | if(this.close !== StreamHandle.prototype.close){ 387 | this.close() 388 | } 389 | } 390 | } 391 | } 392 | 393 | 394 | } 395 | 396 | 397 | function ListenHandle(connection_event, worker){ 398 | __assert(typeof connection_event === 'string' || typeof connection_event === 'number', 399 | "typeof connection_event === 'string' || typeof connection_event === 'number'") 400 | this._id = connection_event.toString() 401 | this._worker = worker 402 | this._listening = false 403 | this._closed = false 404 | this._pending_connections = [] 405 | this.onconnection = null 406 | } 407 | 408 | ListenHandle.prototype = { 409 | __proto__:TCP.prototype, 410 | createStreamHandle:function(sid,event){ 411 | __assert(event === this._id) 412 | return new StreamHandle(sid,this._worker,this) 413 | }, 414 | push:function(sh){ 415 | if(!this._closed){ 416 | if(this._listening){ 417 | this.onconnection(sh) 418 | } else { 419 | this._pending_connections.push(sh) 420 | } 421 | } 422 | }, 423 | ref:function(){ 424 | __assert(this._worker) 425 | this._worker.ref() 426 | }, 427 | unref:function(){ 428 | __assert(this._worker) 429 | this._worker.unref() 430 | }, 431 | listen:function(){ 432 | if(!this._listening){ 433 | this._listening = true 434 | this._worker.listen() 435 | this._emitPendingConnections() 436 | } 437 | }, 438 | close:function(){ 439 | if(!this._closed){ 440 | this._closed = true 441 | this._worker.removeListenHandle(this) 442 | } 443 | }, 444 | _emitPendingConnections:function(){ 445 | var sh 446 | while(!this._closed && (sh = this._pending_connections.shift())){ 447 | this.onconnection(sh) 448 | } 449 | }, 450 | readStart:notImplemented, 451 | readStop:notImplemented, 452 | shutdown:notImplemented, 453 | writeBuffer:notImplemented, 454 | writeAsciiString:notImplemented, 455 | writeUtf8String:notImplemented, 456 | writeUsc2String:notImplemented, 457 | writev:notImplemented, 458 | open:notImplemented, 459 | bind:notImplemented, 460 | connect:notImplemented, 461 | setNoDelay:notImplemented, 462 | setKeepAlive:notImplemented 463 | } 464 | 465 | 466 | function notImplemented(){ 467 | throw new Error('method not implemented') 468 | } 469 | 470 | 471 | 472 | module.exports = { 473 | ListenHandle:ListenHandle, 474 | StreamHandle:StreamHandle 475 | } 476 | 477 | 478 | 479 | -------------------------------------------------------------------------------- /lib/worker/handles2.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var mp = require('msgpack-bin') 4 | var __assert = require('assert') 5 | 6 | //var jp = require('@nojs/jampack') 7 | 8 | var debug = require('debug')('co:handles2') 9 | 10 | var util = require('../util') 11 | 12 | var TCP = process.binding('tcp_wrap').TCP 13 | 14 | var protocol = require('../protocol') 15 | var RPC = protocol.RPC 16 | 17 | var uv = process.binding('uv') 18 | 19 | var trace = 0 20 | 21 | function Req(handle,cb){ 22 | this._handle = handle 23 | this.oncomplete = cb 24 | } 25 | 26 | function decodeRequest(req){ 27 | // [method, url, httpver, headers, body] 28 | req[0] = req[0].toString('ascii') 29 | req[1] = req[1].toString('ascii') 30 | req[2] = req[2].toString('ascii') 31 | 32 | var hh = req[3] 33 | for(var hi = 0; hi < hh.length; hi++){ 34 | var kv = hh[hi] 35 | kv[0] = kv[0].toString('ascii') 36 | kv[1] = kv[1].toString('ascii') 37 | } 38 | // req[4] = req[4] 39 | 40 | return req 41 | } 42 | 43 | // var requestParser = jp([ 44 | // jp.string, //method 45 | // jp.string, //uri 46 | // jp.string, //httpver 47 | // jp.list([ //headers 48 | // jp.string, 49 | // jp.string]), 50 | // jp.binary //binary-body 51 | // ]) 52 | 53 | function parseHttpVersion(httpVer){ 54 | if(httpVer[0] === 'H'){ 55 | __assert(httpVer[1] === 'T' 56 | && httpVer[2] === 'T' 57 | && httpVer[3] === 'P' 58 | && httpVer[4] === '/', 59 | 'HTTP/') 60 | httpVer = httpVer.slice(5) 61 | } 62 | 63 | var Mm = httpVer.split('.') 64 | __assert(Mm.length === 2) 65 | 66 | var M = parseInt(Mm[0]) 67 | var m = parseInt(Mm[1]) 68 | 69 | __assert(!isNaN(M) && !isNaN(m), '!isNaN(M) && !isNaN(m)') 70 | 71 | return [M,m] 72 | } 73 | 74 | function bakeRequest(chunk){ 75 | // ('PUT', '/', 'HTTP/1.1', 76 | // [('adsfasdf', 'qewrqwer'), 77 | // ('Content-Length', '11'), 78 | // ('Accept', '*/*'), 79 | // ('User-Agent', 'curl/7.29.0'), 80 | // ('Host', 'localhost:8080'), 81 | // ('Cookie', 'adsfasdf=qewrqwer'), 82 | // ('Content-Type', 'application/x-www-form-urlencoded')], 'binary-body') 83 | 84 | // which also could be (note the other version format): 85 | // ('PUT', '/', '1.1', 86 | // [('adsfasdf', 'qewrqwer'), 87 | // ('Content-Length', '11'), 88 | // ('Accept', '*/*'), 89 | // ('User-Agent', 'curl/7.29.0'), 90 | // ('Host', 'localhost:8080'), 91 | // ('Cookie', 'adsfasdf=qewrqwer'), 92 | // ('Content-Type', 'application/x-www-form-urlencoded')], 'binary-body') 93 | 94 | __assert(Buffer.isBuffer(chunk),'parsing request not from a buffer') 95 | var rq = mp.unpack(chunk, true) 96 | rq = decodeRequest(rq) 97 | 98 | var method = rq[0] 99 | var uri = rq[1] 100 | var httpVer = rq[2] 101 | var hh = rq[3].map(function(kv){ 102 | return kv[0]+': '+kv[1] 103 | }) 104 | var Mm = parseHttpVersion(httpVer) 105 | hh.unshift(method+' '+uri+' HTTP/'+Mm[0]+'.'+Mm[1]) 106 | hh.push('\r\n') 107 | var requestBuf = Buffer(hh.join('\r\n')) 108 | if(rq[4].length){ 109 | var body = rq[4] 110 | requestBuf = Buffer.concat([requestBuf,body]) 111 | // TODO: we could return [requestBuf,body 112 | } 113 | trace && console.log('baked request: ================================================================') 114 | trace && console.log(requestBuf.toString('ascii')) 115 | return requestBuf 116 | } 117 | 118 | Req.prototype = { 119 | done:function(){ 120 | this.oncomplete(0,this._handle,this) 121 | }, 122 | fail:function(status){ 123 | this.oncomplete(status,this._handle,this) 124 | } 125 | } 126 | 127 | function WriteReq(handle,chunk,cb){ 128 | Req.call(this,handle,cb) 129 | this.buffer = chunk 130 | this.bytes = chunk.length 131 | } 132 | 133 | WriteReq.prototype.__proto__ = Req.prototype 134 | 135 | function StreamHandle(id,worker,listenHandle){ 136 | this._id = id 137 | this._worker = worker 138 | this._listenHandle = listenHandle 139 | this._meta = null 140 | 141 | this._first_outgoing = null 142 | 143 | this._paused = true 144 | 145 | this._read_ended = false 146 | this._read_done = false 147 | this._read_chunks = [] 148 | 149 | this._write_ended = false 150 | this._write_done = false 151 | this._write_reqs = [] 152 | this._shutdown_req = null 153 | 154 | this._cocaine_lingering_shutdown_enabled = false 155 | 156 | this._closing = false 157 | this._close_done = false 158 | 159 | this.onread = null 160 | 161 | this._hdl = {} 162 | util.bindHandlers(this.hdl,this._hdl,this) 163 | } 164 | 165 | StreamHandle.prototype = { 166 | writeQueueSize:0, 167 | _peer_addr:{ 168 | address:'0.0.0.0', 169 | port:12345 170 | }, 171 | _sock_addr:{ 172 | address:'127.0.0.1', 173 | port:12345 174 | }, 175 | 176 | push:function(chunk){ 177 | __assert(!this._read_ended) 178 | 179 | if(!this._connected){ 180 | debug('!this._connected') 181 | 182 | if(this._listenHandle){ 183 | 184 | debug('this._listenHandle.push()') 185 | 186 | this._listenHandle.push(this) 187 | this._connected = true 188 | } 189 | } 190 | 191 | if(this._closing){ 192 | debug('this._closing') 193 | return 194 | } 195 | 196 | if(chunk === null){ 197 | debug('chunk === null, so set this._read_ended = true') 198 | this._read_ended = true 199 | } 200 | 201 | // HACK: transform the only request chunk to usual http request 202 | if(chunk){ 203 | debug('// HACK: transform the only request chunk to usual http request') 204 | __assert(!this._meta,'got two request chunks in http request, which is absolutely not in 0.10 cocaine fashion') 205 | this._meta = chunk 206 | chunk = bakeRequest(chunk) 207 | debug('and request is ----\n%s\n--------', chunk.toString()) 208 | } 209 | 210 | if(this._paused){ 211 | debug('this._paused, so this._read_chunks.push(chunk)') 212 | this._read_chunks.push(chunk) 213 | } else { 214 | if(!this._read_ended){ 215 | debug('!this._read_ended, so this.onread(chunk,0,chunk.length)', chunk,0,chunk.length) 216 | this.onread(chunk.length, chunk) 217 | } else { 218 | debug('this._pushClose()') 219 | this._pushClose() 220 | } 221 | } 222 | }, 223 | 224 | _pushClose:function(){ 225 | __assert(!this._read_done) 226 | process._errno = 'EOF' 227 | global.errno = 'EOF' // node 0.8 compatibility 228 | this.onread(uv.UV_EOF) 229 | this._read_done = true 230 | }, 231 | 232 | pushChunk:function(chunk){ 233 | __assert(Buffer.isBuffer(chunk)) 234 | debug('pushChunk', chunk) 235 | this.push(chunk) 236 | }, 237 | 238 | pushChoke:function(){ 239 | this.push(null) 240 | }, 241 | 242 | pushError:function(errno){ 243 | process._errno = errno 244 | this.onread() 245 | }, 246 | 247 | _emitReadChunks:function(){ 248 | while(!this._paused && this._read_chunks.length){ 249 | var c = this._read_chunks.shift() 250 | if(c === null){ 251 | this._pushClose() 252 | } else { 253 | this.onread(c,0,c.length) 254 | } 255 | } 256 | }, 257 | 258 | readStart:function(){ 259 | __assert(!this._closing) 260 | if(this._paused){ 261 | this._paused = false 262 | this._emitReadChunks() 263 | } 264 | }, 265 | readStop:function(){ 266 | __assert(!this.closing) 267 | this._paused = true 268 | }, 269 | writeBuffer:function(req, chunk){ 270 | __assert(!this._closing) 271 | 272 | // HACK: pack all but first outgoing chunks 273 | // HACK: prepare to remove the above hack: first outgoing is packed 274 | // elsewhere, and we don't pack any following chunks 275 | if(!this._first_outgoing){ 276 | this._first_outgoing = chunk 277 | } else { 278 | // HACK: don't pack. see note above. 279 | //chunk = mp.pack(chunk) 280 | } 281 | 282 | //this._worker._handle.send(mp.pack([this._id, RPC.chunk, [chunk]])) 283 | this._worker._handle.send(mp.pack([this._id, 0, [chunk]])) 284 | //var req = new WriteReq(this,chunk) 285 | this._write_reqs.push(req) 286 | process.nextTick(this._hdl.afterWrite) 287 | return 0 288 | //return req 289 | }, 290 | shutdown:function(req){ 291 | __assert(!this._closing && 292 | !this._write_ended && !this._write_done) 293 | this._write_ended = true 294 | 295 | this._meta && (this._meta = null) 296 | this._first_outgoing && (this._first_outgoing = null) 297 | 298 | this._shutdown_req = req 299 | process.nextTick(this._hdl.afterShutdown) 300 | return 0 301 | }, 302 | close:function(cb){ 303 | __assert(!this._closing) 304 | this._closing = true 305 | 306 | this._meta && (this._meta = null) 307 | this._first_outgoing && (this._first_outgoing = null) 308 | 309 | if(this._cocaine_lingering_shutdown_enabled) { 310 | this._worker._handle.send(mp.pack([this._id, 0, [""]])) 311 | } else { 312 | //this._worker._handle.send(mp.pack([this._id, RPC.choke, []])) 313 | this._worker._handle.send(mp.pack([this._id, 2, []])) 314 | } 315 | 316 | if(typeof cb === 'function'){ 317 | this.close = cb // the exact behavior of node::HandleWrap::Close 318 | } 319 | process.nextTick(this._hdl.afterClose) 320 | }, 321 | ref:function(){ 322 | this._worker.ref() 323 | }, 324 | unref:function(){ 325 | this._worker.unref() 326 | }, 327 | writeString:function(req, s, encoding){ 328 | debug('writeString', req, s, encoding) 329 | __assert(typeof s === 'string' 330 | && typeof s === 'string') 331 | return this.writeBuffer(req, new Buffer(s, encoding)) 332 | }, 333 | writeAsciiString:function(req, s){ 334 | return this.writeString(req, s, 'ascii') 335 | }, 336 | writeUtf8String:function(req, s){ 337 | return this.writeString(req, s, 'utf8') 338 | }, 339 | writeUcs2String:function(req, s){ 340 | return this.writeString(req, s, 'ucs2') 341 | }, 342 | writeBinaryString:function(req, s){ 343 | return this.writeString(req, s, 'binary') 344 | }, 345 | getpeername:function(){ 346 | return this._peer_addr 347 | }, 348 | _setpeername:function(addr){ 349 | this._peer_addr = addr 350 | }, 351 | getsockname:function(){ 352 | return this._sock_addr 353 | }, 354 | 355 | setCocaineLingeringShutdown: function(v){ 356 | __assert(v === true, "setCocaineLingeringShutdownworks in enable-only fashion, "+ 357 | "so use it as setCocaineLingeringShutdownworks(true)") 358 | this._cocaine_lingering_shutdown_enabled = true 359 | }, 360 | 361 | cocaineLingeringShutdownClose: function(){ 362 | this._worker._handle.send(mp.pack([this._id, 2, []])) 363 | }, 364 | 365 | hdl:{ 366 | afterShutdown:function(){ 367 | __assert(this._write_ended && !this._write_done 368 | && this._shutdown_req) 369 | if(!this._closing){ 370 | this._write_done = true 371 | var req = this._shutdown_req 372 | this._shutdown_req = null 373 | var handle = req.handle 374 | req.handle = null 375 | if(typeof req.oncomplete === 'function'){ 376 | req.oncomplete(0, this, req) 377 | } 378 | } 379 | }, 380 | afterWrite:function(){ 381 | __assert(!this._write_done) 382 | if(!this._closing){ 383 | var req 384 | while(req = this._write_reqs.shift()){ 385 | var handle = req.handle 386 | req.handle = null 387 | if(typeof req.oncomplete === 'function'){ 388 | req.oncomplete(0, this, req) 389 | } 390 | } 391 | } 392 | }, 393 | afterClose:function(){ 394 | __assert(this._closing) 395 | if(!this._close_done){ 396 | this._close_done = true 397 | if(this.close !== StreamHandle.prototype.close){ 398 | this.close() 399 | } 400 | } 401 | } 402 | } 403 | 404 | 405 | } 406 | 407 | 408 | function ListenHandle(connection_event, worker){ 409 | __assert(typeof connection_event === 'string' || typeof connection_event === 'number', 410 | "typeof connection_event === 'string' || typeof connection_event === 'number'") 411 | this._id = connection_event.toString() 412 | this._worker = worker 413 | this._listening = false 414 | this._closed = false 415 | this._pending_connections = [] 416 | this.onconnection = null 417 | } 418 | 419 | ListenHandle.prototype = { 420 | __proto__:TCP.prototype, 421 | createStreamHandle:function(sid,event){ 422 | __assert(event === this._id) 423 | return new StreamHandle(sid,this._worker,this) 424 | }, 425 | push:function(sh){ 426 | if(!this._closed){ 427 | if(this._listening){ 428 | this.onconnection(null, sh) 429 | } else { 430 | this._pending_connections.push(sh) 431 | } 432 | } 433 | }, 434 | ref:function(){ 435 | __assert(this._worker) 436 | this._worker.ref() 437 | }, 438 | unref:function(){ 439 | __assert(this._worker) 440 | this._worker.unref() 441 | }, 442 | listen:function(){ 443 | if(!this._listening){ 444 | this._listening = true 445 | this._worker.listen() 446 | this._emitPendingConnections() 447 | } 448 | }, 449 | close:function(){ 450 | if(!this._closed){ 451 | this._closed = true 452 | this._worker.removeListenHandle(this) 453 | } 454 | }, 455 | _emitPendingConnections:function(){ 456 | var sh 457 | while(!this._closed && (sh = this._pending_connections.shift())){ 458 | this.onconnection(null, sh) 459 | } 460 | }, 461 | readStart:notImplemented, 462 | readStop:notImplemented, 463 | shutdown:notImplemented, 464 | writeBuffer:notImplemented, 465 | writeAsciiString:notImplemented, 466 | writeUtf8String:notImplemented, 467 | writeUsc2String:notImplemented, 468 | writev:notImplemented, 469 | open:notImplemented, 470 | bind:notImplemented, 471 | connect:notImplemented, 472 | setNoDelay:notImplemented, 473 | setKeepAlive:notImplemented 474 | } 475 | 476 | 477 | function notImplemented(){ 478 | throw new Error('method not implemented') 479 | } 480 | 481 | 482 | 483 | module.exports = { 484 | ListenHandle:ListenHandle, 485 | StreamHandle:StreamHandle 486 | } 487 | 488 | 489 | 490 | --------------------------------------------------------------------------------