<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var extend = require('xtend');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');

var errors = require('./errors');
var States = require('./reqres_states');
var Operations = require('./operations');

var DEFAULT_OUTGOING_REQ_TIMEOUT = 2000;
var CONNECTION_BASE_IDENTIFIER = 0;

function TChannelConnectionBase(channel, direction, remoteAddr) {
    assert(!channel.destroyed, 'refuse to create connection for destroyed channel');

    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.timedOutEvent = self.defineEvent('timedOut');
    self.spanEvent = self.defineEvent('span');

    self.closing = false;
    self.closeError = null;
    self.closeEvent = self.defineEvent('close');

    self.channel = channel;
    self.options = self.channel.options;
    self.logger = channel.logger;
    self.random = channel.random;
    self.timers = channel.timers;
    self.direction = direction;
    self.remoteAddr = remoteAddr;
    self.timer = null;
    self.remoteName = null; // filled in by identify message

    self.ops = new Operations({
        timers: self.timers,
        logger: self.logger,
        random: self.random,
        initTimeout: self.channel.initTimeout,
        timeoutCheckInterval: self.options.timeoutCheckInterval,
        timeoutFuzz: self.options.timeoutFuzz,
        connection: self
    });

    self.guid = ++CONNECTION_BASE_IDENTIFIER;

    self.tracer = self.channel.tracer;
    self.ops.startTimeoutTimer();
}
inherits(TChannelConnectionBase, EventEmitter);

TChannelConnectionBase.prototype.close = function close(callback) {
    var self = this;

    self.resetAll(errors.SocketClosedError({reason: 'local close'}));
    callback();
};

// this connection is completely broken, and is going away
// In addition to erroring out all of the pending work, we reset the state in case anybody
// stumbles across this object in a core dump.
TChannelConnectionBase.prototype.resetAll = function resetAll(err) {
    var self = this;

    self.ops.destroy();

    if (self.closing) return;
    self.closing = true;
    self.closeError = err;
    self.closeEvent.emit(self, err);

    var requests = self.ops.getRequests();
    var pending = self.ops.getPending();

    var inOpKeys = Object.keys(requests.in);
    var outOpKeys = Object.keys(requests.out);

    if (!err) {
        err = new Error('unknown connection reset'); // TODO typed error
    }

    var logInfo = {
        error: err,
        remoteName: self.remoteName,
        localName: self.channel.hostPort,
        numInOps: inOpKeys.length,
        numOutOps: outOpKeys.length,
        inPending: pending.in,
        outPending: pending.out
    };

    if (err.type && err.type.lastIndexOf('tchannel.socket', 0) < 0) {
        self.logger.warn('resetting connection', logInfo);
        self.errorEvent.emit(self, err);
    } else if (err.type !== 'tchannel.socket-closed') {
        logInfo.error = extend(err);
        logInfo.error.message = err.message;
        self.logger.info('resetting connection', logInfo);
    }

    // requests that we've received we can delete, but these reqs may have started their
    //   own outgoing work, which is hard to cancel. By setting this.closing, we make sure
    //   that once they do finish that their callback will swallow the response.
    inOpKeys.forEach(function eachInOp(id) {
        // TODO: support canceling pending handlers
        self.ops.removeReq(id);
        // TODO report or handle or log errors or something
    });

    // for all outgoing requests, forward the triggering error to the user callback
    outOpKeys.forEach(function eachOutOp(id) {
        var req = requests.out[id];
        self.ops.removeReq(id);
        err = errors.TChannelConnectionResetError(err);
        req.errorEvent.emit(req, err);
    });

    self.ops.clear();
};

TChannelConnectionBase.prototype.popOutReq = function popOutReq(id) {
    var self = this;

    return self.ops.popOutReq(id);
};

// create a request
TChannelConnectionBase.prototype.request = function connBaseRequest(options) {
    var self = this;
    if (!options) options = {};
    options.remoteAddr = self.remoteAddr;

    // TODO: use this to protect against >4Mi outstanding messages edge case
    // (e.g. zombie operation bug, incredible throughput, or simply very long
    // timeout
    // assert(!self.requests.out[id], 'duplicate frame id in flight');
    // TODO: provide some sort of channel default for "service"
    // TODO: generate tracing if empty?
    // TODO: refactor callers
    options.checksumType = options.checksum;

    // TODO: better default, support for dynamic
    options.ttl = options.timeout || DEFAULT_OUTGOING_REQ_TIMEOUT;
    options.tracer = self.tracer;
    var req = self.buildOutRequest(options);

    return self.ops.addOutReq(req);
};

TChannelConnectionBase.prototype.handleCallRequest = function handleCallRequest(req) {
    var self = this;

    self.ops.addInReq(req);

    req.remoteAddr = self.remoteName;
    req.errorEvent.on(onReqError);

    process.nextTick(runHandler);

    function onReqError(err) {
        self.onReqError(req, err);
    }

    function runHandler() {
        self.runHandler(req);
    }
};

TChannelConnectionBase.prototype.onReqError = function onReqError(req, err) {
    var self = this;
    if (!req.res) self.buildResponse(req);
    if (err.type === 'tchannel.timeout' ||
        err.type === 'tchannel.request.timeout'
    ) {
        req.res.sendError('Timeout', err.message);
    } else {
        var errName = err.name || err.constructor.name;
        req.res.sendError('UnexpectedError', errName + ': ' + err.message);
    }
};

TChannelConnectionBase.prototype.runHandler = function runHandler(req) {
    var self = this;
    self.channel.handler.handleRequest(req, buildResponse);
    function buildResponse(options) {
        return self.buildResponse(req, options);
    }
};

TChannelConnectionBase.prototype.buildResponse = function buildResponse(req, options) {
    var self = this;
    var done = false;
    if (req.res && req.res.state !== States.Initial) {
        self.errorEvent.emit(self, errors.ResponseAlreadyStarted({
            state: req.res.state
        }));
    }
    req.res = self.buildOutResponse(req, options);
    req.res.finishEvent.on(opDone);
    req.res.spanEvent.on(handleSpanFromRes);
    return req.res;

    function handleSpanFromRes(span) {
        self.spanEvent.emit(self, span);
    }

    function opDone() {
        if (done) return;
        done = true;
        self.onReqDone(req);
    }
};

TChannelConnectionBase.prototype.onReqDone = function onReqDone(req) {
    var self = this;

    var inreq = self.ops.popInReq(req.id);

    // incoming req that timed out are already cleaned up
    if (inreq !== req && !req.timedOut) {
        self.logger.warn('mismatched onReqDone callback', {
            hostPort: self.channel.hostPort,
            hasInReq: inreq !== undefined,
            id: req.id
        });
    }
};

module.exports = TChannelConnectionBase;
<commit_msg>connection: listen to response errors and error()<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var extend = require('xtend');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');

var errors = require('./errors');
var States = require('./reqres_states');
var Operations = require('./operations');

var DEFAULT_OUTGOING_REQ_TIMEOUT = 2000;
var CONNECTION_BASE_IDENTIFIER = 0;

function TChannelConnectionBase(channel, direction, remoteAddr) {
    assert(!channel.destroyed, 'refuse to create connection for destroyed channel');

    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.timedOutEvent = self.defineEvent('timedOut');
    self.spanEvent = self.defineEvent('span');

    self.closing = false;
    self.closeError = null;
    self.closeEvent = self.defineEvent('close');

    self.channel = channel;
    self.options = self.channel.options;
    self.logger = channel.logger;
    self.random = channel.random;
    self.timers = channel.timers;
    self.direction = direction;
    self.remoteAddr = remoteAddr;
    self.timer = null;
    self.remoteName = null; // filled in by identify message

    self.ops = new Operations({
        timers: self.timers,
        logger: self.logger,
        random: self.random,
        initTimeout: self.channel.initTimeout,
        timeoutCheckInterval: self.options.timeoutCheckInterval,
        timeoutFuzz: self.options.timeoutFuzz,
        connection: self
    });

    self.guid = ++CONNECTION_BASE_IDENTIFIER;

    self.tracer = self.channel.tracer;
    self.ops.startTimeoutTimer();
}
inherits(TChannelConnectionBase, EventEmitter);

TChannelConnectionBase.prototype.close = function close(callback) {
    var self = this;

    self.resetAll(errors.SocketClosedError({reason: 'local close'}));
    callback();
};

// this connection is completely broken, and is going away
// In addition to erroring out all of the pending work, we reset the state in case anybody
// stumbles across this object in a core dump.
TChannelConnectionBase.prototype.resetAll = function resetAll(err) {
    var self = this;

    self.ops.destroy();

    if (self.closing) return;
    self.closing = true;
    self.closeError = err;
    self.closeEvent.emit(self, err);

    var requests = self.ops.getRequests();
    var pending = self.ops.getPending();

    var inOpKeys = Object.keys(requests.in);
    var outOpKeys = Object.keys(requests.out);

    if (!err) {
        err = new Error('unknown connection reset'); // TODO typed error
    }

    var logInfo = {
        error: err,
        remoteName: self.remoteName,
        localName: self.channel.hostPort,
        numInOps: inOpKeys.length,
        numOutOps: outOpKeys.length,
        inPending: pending.in,
        outPending: pending.out
    };

    if (err.type && err.type.lastIndexOf('tchannel.socket', 0) < 0) {
        self.logger.warn('resetting connection', logInfo);
        self.errorEvent.emit(self, err);
    } else if (err.type !== 'tchannel.socket-closed') {
        logInfo.error = extend(err);
        logInfo.error.message = err.message;
        self.logger.info('resetting connection', logInfo);
    }

    // requests that we've received we can delete, but these reqs may have started their
    //   own outgoing work, which is hard to cancel. By setting this.closing, we make sure
    //   that once they do finish that their callback will swallow the response.
    inOpKeys.forEach(function eachInOp(id) {
        // TODO: support canceling pending handlers
        self.ops.removeReq(id);
        // TODO report or handle or log errors or something
    });

    // for all outgoing requests, forward the triggering error to the user callback
    outOpKeys.forEach(function eachOutOp(id) {
        var req = requests.out[id];
        self.ops.removeReq(id);
        err = errors.TChannelConnectionResetError(err);
        req.errorEvent.emit(req, err);
    });

    self.ops.clear();
};

TChannelConnectionBase.prototype.popOutReq = function popOutReq(id) {
    var self = this;

    return self.ops.popOutReq(id);
};

// create a request
TChannelConnectionBase.prototype.request = function connBaseRequest(options) {
    var self = this;
    if (!options) options = {};
    options.remoteAddr = self.remoteAddr;

    // TODO: use this to protect against >4Mi outstanding messages edge case
    // (e.g. zombie operation bug, incredible throughput, or simply very long
    // timeout
    // assert(!self.requests.out[id], 'duplicate frame id in flight');
    // TODO: provide some sort of channel default for "service"
    // TODO: generate tracing if empty?
    // TODO: refactor callers
    options.checksumType = options.checksum;

    // TODO: better default, support for dynamic
    options.ttl = options.timeout || DEFAULT_OUTGOING_REQ_TIMEOUT;
    options.tracer = self.tracer;
    var req = self.buildOutRequest(options);

    return self.ops.addOutReq(req);
};

TChannelConnectionBase.prototype.handleCallRequest = function handleCallRequest(req) {
    var self = this;

    self.ops.addInReq(req);

    req.remoteAddr = self.remoteName;
    req.errorEvent.on(onReqError);

    process.nextTick(runHandler);

    function onReqError(err) {
        self.onReqError(req, err);
    }

    function runHandler() {
        self.runHandler(req);
    }
};

TChannelConnectionBase.prototype.onReqError = function onReqError(req, err) {
    var self = this;
    if (!req.res) self.buildResponse(req);
    if (err.type === 'tchannel.timeout' ||
        err.type === 'tchannel.request.timeout'
    ) {
        req.res.sendError('Timeout', err.message);
    } else {
        var errName = err.name || err.constructor.name;
        req.res.sendError('UnexpectedError', errName + ': ' + err.message);
    }
};

TChannelConnectionBase.prototype.runHandler = function runHandler(req) {
    var self = this;
    self.channel.handler.handleRequest(req, buildResponse);
    function buildResponse(options) {
        return self.buildResponse(req, options);
    }
};

TChannelConnectionBase.prototype.buildResponse = function buildResponse(req, options) {
    var self = this;
    var done = false;
    if (req.res && req.res.state !== States.Initial) {
        self.errorEvent.emit(self, errors.ResponseAlreadyStarted({
            state: req.res.state
        }));
    }
    req.res = self.buildOutResponse(req, options);
    req.res.errorEvent.on(onError);
    req.res.finishEvent.on(opDone);
    req.res.spanEvent.on(handleSpanFromRes);
    return req.res;

    function handleSpanFromRes(span) {
        self.spanEvent.emit(self, span);
    }

    function opDone() {
        if (done) return;
        done = true;
        self.onReqDone(req);
    }

    function onError(err) {
        self.onResponseError(err, req);
    }
};

function isStringOrBuffer(x) {
    return typeof x === 'string' || Buffer.isBuffer(x);
}

TChannelConnectionBase.prototype.onResponseError =
function onResponseError(err, req) {
    var self = this;

    var arg2 = isStringOrBuffer(req.res.arg2) ?
        req.res.arg2 : 'streaming';
    var arg3 = isStringOrBuffer(req.res.arg3) ?
        req.res.arg3 : 'streaming';

    self.logger.error('outgoing response has an error', {
        err: err,
        arg1: String(req.arg1),
        ok: req.res.ok,
        type: req.res.type,
        bufArg2: arg2.slice(0, 50),
        arg2: String(arg2).slice(0, 50),
        bufArg3: arg3.slice(0, 50),
        arg3: String(arg3).slice(0, 50)
    });
};

TChannelConnectionBase.prototype.onReqDone = function onReqDone(req) {
    var self = this;

    var inreq = self.ops.popInReq(req.id);

    // incoming req that timed out are already cleaned up
    if (inreq !== req && !req.timedOut) {
        self.logger.warn('mismatched onReqDone callback', {
            hostPort: self.channel.hostPort,
            hasInReq: inreq !== undefined,
            id: req.id
        });
    }
};

module.exports = TChannelConnectionBase;
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;

var errors = require('./errors');
var States = require('./reqres_states');

var DEFAULT_OUTGOING_REQ_TIMEOUT = 2000;

function TChannelConnectionBase(channel, direction, remoteAddr) {
    assert(!channel.destroyed, 'refuse to create connection for destroyed channel');

    var self = this;
    EventEmitter.call(self);
    self.channel = channel;
    self.options = self.channel.options;
    self.logger = channel.logger;
    self.random = channel.random;
    self.timers = channel.timers;
    self.direction = direction;
    self.remoteAddr = remoteAddr;
    self.timer = null;
    self.remoteName = null; // filled in by identify message

    // TODO: factor out an operation collection abstraction
    self.requests = {
        in: {},
        out: {}
    };
    self.pending = {
        in: 0,
        out: 0
    };

    self.lastTimeoutTime = 0;
    self.closing = false;

    self.startTimeoutTimer();
    self.tracer = self.channel.tracer;
}
inherits(TChannelConnectionBase, EventEmitter);

TChannelConnectionBase.prototype.close = function close(callback) {
    var self = this;
    self.resetAll(errors.SocketClosedError({reason: 'local close'}));
    callback();
};

// timeout check runs every timeoutCheckInterval +/- some random fuzz. Range is from
//   base - fuzz/2 to base + fuzz/2
TChannelConnectionBase.prototype.getTimeoutDelay = function getTimeoutDelay() {
    var self = this;
    var base = self.options.timeoutCheckInterval;
    var fuzz = self.options.timeoutFuzz;
    if (fuzz) {
        fuzz = Math.round(Math.floor(self.random() * fuzz) - (fuzz / 2));
    }
    return base + fuzz;
};

TChannelConnectionBase.prototype.startTimeoutTimer = function startTimeoutTimer() {
    var self = this;
    self.timer = self.timers.setTimeout(function onChannelTimeout() {
        // TODO: worth it to clear the fired self.timer objcet?
        self.onTimeoutCheck();
    }, self.getTimeoutDelay());
};

TChannelConnectionBase.prototype.clearTimeoutTimer = function clearTimeoutTimer() {
    var self = this;
    if (self.timer) {
        self.timers.clearTimeout(self.timer);
        self.timer = null;
    }
};

// If the connection has some success and some timeouts, we should probably leave it up,
// but if everything is timing out, then we should kill the connection.
TChannelConnectionBase.prototype.onTimeoutCheck = function onTimeoutCheck() {
    var self = this;
    if (self.closing) {
        return;
    }
    if (self.lastTimeoutTime) {
        self.emit('timedOut');
    } else {
        self.checkTimeout(self.requests.out, 'out');
        self.checkTimeout(self.requests.in, 'in');
        self.startTimeoutTimer();
    }
};

TChannelConnectionBase.prototype.checkTimeout = function checkTimeout(ops, direction) {
    var self = this;
    var opKeys = Object.keys(ops);
    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var req = ops[id];
        if (req === undefined) {
            self.logger.warn('unexpected undefined request', {
                direction: direction,
                id: id
            });
        } else if (req.timedOut) {
            self.logger.warn('lingering timed-out request', {
                direction: direction,
                id: id
            });
            delete ops[id];
            self.pending[direction]--;
        } else if (req.checkTimeout()) {
            if (direction === 'out') {
                self.lastTimeoutTime = self.timers.now();
            // } else {
            //     req.res.sendError // XXX may need to build
            }
            delete ops[id];
            self.pending[direction]--;
        }
    }
};

// this connection is completely broken, and is going away
// In addition to erroring out all of the pending work, we reset the state in case anybody
// stumbles across this object in a core dump.
TChannelConnectionBase.prototype.resetAll = function resetAll(err) {
    var self = this;

    self.clearTimeoutTimer();

    if (self.closing) return;
    self.closing = true;

    var inOpKeys = Object.keys(self.requests.in);
    var outOpKeys = Object.keys(self.requests.out);

    if (!err) {
        err = new Error('unknown connection reset'); // TODO typed error
    }

    var isError = err.type.indexOf('tchannel.socket') !== 0;
    self.logger[isError ? 'warn' : 'info']('resetting connection', {
        error: err,
        remoteName: self.remoteName,
        localName: self.channel.hostPort,
        numInOps: inOpKeys.length,
        numOutOps: outOpKeys.length,
        inPending: self.pending.in,
        outPending: self.pending.out
    });

    if (isError) {
        self.emit('error', err);
    }

    // requests that we've received we can delete, but these reqs may have started their
    //   own outgoing work, which is hard to cancel. By setting this.closing, we make sure
    //   that once they do finish that their callback will swallow the response.
    inOpKeys.forEach(function eachInOp(id) {
        // TODO: support canceling pending handlers
        delete self.requests.in[id];
        // TODO report or handle or log errors or something
    });

    // for all outgoing requests, forward the triggering error to the user callback
    outOpKeys.forEach(function eachOutOp(id) {
        var req = self.requests.out[id];
        delete self.requests.out[id];
        // TODO: shared mutable object... use Object.create(err)?
        req.emit('error', err);
    });

    self.pending.in = 0;
    self.pending.out = 0;
};

TChannelConnectionBase.prototype.popOutReq = function popOutReq(id) {
    var self = this;
    var req = self.requests.out[id];
    if (!req) {
        // TODO else case. We should warn about an incoming response for an
        // operation we did not send out.  This could be because of a timeout
        // or could be because of a confused / corrupted server.
        return;
    }
    delete self.requests.out[id];
    self.pending.out--;
    return req;
};

// create a request
TChannelConnectionBase.prototype.request = function connBaseRequest(options) {
    var self = this;
    if (!options) options = {};
    options.remoteAddr = self.remoteAddr;

    // TODO: use this to protect against >4Mi outstanding messages edge case
    // (e.g. zombie operation bug, incredible throughput, or simply very long
    // timeout
    // assert(!self.requests.out[id], 'duplicate frame id in flight');
    // TODO: provide some sort of channel default for "service"
    // TODO: generate tracing if empty?
    // TODO: refactor callers
    options.checksumType = options.checksum;

    // TODO: better default, support for dynamic
    options.ttl = options.timeout || DEFAULT_OUTGOING_REQ_TIMEOUT;
    options.tracer = self.tracer;
    var req = self.buildOutRequest(options);

    return self._addOutReq(req);
};

TChannelConnectionBase.prototype._addOutReq = function _addOutReq(req) {
    var self = this;
    self.requests.out[req.id] = req;
    self.pending.out++;
    return req;
};

TChannelConnectionBase.prototype.handleCallRequest = function handleCallRequest(req) {
    var self = this;
    req.remoteAddr = self.remoteName;
    self.pending.in++;
    self.requests.in[req.id] = req;
    req.on('error', onReqError);
    process.nextTick(runHandler);

    function onReqError(err) {
        self.onReqError(req, err);
    }

    function runHandler() {
        self.runHandler(req);
    }
};

TChannelConnectionBase.prototype.onReqError = function onReqError(req, err) {
    var self = this;
    if (!req.res) self.buildResponse(req);
    if (err.type === 'tchannel.timeout') {
        req.res.sendError('Timeout', err.message);
    } else {
        var errName = err.name || err.constructor.name;
        req.res.sendError('UnexpectedError', errName + ': ' + err.message);
    }
};

TChannelConnectionBase.prototype.runHandler = function runHandler(req) {
    var self = this;
    self.channel.handler.handleRequest(req, buildResponse);
    function buildResponse(options) {
        return self.buildResponse(req, options);
    }
};

TChannelConnectionBase.prototype.buildResponse = function buildResponse(req, options) {
    var self = this;
    var done = false;
    if (req.res && req.res.state !== States.Initial) {
        self.emit('error', errors.ResponseAlreadyStarted({
            state: req.res.state
        }));
    }
    req.res = self.buildOutResponse(req, options);
    req.res.on('finish', opDone);
    req.res.on('span', handleSpanFromRes);
    return req.res;

    function handleSpanFromRes(span) {
        self.emit('span', span);
    }

    function opDone() {
        if (done) return;
        done = true;
        if (self.requests.in[req.id] !== req) {
            self.logger.warn('mismatched opDone callback', {
                hostPort: self.channel.hostPort,
                id: req.id
            });
            return;
        }
        delete self.requests.in[req.id];
        self.pending.in--;
    }
};

module.exports = TChannelConnectionBase;
<commit_msg>ConnectionBase: factor out #onReqDone<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;

var errors = require('./errors');
var States = require('./reqres_states');

var DEFAULT_OUTGOING_REQ_TIMEOUT = 2000;

function TChannelConnectionBase(channel, direction, remoteAddr) {
    assert(!channel.destroyed, 'refuse to create connection for destroyed channel');

    var self = this;
    EventEmitter.call(self);
    self.channel = channel;
    self.options = self.channel.options;
    self.logger = channel.logger;
    self.random = channel.random;
    self.timers = channel.timers;
    self.direction = direction;
    self.remoteAddr = remoteAddr;
    self.timer = null;
    self.remoteName = null; // filled in by identify message

    // TODO: factor out an operation collection abstraction
    self.requests = {
        in: {},
        out: {}
    };
    self.pending = {
        in: 0,
        out: 0
    };

    self.lastTimeoutTime = 0;
    self.closing = false;

    self.startTimeoutTimer();
    self.tracer = self.channel.tracer;
}
inherits(TChannelConnectionBase, EventEmitter);

TChannelConnectionBase.prototype.close = function close(callback) {
    var self = this;
    self.resetAll(errors.SocketClosedError({reason: 'local close'}));
    callback();
};

// timeout check runs every timeoutCheckInterval +/- some random fuzz. Range is from
//   base - fuzz/2 to base + fuzz/2
TChannelConnectionBase.prototype.getTimeoutDelay = function getTimeoutDelay() {
    var self = this;
    var base = self.options.timeoutCheckInterval;
    var fuzz = self.options.timeoutFuzz;
    if (fuzz) {
        fuzz = Math.round(Math.floor(self.random() * fuzz) - (fuzz / 2));
    }
    return base + fuzz;
};

TChannelConnectionBase.prototype.startTimeoutTimer = function startTimeoutTimer() {
    var self = this;
    self.timer = self.timers.setTimeout(function onChannelTimeout() {
        // TODO: worth it to clear the fired self.timer objcet?
        self.onTimeoutCheck();
    }, self.getTimeoutDelay());
};

TChannelConnectionBase.prototype.clearTimeoutTimer = function clearTimeoutTimer() {
    var self = this;
    if (self.timer) {
        self.timers.clearTimeout(self.timer);
        self.timer = null;
    }
};

// If the connection has some success and some timeouts, we should probably leave it up,
// but if everything is timing out, then we should kill the connection.
TChannelConnectionBase.prototype.onTimeoutCheck = function onTimeoutCheck() {
    var self = this;
    if (self.closing) {
        return;
    }
    if (self.lastTimeoutTime) {
        self.emit('timedOut');
    } else {
        self.checkTimeout(self.requests.out, 'out');
        self.checkTimeout(self.requests.in, 'in');
        self.startTimeoutTimer();
    }
};

TChannelConnectionBase.prototype.checkTimeout = function checkTimeout(ops, direction) {
    var self = this;
    var opKeys = Object.keys(ops);
    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var req = ops[id];
        if (req === undefined) {
            self.logger.warn('unexpected undefined request', {
                direction: direction,
                id: id
            });
        } else if (req.timedOut) {
            self.logger.warn('lingering timed-out request', {
                direction: direction,
                id: id
            });
            delete ops[id];
            self.pending[direction]--;
        } else if (req.checkTimeout()) {
            if (direction === 'out') {
                self.lastTimeoutTime = self.timers.now();
            // } else {
            //     req.res.sendError // XXX may need to build
            }
            delete ops[id];
            self.pending[direction]--;
        }
    }
};

// this connection is completely broken, and is going away
// In addition to erroring out all of the pending work, we reset the state in case anybody
// stumbles across this object in a core dump.
TChannelConnectionBase.prototype.resetAll = function resetAll(err) {
    var self = this;

    self.clearTimeoutTimer();

    if (self.closing) return;
    self.closing = true;

    var inOpKeys = Object.keys(self.requests.in);
    var outOpKeys = Object.keys(self.requests.out);

    if (!err) {
        err = new Error('unknown connection reset'); // TODO typed error
    }

    var isError = err.type.indexOf('tchannel.socket') !== 0;
    self.logger[isError ? 'warn' : 'info']('resetting connection', {
        error: err,
        remoteName: self.remoteName,
        localName: self.channel.hostPort,
        numInOps: inOpKeys.length,
        numOutOps: outOpKeys.length,
        inPending: self.pending.in,
        outPending: self.pending.out
    });

    if (isError) {
        self.emit('error', err);
    }

    // requests that we've received we can delete, but these reqs may have started their
    //   own outgoing work, which is hard to cancel. By setting this.closing, we make sure
    //   that once they do finish that their callback will swallow the response.
    inOpKeys.forEach(function eachInOp(id) {
        // TODO: support canceling pending handlers
        delete self.requests.in[id];
        // TODO report or handle or log errors or something
    });

    // for all outgoing requests, forward the triggering error to the user callback
    outOpKeys.forEach(function eachOutOp(id) {
        var req = self.requests.out[id];
        delete self.requests.out[id];
        // TODO: shared mutable object... use Object.create(err)?
        req.emit('error', err);
    });

    self.pending.in = 0;
    self.pending.out = 0;
};

TChannelConnectionBase.prototype.popOutReq = function popOutReq(id) {
    var self = this;
    var req = self.requests.out[id];
    if (!req) {
        // TODO else case. We should warn about an incoming response for an
        // operation we did not send out.  This could be because of a timeout
        // or could be because of a confused / corrupted server.
        return;
    }
    delete self.requests.out[id];
    self.pending.out--;
    return req;
};

// create a request
TChannelConnectionBase.prototype.request = function connBaseRequest(options) {
    var self = this;
    if (!options) options = {};
    options.remoteAddr = self.remoteAddr;

    // TODO: use this to protect against >4Mi outstanding messages edge case
    // (e.g. zombie operation bug, incredible throughput, or simply very long
    // timeout
    // assert(!self.requests.out[id], 'duplicate frame id in flight');
    // TODO: provide some sort of channel default for "service"
    // TODO: generate tracing if empty?
    // TODO: refactor callers
    options.checksumType = options.checksum;

    // TODO: better default, support for dynamic
    options.ttl = options.timeout || DEFAULT_OUTGOING_REQ_TIMEOUT;
    options.tracer = self.tracer;
    var req = self.buildOutRequest(options);

    return self._addOutReq(req);
};

TChannelConnectionBase.prototype._addOutReq = function _addOutReq(req) {
    var self = this;
    self.requests.out[req.id] = req;
    self.pending.out++;
    return req;
};

TChannelConnectionBase.prototype.handleCallRequest = function handleCallRequest(req) {
    var self = this;
    req.remoteAddr = self.remoteName;
    self.pending.in++;
    self.requests.in[req.id] = req;
    req.on('error', onReqError);
    process.nextTick(runHandler);

    function onReqError(err) {
        self.onReqError(req, err);
    }

    function runHandler() {
        self.runHandler(req);
    }
};

TChannelConnectionBase.prototype.onReqError = function onReqError(req, err) {
    var self = this;
    if (!req.res) self.buildResponse(req);
    if (err.type === 'tchannel.timeout') {
        req.res.sendError('Timeout', err.message);
    } else {
        var errName = err.name || err.constructor.name;
        req.res.sendError('UnexpectedError', errName + ': ' + err.message);
    }
};

TChannelConnectionBase.prototype.runHandler = function runHandler(req) {
    var self = this;
    self.channel.handler.handleRequest(req, buildResponse);
    function buildResponse(options) {
        return self.buildResponse(req, options);
    }
};

TChannelConnectionBase.prototype.buildResponse = function buildResponse(req, options) {
    var self = this;
    var done = false;
    if (req.res && req.res.state !== States.Initial) {
        self.emit('error', errors.ResponseAlreadyStarted({
            state: req.res.state
        }));
    }
    req.res = self.buildOutResponse(req, options);
    req.res.on('finish', opDone);
    req.res.on('span', handleSpanFromRes);
    return req.res;

    function handleSpanFromRes(span) {
        self.emit('span', span);
    }

    function opDone() {
        if (done) return;
        done = true;
        self.onReqDone(req);
    }
};

TChannelConnectionBase.prototype.onReqDone = function onReqDone(req) {
    var self = this;
    if (self.requests.in[req.id] !== req) {
        self.logger.warn('mismatched onReqDone callback', {
            hostPort: self.channel.hostPort,
            id: req.id
        });
        return;
    }
    delete self.requests.in[req.id];
    self.pending.in--;
};

module.exports = TChannelConnectionBase;
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;

var errors = require('./errors');
var OutgoingResponse = require('./outgoing_response');

var DEFAULT_OUTGOING_REQ_TIMEOUT = 2000;

var TChannelServerOp = require('./server_op');
var TChannelClientOp = require('./client_op');

function TChannelConnectionBase(channel, direction, remoteAddr) {
    var self = this;
    EventEmitter.call(self);
    self.channel = channel;
    self.options = self.channel.options;
    self.logger = channel.logger;
    self.random = channel.random;
    self.timers = channel.timers;
    self.direction = direction;
    self.remoteAddr = remoteAddr;
    self.timer = null;
    self.remoteName = null; // filled in by identify message

    // TODO: factor out an operation collection abstraction
    self.inOps = Object.create(null);
    self.outOps = Object.create(null);
    self.pending = {
        in: 0,
        out: 0
    };

    self.lastTimeoutTime = 0;
    self.closing = false;

    self.startTimeoutTimer();
    self.tracer = self.channel.tracer;
}
inherits(TChannelConnectionBase, EventEmitter);

TChannelConnectionBase.prototype.close = function close(callback) {
    var self = this;
    self.resetAll(errors.SocketClosedError({reason: 'local close'}));
    callback();
};

// timeout check runs every timeoutCheckInterval +/- some random fuzz. Range is from
//   base - fuzz/2 to base + fuzz/2
TChannelConnectionBase.prototype.getTimeoutDelay = function getTimeoutDelay() {
    var self = this;
    var base = self.options.timeoutCheckInterval;
    var fuzz = self.options.timeoutFuzz;
    return base + Math.round(Math.floor(self.random() * fuzz) - (fuzz / 2));
};

TChannelConnectionBase.prototype.startTimeoutTimer = function startTimeoutTimer() {
    var self = this;
    self.timer = self.timers.setTimeout(function onChannelTimeout() {
        // TODO: worth it to clear the fired self.timer objcet?
        self.onTimeoutCheck();
    }, self.getTimeoutDelay());
};

TChannelConnectionBase.prototype.clearTimeoutTimer = function clearTimeoutTimer() {
    var self = this;
    if (self.timer) {
        self.timers.clearTimeout(self.timer);
        self.timer = null;
    }
};

// If the connection has some success and some timeouts, we should probably leave it up,
// but if everything is timing out, then we should kill the connection.
TChannelConnectionBase.prototype.onTimeoutCheck = function onTimeoutCheck() {
    var self = this;
    if (self.closing) {
        return;
    }
    if (self.lastTimeoutTime) {
        self.emit('timedOut');
    } else {
        self.checkOutOpsForTimeout(self.outOps);
        self.checkInOpsForTimeout(self.inOps);
        self.startTimeoutTimer();
    }
};

TChannelConnectionBase.prototype.checkInOpsForTimeout = function checkInOpsForTimeout(ops) {
    var self = this;
    var opKeys = Object.keys(ops);
    var now = self.timers.now();

    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var op = ops[id];

        if (op === undefined) {
            continue;
        }

        var duration = now - op.start;
        if (duration > op.req.ttl) {
            delete ops[id];
            self.pending.in--;
        }
    }
};

TChannelConnectionBase.prototype.checkOutOpsForTimeout = function checkOutOpsForTimeout(ops) {
    var self = this;
    var opKeys = Object.keys(ops);
    var now = self.timers.now();
    for (var i = 0; i < opKeys.length ; i++) {
        var id = opKeys[i];
        var op = ops[id];
        if (op.timedOut) {
            delete ops[id];
            self.pending.out--;
            self.logger.warn('lingering timed-out outgoing operation');
            continue;
        }
        if (op === undefined) {
            // TODO: why not null and empty string too? I mean I guess false
            // and 0 might be a thing, but really why not just !op?
            self.logger.warn('unexpected undefined operation', {
                id: id,
                op: op
            });
            continue;
        }
        var duration = now - op.start;
        if (duration > op.req.ttl) {
            delete ops[id];
            self.pending.out--;
            self.onReqTimeout(op);
        }
    }
};

TChannelConnectionBase.prototype.onReqTimeout = function onReqTimeout(op) {
    var self = this;
    op.timedOut = true;
    op.req.emit('error', errors.TimeoutError({
        id: op.req.id,
        start: op.start,
        elapsed: self.timers.now() - op.start,
        timeout: op.req.ttl
    }));

    // TODO: why don't we pop the op?
    self.lastTimeoutTime = self.timers.now();
};

// this connection is completely broken, and is going away
// In addition to erroring out all of the pending work, we reset the state in case anybody
// stumbles across this object in a core dump.
TChannelConnectionBase.prototype.resetAll = function resetAll(err) {
    var self = this;

    self.clearTimeoutTimer();

    if (self.closing) return;
    self.closing = true;

    var inOpKeys = Object.keys(self.inOps);
    var outOpKeys = Object.keys(self.outOps);

    if (!err) {
        err = new Error('unknown connection reset'); // TODO typed error
    }

    var isError = err.type !== 'tchannel.socket-closed';
    self.logger[isError ? 'warn' : 'info']('resetting connection', {
        error: err,
        remoteName: self.remoteName,
        localName: self.channel.hostPort,
        numInOps: inOpKeys.length,
        numOutOps: outOpKeys.length,
        inPending: self.pending.in,
        outPending: self.pending.out
    });

    if (isError) {
        self.emit('error', err);
    }

    // requests that we've received we can delete, but these reqs may have started their
    //   own outgoing work, which is hard to cancel. By setting this.closing, we make sure
    //   that once they do finish that their callback will swallow the response.
    inOpKeys.forEach(function eachInOp(id) {
        // TODO: we could support an op.cancel opt-in callback
        delete self.inOps[id];
        // TODO report or handle or log errors or something
    });

    // for all outgoing requests, forward the triggering error to the user callback
    outOpKeys.forEach(function eachOutOp(id) {
        var op = self.outOps[id];
        delete self.outOps[id];
        // TODO: shared mutable object... use Object.create(err)?
        op.req.emit('error', err);
    });

    self.pending.in = 0;
    self.pending.out = 0;
};

TChannelConnectionBase.prototype.popOutOp = function popOutOp(id) {
    var self = this;
    var op = self.outOps[id];
    if (!op) {
        // TODO else case. We should warn about an incoming response for an
        // operation we did not send out.  This could be because of a timeout
        // or could be because of a confused / corrupted server.
        return;
    }
    delete self.outOps[id];
    self.pending.out--;
    return op;
};

// create a request
TChannelConnectionBase.prototype.request = function connBaseRequest(options) {
    var self = this;
    if (!options) options = {};

    // TODO: use this to protect against >4Mi outstanding messages edge case
    // (e.g. zombie operation bug, incredible throughput, or simply very long
    // timeout
    // if (self.outOps[id]) {
    //  throw new Error('duplicate frame id in flight'); // TODO typed error
    // }
    // TODO: provide some sort of channel default for "service"
    // TODO: generate tracing if empty?
    // TODO: refactor callers
    options.checksumType = options.checksum;

    // TODO: better default, support for dynamic
    options.ttl = options.timeout || DEFAULT_OUTGOING_REQ_TIMEOUT;
    options.tracer = self.tracer;
    var req = self.buildOutgoingRequest(options);
    var id = req.id;
    self.outOps[id] = new TChannelClientOp(req, self.timers.now());
    self.pending.out++;
    return req;
};

TChannelConnectionBase.prototype.handleCallRequest = function handleCallRequest(req) {
    var self = this;
    req.remoteAddr = self.remoteName;
    var id = req.id;
    self.pending.in++;
    var op = self.inOps[id] = new TChannelServerOp(self, self.timers.now(), req);
    var done = false;
    req.on('error', onReqError);
    process.nextTick(runHandler);

    if (req.span) {
        req.span.endpoint.serviceName = self.channel.serviceName;
    }

    function onReqError(err) {
        if (!op.res) buildResponse();
        if (err.type === 'tchannel.timeout-error') {
            op.res.sendError('Timeout', err.message);
        } else {
            var errName = err.name || err.constructor.name;
            op.res.sendError('UnexpectedError', errName + ': ' + err.message);
        }
    }

    function runHandler() {
        self.channel.handler.handleRequest(req, buildResponse);
    }

    function handleSpanFromRes(span) {
        self.emit('span', span);
    }

    function buildResponse(options) {
        if (op.res && op.res.state !== OutgoingResponse.States.Initial) {
            throw errors.ResponseAlreadyStarted({
                state: op.res.state
            });
        }
        op.res = self.buildOutgoingResponse(req, options);
        op.res.on('finish', opDone);
        op.res.on('errored', opDone);
        op.res.on('span', handleSpanFromRes);
        return op.res;
    }

    function opDone() {
        if (done) return;
        done = true;
        if (self.inOps[id] !== op) {
            self.logger.warn('mismatched opDone callback', {
                hostPort: self.channel.hostPort,
                opId: id
            });
            return;
        }
        delete self.inOps[id];
        self.pending.in--;
    }
};

module.exports = TChannelConnectionBase;
<commit_msg>TChannelConnectionBase: trivial cleanup<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;

var errors = require('./errors');
var OutgoingResponse = require('./outgoing_response');

var DEFAULT_OUTGOING_REQ_TIMEOUT = 2000;

var TChannelServerOp = require('./server_op');
var TChannelClientOp = require('./client_op');

function TChannelConnectionBase(channel, direction, remoteAddr) {
    var self = this;
    EventEmitter.call(self);
    self.channel = channel;
    self.options = self.channel.options;
    self.logger = channel.logger;
    self.random = channel.random;
    self.timers = channel.timers;
    self.direction = direction;
    self.remoteAddr = remoteAddr;
    self.timer = null;
    self.remoteName = null; // filled in by identify message

    // TODO: factor out an operation collection abstraction
    self.inOps = Object.create(null);
    self.outOps = Object.create(null);
    self.pending = {
        in: 0,
        out: 0
    };

    self.lastTimeoutTime = 0;
    self.closing = false;

    self.startTimeoutTimer();
    self.tracer = self.channel.tracer;
}
inherits(TChannelConnectionBase, EventEmitter);

TChannelConnectionBase.prototype.close = function close(callback) {
    var self = this;
    self.resetAll(errors.SocketClosedError({reason: 'local close'}));
    callback();
};

// timeout check runs every timeoutCheckInterval +/- some random fuzz. Range is from
//   base - fuzz/2 to base + fuzz/2
TChannelConnectionBase.prototype.getTimeoutDelay = function getTimeoutDelay() {
    var self = this;
    var base = self.options.timeoutCheckInterval;
    var fuzz = self.options.timeoutFuzz;
    return base + Math.round(Math.floor(self.random() * fuzz) - (fuzz / 2));
};

TChannelConnectionBase.prototype.startTimeoutTimer = function startTimeoutTimer() {
    var self = this;
    self.timer = self.timers.setTimeout(function onChannelTimeout() {
        // TODO: worth it to clear the fired self.timer objcet?
        self.onTimeoutCheck();
    }, self.getTimeoutDelay());
};

TChannelConnectionBase.prototype.clearTimeoutTimer = function clearTimeoutTimer() {
    var self = this;
    if (self.timer) {
        self.timers.clearTimeout(self.timer);
        self.timer = null;
    }
};

// If the connection has some success and some timeouts, we should probably leave it up,
// but if everything is timing out, then we should kill the connection.
TChannelConnectionBase.prototype.onTimeoutCheck = function onTimeoutCheck() {
    var self = this;
    if (self.closing) {
        return;
    }
    if (self.lastTimeoutTime) {
        self.emit('timedOut');
    } else {
        self.checkOutOpsForTimeout(self.outOps);
        self.checkInOpsForTimeout(self.inOps);
        self.startTimeoutTimer();
    }
};

TChannelConnectionBase.prototype.checkInOpsForTimeout = function checkInOpsForTimeout(ops) {
    var self = this;
    var opKeys = Object.keys(ops);
    var now = self.timers.now();

    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var op = ops[id];

        if (op === undefined) {
            continue;
        }

        var duration = now - op.start;
        if (duration > op.req.ttl) {
            delete ops[id];
            self.pending.in--;
        }
    }
};

TChannelConnectionBase.prototype.checkOutOpsForTimeout = function checkOutOpsForTimeout(ops) {
    var self = this;
    var opKeys = Object.keys(ops);
    var now = self.timers.now();
    for (var i = 0; i < opKeys.length ; i++) {
        var id = opKeys[i];
        var op = ops[id];
        if (op.timedOut) {
            delete ops[id];
            self.pending.out--;
            self.logger.warn('lingering timed-out outgoing operation');
            continue;
        }
        if (op === undefined) {
            // TODO: why not null and empty string too? I mean I guess false
            // and 0 might be a thing, but really why not just !op?
            self.logger.warn('unexpected undefined operation', {
                id: id,
                op: op
            });
            continue;
        }
        var duration = now - op.start;
        if (duration > op.req.ttl) {
            delete ops[id];
            self.pending.out--;
            self.onReqTimeout(op);
        }
    }
};

TChannelConnectionBase.prototype.onReqTimeout = function onReqTimeout(op) {
    var self = this;
    op.timedOut = true;
    op.req.emit('error', errors.TimeoutError({
        id: op.req.id,
        start: op.start,
        elapsed: self.timers.now() - op.start,
        timeout: op.req.ttl
    }));

    // TODO: why don't we pop the op?
    self.lastTimeoutTime = self.timers.now();
};

// this connection is completely broken, and is going away
// In addition to erroring out all of the pending work, we reset the state in case anybody
// stumbles across this object in a core dump.
TChannelConnectionBase.prototype.resetAll = function resetAll(err) {
    var self = this;

    self.clearTimeoutTimer();

    if (self.closing) return;
    self.closing = true;

    var inOpKeys = Object.keys(self.inOps);
    var outOpKeys = Object.keys(self.outOps);

    if (!err) {
        err = new Error('unknown connection reset'); // TODO typed error
    }

    var isError = err.type !== 'tchannel.socket-closed';
    self.logger[isError ? 'warn' : 'info']('resetting connection', {
        error: err,
        remoteName: self.remoteName,
        localName: self.channel.hostPort,
        numInOps: inOpKeys.length,
        numOutOps: outOpKeys.length,
        inPending: self.pending.in,
        outPending: self.pending.out
    });

    if (isError) {
        self.emit('error', err);
    }

    // requests that we've received we can delete, but these reqs may have started their
    //   own outgoing work, which is hard to cancel. By setting this.closing, we make sure
    //   that once they do finish that their callback will swallow the response.
    inOpKeys.forEach(function eachInOp(id) {
        // TODO: we could support an op.cancel opt-in callback
        delete self.inOps[id];
        // TODO report or handle or log errors or something
    });

    // for all outgoing requests, forward the triggering error to the user callback
    outOpKeys.forEach(function eachOutOp(id) {
        var op = self.outOps[id];
        delete self.outOps[id];
        // TODO: shared mutable object... use Object.create(err)?
        op.req.emit('error', err);
    });

    self.pending.in = 0;
    self.pending.out = 0;
};

TChannelConnectionBase.prototype.popOutOp = function popOutOp(id) {
    var self = this;
    var op = self.outOps[id];
    if (!op) {
        // TODO else case. We should warn about an incoming response for an
        // operation we did not send out.  This could be because of a timeout
        // or could be because of a confused / corrupted server.
        return;
    }
    delete self.outOps[id];
    self.pending.out--;
    return op;
};

// create a request
TChannelConnectionBase.prototype.request = function connBaseRequest(options) {
    var self = this;
    if (!options) options = {};

    // TODO: use this to protect against >4Mi outstanding messages edge case
    // (e.g. zombie operation bug, incredible throughput, or simply very long
    // timeout
    // if (self.outOps[id]) {
    //  throw new Error('duplicate frame id in flight'); // TODO typed error
    // }
    // TODO: provide some sort of channel default for "service"
    // TODO: generate tracing if empty?
    // TODO: refactor callers
    options.checksumType = options.checksum;

    // TODO: better default, support for dynamic
    options.ttl = options.timeout || DEFAULT_OUTGOING_REQ_TIMEOUT;
    options.tracer = self.tracer;
    var req = self.buildOutgoingRequest(options);
    self.outOps[req.id] = new TChannelClientOp(req, self.timers.now());
    self.pending.out++;
    return req;
};

TChannelConnectionBase.prototype.handleCallRequest = function handleCallRequest(req) {
    var self = this;
    req.remoteAddr = self.remoteName;
    self.pending.in++;
    var op = self.inOps[req.id] = new TChannelServerOp(self, self.timers.now(), req);
    var done = false;
    req.on('error', onReqError);
    process.nextTick(runHandler);

    if (req.span) {
        req.span.endpoint.serviceName = self.channel.serviceName;
    }

    function onReqError(err) {
        if (!op.res) buildResponse();
        if (err.type === 'tchannel.timeout-error') {
            op.res.sendError('Timeout', err.message);
        } else {
            var errName = err.name || err.constructor.name;
            op.res.sendError('UnexpectedError', errName + ': ' + err.message);
        }
    }

    function runHandler() {
        self.channel.handler.handleRequest(req, buildResponse);
    }

    function handleSpanFromRes(span) {
        self.emit('span', span);
    }

    function buildResponse(options) {
        if (op.res && op.res.state !== OutgoingResponse.States.Initial) {
            throw errors.ResponseAlreadyStarted({
                state: op.res.state
            });
        }
        op.res = self.buildOutgoingResponse(req, options);
        op.res.on('finish', opDone);
        op.res.on('errored', opDone);
        op.res.on('span', handleSpanFromRes);
        return op.res;
    }

    function opDone() {
        if (done) return;
        done = true;
        if (self.inOps[req.id] !== op) {
            self.logger.warn('mismatched opDone callback', {
                hostPort: self.channel.hostPort,
                opId: req.id
            });
            return;
        }
        delete self.inOps[req.id];
        self.pending.in--;
    }
};

module.exports = TChannelConnectionBase;
<|endoftext|>


<commit_before>steal('../libs/underscore.js', function (_) {
	var exports = {};
	var getParents = function (children) {
		var parents = [];
		var gatherParents = function (children) {
			var current = _.find(children, function (child) {
				return child.active;
			});

			if (!current) {
				return;
			}

			parents.push(current);
			if (current.children && current.type) {
				gatherParents(current.children);
			}
		}

		gatherParents(children);
		return parents;
	}
	var constructorParentPosition = function(children) {
		var parents = getParents(children);
		var active = _.last(parents);

		if ((active && (!active.children || !active.children.length)) && parents.length > 2) {
			// Active has no children so lets check if it is part of a construct
			return parents.length - 3;
		}

		return parents.length;
	}

	exports.docsFilename = function (name) {
		return name.replace(/ /g, "_")
			.replace(/./g, ".")
			.replace(/>/g, "_gt_")
			.replace(/\*/g, "_star_")
			.replace(/\//g, "|") + '.html';
	}

	exports.name = function (file) {
		return file.substring(0, file.lastIndexOf('.'));
	}

	exports.handlebarsHelpers = function (helpers, Handlebars) {
		if (helpers && Handlebars) {
			_.each(helpers, function (helper, name) {
				Handlebars.registerHelper(name, helper);
			});
		}
		return Handlebars;
	}

	exports.handlebarsPartials = function (folder, Handlebars) {
		new steal.URI(folder).contents(function (name) {
			var template = readFile(folder + '/' + name);
			Handlebars.registerPartial(name, template);
		});

		return Handlebars;
	}

	exports.activateItems = function (root, name) {
		var traverse = function (children) {
			var anyActive = false;
			_.each(children, function (child) {
				var active = false;
				if (child.children) {
					active = traverse(child.children);
				}
				child.active = active || child.name == name;
				if (child.active) {
					anyActive = true;
				}
			});

			return anyActive;
		}

		traverse(root.children);

		return root;
	}

	exports.menuTree = function (data, root) {
		var copies = _.map(data, function (value) {
			return _.extend({
				active: false
			}, value);
		});

		_.each(copies, function (current, index, arr) {
			var parent = _.find(arr, function (value) {
				return value.name === current.parent;
			});

			if (parent) {
				parent.children = parent.children || [];
				parent.children.push(current);
				parent.children = _.sortBy(parent.children, function (child) {
					return child.order || child.name;
				});
			}
		});

		if (root) {
			return _.find(copies, function (current) {
				return current.name == root;
			});
		}

		return copies;
	}

	exports.replaceLinks = function (text, data) {
		if (!text) return "";
		var replacer = function (match, content) {
			var parts = content.match(/^(\S+)\s(.*)/);
			var link = parts ? parts[1].replace('::', '.prototype.') : content;
			var description = parts && parts[2] ? parts[2] : link;

			if (data[link]) {
				return '' + description + '';
			}

			return match;
		};
		return text.replace(/[\[](.*?)\]/g, replacer);
	}
	exports.linkTo = function(name, title){
		if (!name) return (title || "");
		name = name.replace('::', '.prototype.')
		if (data[name]) {
			return '' + (name || title) + '';
		} else {
			return title || name || ""
		}
	}
	var data;
	exports.data = function(d){
		if(d){
			data = d;
		} else {
			return data;
		}
	}

	exports.helpers = {
		activePage: function (current, expected) {
			return current == this.page ? 'active' : '';
		},

		getTitle: function () {
			var node = this;
			if (node.title) {
				return node.title
			}
			// name: "cookbook/recipe/list.static.defaults"
			// parent: "cookbook/recipe/list.static"
			// src: "cookbook/recipe/list/list.js"
			var title = node.name.replace(node.parent + ".", "");
			return title;
		},

		url: function (url) {
			return this.root + '/' + url;
		},

		makeHref: function (name) {
			return exports.docsFilename(name);
		},
		hasActiveChild: function (options) {
			var hasActiveChild = _.some(this.children, function (child) {
				return child.active;
			});

			return hasActiveChild ? options.fn(this) : options.inverse(this);
		},
		isConstructor: function (options) {
			if (this.type === 'constructor') {
				return options.fn(this);
			}
			return options.inverse(this);
		},
		makeParamsString: function(params){
			if(!params || !params.length){
				return ""
			}
			return params.map(function(param){
				return exports.linkTo(param.types && param.types[0] && param.types[0].type, param.name)
			}).join(", ")
		},
		makeTypes: function(types){
			if (types.length) {
				// turns [{type: 'Object'}, {type: 'String'}] into '{Object | String}'
				return types.map(function (t) {
					if(t.type === "function"){
						return "function("+exports.helpers.makeParamsString(t.params)+")";
					}
					return t.type;
				}).join(' | ');
			} else {
				return '';
			}
		},
		makeTypesString: function (types) {
			if (types.length) {
				// turns [{type: 'Object'}, {type: 'String'}] into '{Object | String}'
				return "{"+exports.helpers.makeTypes(types)+"}";
			} else {
				return '';
			}
		},
		downloadUrl: function (download, isPlugin) {
			return '';
			if (isPlugin) {
				download = 'plugins=' + download;
			}
			// TOOO make builder URL configurable
			return 'http://bitbuilder.herokuapp.com/can.custom.js?' + download;
		},
		sourceUrl: function (src, type, line) {
			return '';
			var pkg = {},
				relative = path.relative(grunt.config('can.path'), src),
				hash = type !== 'page' && type !== 'constructor' && line ? '#L' + line : '';
			return pkg.repository.github + '/tree/v' + pkg.version + '/' + relative + hash;
		},
		testUrl: function (test) {
			return '';
			// TODO we know we're in the docs/ folder for test links but there might
			// be a more flexible way for doing this
			return '../' + test;
		},
		activeParents: function (options) {
			var parents = getParents(this.children);
			var active = _.last(parents);
			var hasConstructorParent = (active && (!active.children || !active.children.length)) && parents.length > 2
				&& parents[parents.length - 3].type === 'constructor';

			if (hasConstructorParent) {
				// Active has no children so lets check if it is part of a construct
				parents = parents.slice(0, parents.length - 3);
			} else {
				parents.pop();
			}

			if( (!active.children || !active.children.length) && !hasConstructorParent) {
				parents.pop();
			}

			parents = _.filter(parents, function(parent) {
				return parent.type !== 'plugins' && parent.type !== 'Pages';
			});

			// Add root level at the beginning
			parents.unshift(this);

			return options.fn(parents);
		},
		activeMenu: function (options) {
			var parents = getParents(this.children);
			var active = _.last(parents);

			if ((active && (!active.children || !active.children.length)) && parents.length > 2) {
				var newActive = parents[parents.length - 3];
				if(newActive.type === 'constructor') {
					// Active has no children so lets check if it is part of a construct
					active = newActive;
				}
			}

			if(!active.children || !active.children.length) {
				active = parents[parents.length - 2];
			}

			return options.fn(active);
		},
		orderedChildren: function(children, options){
			children = children.slice(0).sort(function(child1, child2){
				if(typeof child1.order == "number"){
					if(typeof child2.order == "number"){
						return child1.order - child2.order;
					} else {
						return -1;
					}
				} else {
					if(typeof child2.order == "number"){
						return 1;
					} else {
						return 0;
					}
				}
			});
			var res = "";
			children.forEach(function(child){
				res += options.fn(child)
			})
			return res;
		}
	};

	return exports;
});
<commit_msg>External links working. Closes #23<commit_after>steal('../libs/underscore.js', function (_) {
	var exports = {};
	var getParents = function (children) {
		var parents = [];
		var gatherParents = function (children) {
			var current = _.find(children, function (child) {
				return child.active;
			});

			if (!current) {
				return;
			}

			parents.push(current);
			if (current.children && current.type) {
				gatherParents(current.children);
			}
		}

		gatherParents(children);
		return parents;
	}
	var constructorParentPosition = function(children) {
		var parents = getParents(children);
		var active = _.last(parents);

		if ((active && (!active.children || !active.children.length)) && parents.length > 2) {
			// Active has no children so lets check if it is part of a construct
			return parents.length - 3;
		}

		return parents.length;
	}

	exports.docsFilename = function (name) {
		return name.replace(/ /g, "_")
			.replace(/./g, ".")
			.replace(/>/g, "_gt_")
			.replace(/\*/g, "_star_")
			.replace(/\//g, "|") + '.html';
	}

	exports.name = function (file) {
		return file.substring(0, file.lastIndexOf('.'));
	}

	exports.handlebarsHelpers = function (helpers, Handlebars) {
		if (helpers && Handlebars) {
			_.each(helpers, function (helper, name) {
				Handlebars.registerHelper(name, helper);
			});
		}
		return Handlebars;
	}

	exports.handlebarsPartials = function (folder, Handlebars) {
		new steal.URI(folder).contents(function (name) {
			var template = readFile(folder + '/' + name);
			Handlebars.registerPartial(name, template);
		});

		return Handlebars;
	}

	exports.activateItems = function (root, name) {
		var traverse = function (children) {
			var anyActive = false;
			_.each(children, function (child) {
				var active = false;
				if (child.children) {
					active = traverse(child.children);
				}
				child.active = active || child.name == name;
				if (child.active) {
					anyActive = true;
				}
			});

			return anyActive;
		}

		traverse(root.children);

		return root;
	}

	exports.menuTree = function (data, root) {
		var copies = _.map(data, function (value) {
			return _.extend({
				active: false
			}, value);
		});

		_.each(copies, function (current, index, arr) {
			var parent = _.find(arr, function (value) {
				return value.name === current.parent;
			});

			if (parent) {
				parent.children = parent.children || [];
				parent.children.push(current);
				parent.children = _.sortBy(parent.children, function (child) {
					return child.order || child.name;
				});
			}
		});

		if (root) {
			return _.find(copies, function (current) {
				return current.name == root;
			});
		}

		return copies;
	}

	exports.replaceLinks = function (text, data) {
		if (!text) return "";
		var replacer = function (match, content) {
			var parts = content.match(/^(\S+)\s(.*)/);
			var link = parts ? parts[1].replace('::', '.prototype.') : content;
			var description = parts && parts[2] ? parts[2] : link;

			if(/^http/.test(link)) {
				return '' + description + '';
			}

			if (data[link]) {
				return '' + description + '';
			}

			return match;
		};
		return text.replace(/[\[](.*?)\]/g, replacer);
	}
	exports.linkTo = function(name, title){
		if (!name) return (title || "");
		name = name.replace('::', '.prototype.');
		if (data[name]) {
			return '' + (name || title) + '';
		} else {
			return title || name || ""
		}
	}
	var data;
	exports.data = function(d){
		if(d){
			data = d;
		} else {
			return data;
		}
	}

	exports.helpers = {
		activePage: function (current, expected) {
			return current == this.page ? 'active' : '';
		},

		getTitle: function () {
			var node = this;
			if (node.title) {
				return node.title
			}
			// name: "cookbook/recipe/list.static.defaults"
			// parent: "cookbook/recipe/list.static"
			// src: "cookbook/recipe/list/list.js"
			var title = node.name.replace(node.parent + ".", "");
			return title;
		},

		url: function (url) {
			return this.root + '/' + url;
		},

		makeHref: function (name) {
			return exports.docsFilename(name);
		},
		hasActiveChild: function (options) {
			var hasActiveChild = _.some(this.children, function (child) {
				return child.active;
			});

			return hasActiveChild ? options.fn(this) : options.inverse(this);
		},
		isConstructor: function (options) {
			if (this.type === 'constructor') {
				return options.fn(this);
			}
			return options.inverse(this);
		},
		makeParamsString: function(params){
			if(!params || !params.length){
				return ""
			}
			return params.map(function(param){
				return exports.linkTo(param.types && param.types[0] && param.types[0].type, param.name)
			}).join(", ")
		},
		makeTypes: function(types){
			if (types.length) {
				// turns [{type: 'Object'}, {type: 'String'}] into '{Object | String}'
				return types.map(function (t) {
					if(t.type === "function"){
						return "function("+exports.helpers.makeParamsString(t.params)+")";
					}
					return t.type;
				}).join(' | ');
			} else {
				return '';
			}
		},
		makeTypesString: function (types) {
			if (types.length) {
				// turns [{type: 'Object'}, {type: 'String'}] into '{Object | String}'
				return "{"+exports.helpers.makeTypes(types)+"}";
			} else {
				return '';
			}
		},
		downloadUrl: function (download, isPlugin) {
			return '';
			if (isPlugin) {
				download = 'plugins=' + download;
			}
			// TOOO make builder URL configurable
			return 'http://bitbuilder.herokuapp.com/can.custom.js?' + download;
		},
		sourceUrl: function (src, type, line) {
			return '';
			var pkg = {},
				relative = path.relative(grunt.config('can.path'), src),
				hash = type !== 'page' && type !== 'constructor' && line ? '#L' + line : '';
			return pkg.repository.github + '/tree/v' + pkg.version + '/' + relative + hash;
		},
		testUrl: function (test) {
			return '';
			// TODO we know we're in the docs/ folder for test links but there might
			// be a more flexible way for doing this
			return '../' + test;
		},
		activeParents: function (options) {
			var parents = getParents(this.children);
			var active = _.last(parents);
			var hasConstructorParent = (active && (!active.children || !active.children.length)) && parents.length > 2
				&& parents[parents.length - 3].type === 'constructor';

			if (hasConstructorParent) {
				// Active has no children so lets check if it is part of a construct
				parents = parents.slice(0, parents.length - 3);
			} else {
				parents.pop();
			}

			if( (!active.children || !active.children.length) && !hasConstructorParent) {
				parents.pop();
			}

			parents = _.filter(parents, function(parent) {
				return parent.type !== 'plugins' && parent.type !== 'Pages';
			});

			// Add root level at the beginning
			parents.unshift(this);

			return options.fn(parents);
		},
		activeMenu: function (options) {
			var parents = getParents(this.children);
			var active = _.last(parents);

			if ((active && (!active.children || !active.children.length)) && parents.length > 2) {
				var newActive = parents[parents.length - 3];
				if(newActive.type === 'constructor') {
					// Active has no children so lets check if it is part of a construct
					active = newActive;
				}
			}

			if(!active.children || !active.children.length) {
				active = parents[parents.length - 2];
			}

			return options.fn(active);
		},
		orderedChildren: function(children, options){
			children = children.slice(0).sort(function(child1, child2){
				if(typeof child1.order == "number"){
					if(typeof child2.order == "number"){
						return child1.order - child2.order;
					} else {
						return -1;
					}
				} else {
					if(typeof child2.order == "number"){
						return 1;
					} else {
						return 0;
					}
				}
			});
			var res = "";
			children.forEach(function(child){
				res += options.fn(child)
			})
			return res;
		}
	};

	return exports;
});
<|endoftext|>


<commit_before><commit_msg>add mock<commit_after>var express = require('express');
var app = express();

// 设置跨域访问，方便开发
app.all('*', function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  next();
});

// 具体接口设置
app.get('/api/test', function(req, res) {
  res.send({ code: 200, data: 'your data' });
});

var server = app.listen(3000, function() {
  var host = server.address().address;
  var port = server.address().port;
  console.log('Mock server listening at http://%s:%s', host, port);
});<|endoftext|>


<commit_before>var exports,console,process$,empty;//IGNORE
function Comparison(x){}//IGNORE
function Entry(a,b){}//IGNORE
function Singleton(x){}//IGNORE
function SequenceBuilder(){}//IGNORE
function String$(f,x){}//IGNORE

var larger = Comparison("larger");
function getLarger() { return larger }
var smaller = Comparison("smaller");
function getSmaller() { return smaller }
var equal = Comparison("equal");
function getEqual() { return equal }

exports.getLarger=getLarger;
exports.getSmaller=getSmaller;
exports.getEqual=getEqual;

//These are operators for handling nulls
function exists(value) {
    return value !== null && value !== undefined;
}
function nonempty(value) {
    return value !== null && value !== undefined && !value.empty;
}

function isOfType(obj, type) {
    if (type && type.t) {
        if (type.t == 'i' || type.t == 'u') {
            return isOfTypes(obj, type);
        }
        if (obj === null) {
            return type.t===Null || type.t===Anything;
        }
        if (obj === undefined || obj.getT$all === undefined) { return false; }
        var typeName = type.t.$$.T$name;
        if (obj.getT$all && typeName in obj.getT$all()) {
            if (type.a && obj.$$targs$$) {
                for (var i=0; i<type.a.length; i++) {
                    if (!extendsType(obj.$$targs$$[i], type.a[i])) {
                        return false;
                    }
                }
            }
            return true;
        }
    }
    return false;
}
function isOfTypes(obj, types) {
    if (obj===null) {
        for (var i=0; i < types.l.length; i++) {
            if(types.l[i].t===Null || types.l[i].t===Anything) return true;
            else if (types.l[i].t==='u') {
                if (isOfTypes(null, types.l[i])) return true;
            }
        }
        return false;
    }
    if (obj === undefined || obj.getT$all === undefined) { return false; }
    var unions = false;
    var inters = true;
    var _ints=false;
    var objTypes = obj.getT$all();
    for (var i = 0; i < types.l.length; i++) {
        var t = types.l[i];
        var partial = isOfType(obj, t);
        if (types.t==='u') {
            unions = partial || unions;
        } else {
            inters = partial && inters;
            _ints=true;
        }
    }
    return _ints ? inters||unions : unions;
}
function extendsType(t1, t2) {
    if (t1.t === 'u' || t1.t === 'i') {
        var unions = false;
        var inters = true;
        var _ints = false;
        for (var i = 0; i < t1.l.length; i++) {
            var partial = extendsType(t1.l[i], t2);
            if (t1.t==='u') {
                unions = partial||unions;
            } else {
                inters = partial&&inters;
                _ints=true;
            }
        }
        return _ints ? inters||unions : unions;
    }
    if (t2.t == 'u' || t2.t == 'i') {
        var unions = false;
        var inters = true;
        var _ints = false;
        for (var i = 0; i < t2.l.length; i++) {
            var partial = extendsType(t1, t2.l[i]);
            if (t2.t==='u') {
                unions = partial||unions;
            } else {
                inters = partial&&inters;
                _ints=true;
            }
        }
        return _ints ? inters||unions : unions;
    }
    for (t in t1.t.$$.T$all) {
        if (t === t2.t.$$.T$name || t === 'ceylon.language::Nothing') {
            return true;
        }
    }
    return false;
}

function className(obj) {
    function _typename(t) {
        if (t.t==='i' || t.t==='u') {
            var _sep = t.t==='i'?'&':'|';
            var ct = '';
            for (var i=0; i < t.l.length; i++) {
                if (i>0) { ct+=_sep; }
                ct += _typename(t.l[i]);
            }
            return String$(ct);
        } else {
            var tn = t.t.$$.T$name;
            if (t.a) {
                tn += '<';
                for (var i = 0; i < t.a.length; i++) {
                    if (i>0) { tn += ','; }
                    tn += _typename(t.a[i]);
                }
                tn += '>';
            }
            return tn;
        }
    }
    if (obj === null) return String$('ceylon.language::Null');
    var tn = obj.getT$name();
    if (obj.$$targs$$) {
        /*tn += '<';
        for (var i=0; i < obj.$$targs$$.length; i++) {
            if (i>0) { tn += ','; }
            tn += _typename(obj.$$targs$$[i]);
        }
        tn += '>';*/
    }
    return String$(tn);
}

function identityHash(obj) {
    return obj.BasicID;
}

function set_type_args(obj, targs) {
    if (obj.$$targs$$ === undefined) {
        obj.$$targs$$=targs;
    } else {
        for (x in targs) {
            obj.$$targs$$[x] = targs[x];
        }
    }
}
function add_type_arg(obj, name, type) {
    if (obj.$$targs$$ === undefined) {
        obj.$$targs$$={};
    }
    obj.$$targs$$[name]=type;
}
function throwexc(msg) {
    throw Exception(msg.getT$all?msg:String$(msg));
}
function dyntype(f) {
    if (f===undefined) throw Exception(String$("Cannot instantiate undefined type",33));
    var args = [].slice.call(arguments, 1);
    if (f.$$===undefined) {
        //Simulate "new"
        var n = {};
        n.__proto__=f.prototype;
        var r = f.apply(n,args);
        return r===undefined?n:r;
    }
    return f.apply(this,args);
}
exports.set_type_args=set_type_args;
exports.add_type_arg=add_type_arg;
exports.exists=exists;
exports.nonempty=nonempty;
exports.isOfType=isOfType;
exports.className=className;
exports.identityHash=identityHash;
exports.throwexc=throwexc;
exports.dyntype=dyntype;
<commit_msg>Remove dyntype, no longer needed<commit_after>var exports,console,process$,empty;//IGNORE
function Comparison(x){}//IGNORE
function Entry(a,b){}//IGNORE
function Singleton(x){}//IGNORE
function SequenceBuilder(){}//IGNORE
function String$(f,x){}//IGNORE

var larger = Comparison("larger");
function getLarger() { return larger }
var smaller = Comparison("smaller");
function getSmaller() { return smaller }
var equal = Comparison("equal");
function getEqual() { return equal }

exports.getLarger=getLarger;
exports.getSmaller=getSmaller;
exports.getEqual=getEqual;

//These are operators for handling nulls
function exists(value) {
    return value !== null && value !== undefined;
}
function nonempty(value) {
    return value !== null && value !== undefined && !value.empty;
}

function isOfType(obj, type) {
    if (type && type.t) {
        if (type.t == 'i' || type.t == 'u') {
            return isOfTypes(obj, type);
        }
        if (obj === null) {
            return type.t===Null || type.t===Anything;
        }
        if (obj === undefined || obj.getT$all === undefined) { return false; }
        var typeName = type.t.$$.T$name;
        if (obj.getT$all && typeName in obj.getT$all()) {
            if (type.a && obj.$$targs$$) {
                for (var i=0; i<type.a.length; i++) {
                    if (!extendsType(obj.$$targs$$[i], type.a[i])) {
                        return false;
                    }
                }
            }
            return true;
        }
    }
    return false;
}
function isOfTypes(obj, types) {
    if (obj===null) {
        for (var i=0; i < types.l.length; i++) {
            if(types.l[i].t===Null || types.l[i].t===Anything) return true;
            else if (types.l[i].t==='u') {
                if (isOfTypes(null, types.l[i])) return true;
            }
        }
        return false;
    }
    if (obj === undefined || obj.getT$all === undefined) { return false; }
    var unions = false;
    var inters = true;
    var _ints=false;
    var objTypes = obj.getT$all();
    for (var i = 0; i < types.l.length; i++) {
        var t = types.l[i];
        var partial = isOfType(obj, t);
        if (types.t==='u') {
            unions = partial || unions;
        } else {
            inters = partial && inters;
            _ints=true;
        }
    }
    return _ints ? inters||unions : unions;
}
function extendsType(t1, t2) {
    if (t1.t === 'u' || t1.t === 'i') {
        var unions = false;
        var inters = true;
        var _ints = false;
        for (var i = 0; i < t1.l.length; i++) {
            var partial = extendsType(t1.l[i], t2);
            if (t1.t==='u') {
                unions = partial||unions;
            } else {
                inters = partial&&inters;
                _ints=true;
            }
        }
        return _ints ? inters||unions : unions;
    }
    if (t2.t == 'u' || t2.t == 'i') {
        var unions = false;
        var inters = true;
        var _ints = false;
        for (var i = 0; i < t2.l.length; i++) {
            var partial = extendsType(t1, t2.l[i]);
            if (t2.t==='u') {
                unions = partial||unions;
            } else {
                inters = partial&&inters;
                _ints=true;
            }
        }
        return _ints ? inters||unions : unions;
    }
    for (t in t1.t.$$.T$all) {
        if (t === t2.t.$$.T$name || t === 'ceylon.language::Nothing') {
            return true;
        }
    }
    return false;
}

function className(obj) {
    function _typename(t) {
        if (t.t==='i' || t.t==='u') {
            var _sep = t.t==='i'?'&':'|';
            var ct = '';
            for (var i=0; i < t.l.length; i++) {
                if (i>0) { ct+=_sep; }
                ct += _typename(t.l[i]);
            }
            return String$(ct);
        } else {
            var tn = t.t.$$.T$name;
            if (t.a) {
                tn += '<';
                for (var i = 0; i < t.a.length; i++) {
                    if (i>0) { tn += ','; }
                    tn += _typename(t.a[i]);
                }
                tn += '>';
            }
            return tn;
        }
    }
    if (obj === null) return String$('ceylon.language::Null');
    var tn = obj.getT$name();
    if (obj.$$targs$$) {
        /*tn += '<';
        for (var i=0; i < obj.$$targs$$.length; i++) {
            if (i>0) { tn += ','; }
            tn += _typename(obj.$$targs$$[i]);
        }
        tn += '>';*/
    }
    return String$(tn);
}

function identityHash(obj) {
    return obj.BasicID;
}

function set_type_args(obj, targs) {
    if (obj.$$targs$$ === undefined) {
        obj.$$targs$$=targs;
    } else {
        for (x in targs) {
            obj.$$targs$$[x] = targs[x];
        }
    }
}
function add_type_arg(obj, name, type) {
    if (obj.$$targs$$ === undefined) {
        obj.$$targs$$={};
    }
    obj.$$targs$$[name]=type;
}
function throwexc(msg) {
    throw Exception(msg.getT$all?msg:String$(msg));
}
exports.set_type_args=set_type_args;
exports.add_type_arg=add_type_arg;
exports.exists=exists;
exports.nonempty=nonempty;
exports.isOfType=isOfType;
exports.className=className;
exports.identityHash=identityHash;
exports.throwexc=throwexc;
<|endoftext|>