UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

260 lines 8.89 kB
import { BlobWorker, Pool, spawn, Worker } from 'threads'; import { DataSerializer } from '../data/DataSerializer'; import { DataService } from '../service/DataService'; import { WorkerServiceProxy } from '../service/WorkerServiceProxy'; import { AsyncEventEmitter } from '../_internal/AsyncEventEmitter'; import { DummyDataService } from '../service/DummyDataService'; import { DummyService } from '../service/DummyService'; export class WorkerHandler extends AsyncEventEmitter { constructor(model, options, config) { var _a; super(); this._serviceOutputResponse = new Map(); this.model = model; this.config = config; this.options = options; this.options.timeout = (_a = this.options.timeout) !== null && _a !== void 0 ? _a : 10000; } build() { return new Promise(resolve => { if (typeof process.env.NODE_ENV === 'undefined') { // eslint-disable-next-line const NativeWorker = typeof __non_webpack_require__ === "function" ? __non_webpack_require__("worker_threads").Worker : eval("require")("worker_threads").Worker; if (NativeWorker) { // NodeJS NativeWorker.defaultMaxListeners = 0; const resolvedPath = require.resolve(this.options.worker); if (resolvedPath.match(/\.tsx?$/i)) { // Transpile this.options.worker = ` require('ts-node').register(); require(${JSON.stringify(resolvedPath)}); `; this.options.blob = true; } } } this._pool = Pool(() => this._spawnWorker(), { size: this.options.poolSize || 4, concurrency: this.options.poolConcurrency || 2 }); this._pool.events().subscribe(value => { if (value.type === 'initialized') { resolve(); } }); }); } destroy() { return new Promise((resolve, reject) => { if (this._pool === undefined) { return resolve(); } const timeout = setTimeout(() => { this._pool.terminate(true).then(() => { resolve(); }).catch(ex => { reject(ex); }); }, 2500); this._pool.terminate().then(() => { clearTimeout(timeout); resolve(); }).catch(ex => { clearTimeout(timeout); reject(ex); }); }); } pull(options) { return new Promise((resolve, reject) => { // Pass the pull request to the worker this._pool.queue(worker => { const pullFn = worker.pull; return pullFn(options); }).then(resolve).catch(reject); }); } push(frame, options) { return new Promise((resolve, reject) => { this._pool.queue(worker => { const pushFn = worker.push; return pushFn(DataSerializer.serialize(frame), options); }).then(() => { resolve(); }).catch(reject); }); } invokeMethod(methodName, ...args) { return new Promise((resolve, reject) => { this._pool.queue(worker => { const invokeMethod = worker.invokeMethod; return invokeMethod(methodName, ...args.map(a => DataSerializer.serialize(a))); }).then(result => { if (result !== undefined) { resolve(DataSerializer.deserialize(result)); } else { resolve(result); } }).catch(reject); }); } createWorker() { if (this.options.blob) { const worker = new BlobWorker(this.options.worker, { type: this.options.type === 'typescript' ? 'classic' : this.options.type }); return worker; } else { const worker = new Worker(this.options.worker, { type: this.options.type === 'typescript' ? 'classic' : this.options.type }); return worker; } } /** * Spawn a single worker * This method can be called multiple times in a pool * @returns {Promise<Thread>} Thread spawn promise */ _spawnWorker() { return new Promise((resolve, reject) => { const worker = this.createWorker(); spawn(worker, { timeout: this.options.timeout }).then(thread => { const init = thread.init; const pushOutput = thread.pushOutput; const pullOutput = thread.pullOutput; const serviceOutputCall = thread.serviceOutputCall; const serviceInputCall = thread.serviceInputCall; const eventOutput = thread.eventOutput; const findAllServices = thread.findAllServices; const threadId = worker.threadId; this._serviceOutputResponse.set(threadId, thread.serviceOutputResponse); // Subscribe to the workers pull, push and service functions pullOutput().subscribe(this._onWorkerPull.bind(this)); pushOutput().subscribe(this._onWorkerPush.bind(this)); serviceOutputCall().subscribe(this._onWorkerService.bind(this, threadId)); eventOutput().subscribe(this._onWorkerEvent.bind(this)); // Initialize the worker init(Object.assign({ directory: this.options.directory || __dirname, services: this._getServices(), imports: this.options.imports || [], args: this.options.args || {}, type: this.options.type || 'classic', methods: this.options.methods ? this.options.methods.map(method => { return { name: method.name, handlerFn: method.handler.toString() }; }) : [] }, this.config)).then(() => { return findAllServices(); }).then(services => { this._addServices(services, serviceInputCall); resolve(thread); }).catch(reject); }).catch(reject); }); } /** * Serialize the services of this model * @returns {any[]} Services array */ _getServices() { // Serialize this model services to the worker const services = this.options.services || this.model.findAllServices(); const servicesArray = services.map(service => { // Services are wrapped in a proxy. Get prototype const serviceBase = Object.getPrototypeOf(service); return { uid: service.uid, type: serviceBase.constructor.name, dataType: service instanceof DataService ? service.dataType ? service.dataType.name : undefined : undefined }; }); return servicesArray; } _addServices(services, call) { const model = this.model; services.filter(service => { const internalService = this.model.findService(service.name) || this.model.findDataService(service.name); return internalService === undefined; }).forEach(service => { if (service.dataType) { const DataType = DataSerializer.findTypeByName(service.dataType); model.addService(new DummyDataService(service.uid, DataType), new WorkerServiceProxy({ uid: service.uid, callFunction: call })); } else { model.addService(new DummyService(service.uid), new WorkerServiceProxy({ uid: service.uid, callFunction: call })); } }); } _onWorkerService(threadId, value) { const service = this.model.findDataService(value.serviceUID) || this.model.findService(value.serviceUID); if (service[value.method]) { const serializedParams = value.parameters; const params = []; serializedParams.forEach(param => { if (param['__type']) { params.push(DataSerializer.deserialize(param)); } else { params.push(param); } }); const promise = service[value.method](...params); Promise.resolve(promise).then(_ => { if (Array.isArray(_)) { const result = []; _.forEach(r => { result.push(DataSerializer.serialize(r)); }); this._serviceOutputResponse.get(threadId)({ id: value.id, success: true, result }); } else { const result = DataSerializer.serialize(_); this._serviceOutputResponse.get(threadId)({ id: value.id, success: true, result }); } }).catch(ex => { this._serviceOutputResponse.get(threadId)({ id: value.id, success: false, result: ex }); }); } } _onWorkerEvent(value) { this.emit('event', value); } /** * Triggered for each worker that requests a pull * @param {PullOptions} options Pull options */ _onWorkerPull(options) { this.emit('pull', options); } /** * Triggered for each worker that pushes data * @param {any} value Serialized data * @param {PushOptions} options Push options */ _onWorkerPush(value, options) { const deserializedFrame = DataSerializer.deserialize(value); this.emit('push', deserializedFrame, options); } }