UNPKG

parcel-bundler

Version:

Blazing fast, zero configuration web application bundler

346 lines (285 loc) 9.38 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); const _require = require('events'), EventEmitter = _require.EventEmitter; const errorUtils = require('./errorUtils'); const Worker = require('./Worker'); const cpuCount = require('../utils/cpuCount'); let shared = null; class WorkerFarm extends EventEmitter { constructor(options, farmOptions = {}) { super(); this.options = Object.assign({ maxConcurrentWorkers: WorkerFarm.getNumWorkers(), maxConcurrentCallsPerWorker: WorkerFarm.getConcurrentCallsPerWorker(), forcedKillTime: 500, warmWorkers: true, useLocalWorker: true, workerPath: '../worker' }, farmOptions); this.warmWorkers = 0; this.workers = new Map(); this.callQueue = []; this.localWorker = require(this.options.workerPath); this.run = this.mkhandle('run'); this.init(options); } warmupWorker(method, args) { // Workers are already stopping if (this.ending) { return; } // Workers are not warmed up yet. // Send the job to a remote worker in the background, // but use the result from the local worker - it will be faster. let promise = this.addCall(method, [...args, true]); if (promise) { promise.then(() => { this.warmWorkers++; if (this.warmWorkers >= this.workers.size) { this.emit('warmedup'); } }).catch(() => {}); } } shouldStartRemoteWorkers() { return this.options.maxConcurrentWorkers > 1 || process.env.NODE_ENV === 'test' || !this.options.useLocalWorker; } mkhandle(method) { return (...args) => { // Child process workers are slow to start (~600ms). // While we're waiting, just run on the main thread. // This significantly speeds up startup time. if (this.shouldUseRemoteWorkers()) { return this.addCall(method, [...args, false]); } else { if (this.options.warmWorkers && this.shouldStartRemoteWorkers()) { this.warmupWorker(method, args); } return this.localWorker[method](...args, false); } }; } onError(error, worker) { // Handle ipc errors if (error.code === 'ERR_IPC_CHANNEL_CLOSED') { return this.stopWorker(worker); } } startChild() { let worker = new Worker(this.options); worker.fork(this.options.workerPath, this.bundlerOptions); worker.on('request', data => this.processRequest(data, worker)); worker.on('ready', () => this.processQueue()); worker.on('response', () => this.processQueue()); worker.on('error', err => this.onError(err, worker)); worker.once('exit', () => this.stopWorker(worker)); this.workers.set(worker.id, worker); } stopWorker(worker) { var _this = this; return (0, _asyncToGenerator2.default)(function* () { if (!worker.stopped) { _this.workers.delete(worker.id); worker.isStopping = true; if (worker.calls.size) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = worker.calls.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { let call = _step.value; call.retries++; _this.callQueue.unshift(call); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } worker.calls = null; yield worker.stop(); // Process any requests that failed and start a new worker _this.processQueue(); } })(); } processQueue() { var _this2 = this; return (0, _asyncToGenerator2.default)(function* () { if (_this2.ending || !_this2.callQueue.length) return; if (_this2.workers.size < _this2.options.maxConcurrentWorkers) { _this2.startChild(); } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = _this2.workers.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { let worker = _step2.value; if (!_this2.callQueue.length) { break; } if (!worker.ready || worker.stopped || worker.isStopping) { continue; } if (worker.calls.size < _this2.options.maxConcurrentCallsPerWorker) { worker.call(_this2.callQueue.shift()); } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return != null) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } })(); } processRequest(data, worker = false) { return (0, _asyncToGenerator2.default)(function* () { let result = { idx: data.idx, type: 'response' }; let method = data.method; let args = data.args; let location = data.location; let awaitResponse = data.awaitResponse; if (!location) { throw new Error('Unknown request'); } const mod = require(location); try { result.contentType = 'data'; if (method) { result.content = yield mod[method](...args); } else { result.content = yield mod(...args); } } catch (e) { result.contentType = 'error'; result.content = errorUtils.errorToJson(e); } if (awaitResponse) { if (worker) { worker.send(result); } else { return result; } } })(); } addCall(method, args) { if (this.ending) { throw new Error('Cannot add a worker call if workerfarm is ending.'); } return new Promise((resolve, reject) => { this.callQueue.push({ method, args: args, retries: 0, resolve, reject }); this.processQueue(); }); } end() { var _this3 = this; return (0, _asyncToGenerator2.default)(function* () { _this3.ending = true; yield Promise.all(Array.from(_this3.workers.values()).map(worker => _this3.stopWorker(worker))); _this3.ending = false; shared = null; })(); } init(bundlerOptions) { this.bundlerOptions = bundlerOptions; if (this.shouldStartRemoteWorkers()) { this.persistBundlerOptions(); } this.localWorker.init(bundlerOptions); this.startMaxWorkers(); } persistBundlerOptions() { var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = this.workers.values()[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { let worker = _step3.value; worker.init(this.bundlerOptions); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return != null) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } startMaxWorkers() { // Starts workers untill the maximum is reached if (this.workers.size < this.options.maxConcurrentWorkers) { for (let i = 0; i < this.options.maxConcurrentWorkers - this.workers.size; i++) { this.startChild(); } } } shouldUseRemoteWorkers() { return !this.options.useLocalWorker || this.warmWorkers >= this.workers.size || !this.options.warmWorkers; } static getShared(options) { if (!shared) { shared = new WorkerFarm(options); } else if (options) { shared.init(options); } if (!shared && !options) { throw new Error('Workerfarm should be initialised using options'); } return shared; } static getNumWorkers() { return process.env.PARCEL_WORKERS ? parseInt(process.env.PARCEL_WORKERS, 10) : cpuCount(); } static callMaster(request, awaitResponse = true) { if (WorkerFarm.isWorker()) { const child = require('./child'); return child.addCall(request, awaitResponse); } else { return WorkerFarm.getShared().processRequest(request); } } static isWorker() { return process.send && require.main.filename === require.resolve('./child'); } static getConcurrentCallsPerWorker() { return parseInt(process.env.PARCEL_MAX_CONCURRENT_CALLS, 10) || 5; } } module.exports = WorkerFarm;