UNPKG

@carbon-io/fibers

Version:
295 lines (262 loc) 8.49 kB
var inspect = require('util').inspect var Module = require('module') /*************************************************************************************************** * @namespace fibers */ var debug = require('debug')('@carbon-io/fibers') var fibrous = require('@carbon-io/fibrous') var Fiber = require('fibers') // NOTE: we need to grab Future from fibrous since fibrous defines future on // Function.prototype as an accessor descriptor and Future does not guard // against resetting this property on Function.prototype. var Future = fibrous.Future var _debugLogger = function(mod, msg) { debug(msg) if (mod === require.main) { console.error(msg) } } /*************************************************************************************************** * @method getPoolSize * @description getPoolSize description * @memberof fibers * @returns {xxx} -- Fiber's current pool size */ function getFiberPoolSize() { return Fiber.poolSize } /*************************************************************************************************** * @method setPoolSize * @description setPoolSize description * @memberof fibers * @param {Integer} poolSize -- set Fiber's pool size to poolSize * @returns {undefined} -- undefined */ function setFiberPoolSize(poolSize) { Fiber.poolSize = poolSize } /*************************************************************************************************** * @method getFibersCreated * @description getFibersCreated description * @memberof fibers * @returns {number} -- The number of fibers created */ function getFibersCreated() { return Fiber.fibersCreated } /*************************************************************************************************** * @method getCurrentFiber * @description getCurrentFiber description * @memberof fibers * @returns {xxx} -- The current fiber */ function getCurrentFiber() { return Fiber.current } var __spawn = function(f, cb, logger) { if (cb) { return spawn(f, function(result) { cb(null, result) }, function(err) { cb(err) }, logger) } return spawn(f, undefined, undefined, logger) } // ensure that f gets executed in a Fiber: var __ensure = function(mod) { // XXX: consider using `Module.parent` and `delete require.cache[module.id]` if (!(mod instanceof Module)) { throw new TypeError( 'Module required (e.g., "var __ = require(\'@carbon-io/fibers\').__(module)")') } var __ = function(f, cb) { if (typeof __ensure._getCurrentFiber() === 'undefined') { return __spawn(f, cb, _debugLogger.bind(undefined, mod)) } var ret = undefined try { ret = f() if (cb) { cb(null, ret) } } catch (e) { // XXX: do we want to log to stderr here? setting mod to undefined for // now to disable stderr _debugLogger( undefined, 'Exception caught with cb undefined in __: ' + inspect(e)) if (cb) { cb(e) } else { throw e } } } // __(.main)? __.main = function() { console.warn('"__.main" is DEPRECATED and no longer necessary') return __.apply(undefined, arguments) } // __(.ensure)*(.main)? __.ensure = __ // __(.(ensure|main))* __.main.ensure = __ // __(.(ensure|main))*(.spawn)? __.spawn = __spawn // __(.(ensure|main))*(.spawn(.main(.(ensure|main))*)?)? __.spawn.main = __.main // __(.(ensure|main))*(.spawn(.(ensure|main))*)? __.spawn.ensure = __ // __(.(spawn|ensure|main))* __.spawn.spawn = __spawn return __ } // to allow stubbing in tests (can probably get rid of this with proxy objects?) __ensure._getCurrentFiber = getCurrentFiber /*************************************************************************************************** * @method syncInvoke * @description Based on technique used by 0ctave and olegp: * (https://github.com/0ctave/node-sync/blob/master/lib/sync.js) * (https://github.com/olegp/mongo-sync/blob/master/lib/mongo-sync.js) * @memberof fibers * @param {Object} that -- receiver * @param {String} method -- name of method * @param {Array} args -- xxx * * @throws {Error} -- xxx * @returns {undefined} -- undefined * @ignore */ function syncInvoke(that, method, args) { var result var fiber = getCurrentFiber() var yielded = false var callbackCalled = false var wasError = false // augment original args with callback args = args ? Array.prototype.slice.call(args) : [] args.push(function(error, value) { callbackCalled = true if (error) { wasError = true } if (yielded) { // this may or may not occur after the yield() call below fiber.run(error || value) } else { result = error || value } }) // apply() may or may not result in callback being called synchronously if (that) { that[method].apply(that, args) } else { method.apply(undefined, args) } if (!callbackCalled) { // check if apply() called callback yielded = true result = Fiber.yield() } if (wasError) { if (result instanceof Error){ throw result } else { var errorMsg = result.message || JSON.stringify(result) throw new Error(errorMsg) } } return result } // Number.MAX_SAFE_INTEGER == 9007199254740991 // this would roll over in 104249 days at 10**6 spawns/sec var _spawnBookkeeping = { _fiberId: 0, _fibers: {_length: 0}, _getFiberId: function() { return this._fiberId++ } } /*************************************************************************************************** * @method spawn * @description spawn description * @memberof fibers * @param {Function} f -- function to spawn within a Fiber * @param {Function} next -- optional callback * @param {Function} error -- optional callback * @throws {Exception} -- if no error callback is passed, any exception will be * bubbled up if running synchronously, otherwise, errors * will be lost * @returns {xxx} -- if `next` is not passed, the result of `f` will be returned */ function spawn(f, next, error, logger) { // XXX: logger is explicitly omitted from the documentation since this is // just a temporary fix var fiberFunction = function(fiberId) { var ret = undefined try { ret = f() if (next) { return next(ret) } else { // drop it } } catch (e) { (logger || debug)(e.stack) if (error) { // if there's an error callback then throw it that way return error(e) } else { throw e } } finally { // clean up var fiber_ = _spawnBookkeeping._fibers[fiberId] if (typeof fiber_ === 'undefined') { throw new Error('Failed to find current fiber in spawn.fibers') } // remove our handle on this fiber so that it will get garbage collected delete _spawnBookkeeping._fibers[fiberId] --_spawnBookkeeping._fibers._length } } var fiberId = _spawnBookkeeping._getFiberId() var fiber = new Fiber(fiberFunction.bind(undefined, fiberId)) // maintain a handle for this fiber so it doesn't get garbage collected _spawnBookkeeping._fibers[fiberId] = fiber ++_spawnBookkeeping._fibers._length // otherwise, run async on nextTick process.nextTick(function(fiber) { try { fiber.run() } catch (e) { // we will only get here if next is defined, but error is not // assume that the caller doesn't care if there is an error, but log // to `debug` in case (logger || debug)( 'Exception caught with error undefined in fibers.spawn: ' + inspect(e)) } }.bind(undefined, fiber)) } /*************************************************************************************************** * module.exports */ module.exports = { __: __ensure, getFiberPoolSize: getFiberPoolSize, setFiberPoolSize: setFiberPoolSize, getFibersCreated: getFibersCreated, getCurrentFiber: getCurrentFiber, syncInvoke: syncInvoke, // Backward compat spawn: spawn, // Backward compat _spawnBookkeeping: _spawnBookkeeping, // expose spawn bookkeeping for tests } Object.defineProperty(module.exports, '$Test', { enumerable: false, configurable: false, writeable: false, get: function() { return require('./test/index.js') } })