atom-nuclide
Version:
A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.
209 lines (176 loc) • 7.55 kB
JavaScript
Object.defineProperty(exports, '__esModule', {
value: true
});
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _nuclideRpc2;
function _nuclideRpc() {
return _nuclideRpc2 = require('../nuclide-rpc');
}
var _process2;
function _process() {
return _process2 = require('./process');
}
var _promise2;
function _promise() {
return _promise2 = require('./promise');
}
var _nuclideLogging2;
function _nuclideLogging() {
return _nuclideLogging2 = require('../nuclide-logging');
}
var _assert2;
function _assert() {
return _assert2 = _interopRequireDefault(require('assert'));
}
var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)();
/**
* A generic process wrapper around a stdio-based child process, providing a simple
* promise-based call API. Commonly used to wrap a python (or any other language)
* process, making it invokable through JS code.
*
* This class can be generalized further (to not require stdin/stdout as the communication method)
* by having the Transport class injected, which is currently defaulted to StreamTransport.
*
* Child Process Implementation Notes:
* - See Rpc.js for the JSON protocol that the child process implementation must follow.
* - Note that stdin, stdout, and stderr must be piped, done by node by default.
* Don't override the stdio to close off any of these streams in the constructor opts.
*/
var RpcProcess = (function () {
/**
* @param name a name for this server, used to tag log entries
* @param createProcess a function to used create the child process when needed,
* both during initialization and on restart
*/
function RpcProcess(name, serviceRegistry, createProcess) {
var _this = this;
var messageLogger = arguments.length <= 3 || arguments[3] === undefined ? function (direction, message) {
return;
} : arguments[3];
_classCallCheck(this, RpcProcess);
this._name = name;
this._serviceRegistry = serviceRegistry;
this._rpcConnection = null;
this._disposed = false;
this._ensureProcess = (0, (_promise2 || _promise()).serializeAsyncCall)(function () {
return _this._ensureProcessImpl(createProcess, messageLogger);
});
}
_createClass(RpcProcess, [{
key: 'getName',
value: function getName() {
return this._name;
}
}, {
key: 'dispose',
value: function dispose() {
logger.info(this._name + ' - disposing connection.');
this._disposed = true;
this._cleanup();
}
}, {
key: 'isDisposed',
value: function isDisposed() {
return this._disposed;
}
}, {
key: 'getService',
value: _asyncToGenerator(function* (serviceName) {
yield this._ensureProcess();
(0, (_assert2 || _assert()).default)(this._rpcConnection != null);
return this._rpcConnection.getService(serviceName);
})
/**
* Ensures that the child process is available. Asynchronously creates the child process,
* only if it is currently null.
*/
}, {
key: '_ensureProcessImpl',
value: _asyncToGenerator(function* (createProcess, messageLogger) {
var _this2 = this;
if (this._process) {
return;
}
try {
var proc = yield createProcess();
logger.info(this._name + ' - created child process with PID: ', proc.pid);
proc.stdin.on('error', function (error) {
logger.error(_this2._name + ' - error writing data: ', error);
});
this._rpcConnection = new (_nuclideRpc2 || _nuclideRpc()).RpcConnection('client', this._serviceRegistry, new (_nuclideRpc2 || _nuclideRpc()).StreamTransport(proc.stdin, proc.stdout, messageLogger));
this._subscription = (0, (_process2 || _process()).getOutputStream)(proc).subscribe(this._onProcessMessage.bind(this));
this._process = proc;
} catch (e) {
logger.error(this._name + ' - error spawning child process: ', e);
throw e;
}
})
/**
* Handles lifecycle messages from stderr, exit, and error streams,
* responding by logging and staging for process restart.
*/
}, {
key: '_onProcessMessage',
value: function _onProcessMessage(message) {
switch (message.kind) {
case 'stdout':
break;
case 'stderr':
logger.warn(this._name + ' - error from stderr received: ', message.data.toString());
break;
case 'exit':
// Log exit code if process exited not as a result of being disposed.
if (!this._disposed) {
logger.error(this._name + ' - exited: ', message.exitCode);
}
// Don't attempt to kill the process if it already exited.
this._cleanup(false);
break;
case 'error':
logger.error(this._name + ' - error received: ', message.error.message);
this._cleanup();
break;
default:
// This case should never be reached.
(0, (_assert2 || _assert()).default)(false, this._name + ' - unknown message received: ' + message);
}
}
/**
* Cleans up in case of disposal or failure, clearing all pending calls,
* and killing the child process if necessary.
*/
}, {
key: '_cleanup',
value: function _cleanup() {
var shouldKill = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
if (this._subscription != null) {
this._subscription.unsubscribe();
this._subscription = null;
}
if (this._rpcConnection != null) {
this._rpcConnection.dispose();
this._rpcConnection = null;
}
if (this._process != null && shouldKill) {
this._process.kill();
}
// If shouldKill is false, i.e. the process exited outside of this
// object's control or disposal, the process still needs to be nulled out
// to indicate that the process needs to be restarted upon the next call.
this._process = null;
}
}]);
return RpcProcess;
})();
exports.default = RpcProcess;
module.exports = exports.default;