UNPKG

@jbrowse/core

Version:

JBrowse 2 core libraries used by plugins

117 lines (116 loc) 4.33 kB
import { isAlive, isStateTreeNode } from '@jbrowse/mobx-state-tree'; import { readConfObject } from "../configuration/index.js"; import { clamp } from "../util/index.js"; function isCloneable(thing) { return !(typeof thing === 'function') && !(thing instanceof Error); } function detectHardwareConcurrency() { const mainThread = typeof window !== 'undefined'; const canDetect = mainThread && 'hardwareConcurrency' in window.navigator; return mainThread && canDetect ? window.navigator.hardwareConcurrency : 1; } class LazyWorker { driver; workerP; constructor(driver) { this.driver = driver; } async getWorker() { if (!this.workerP) { this.workerP = this.driver.makeWorker().catch((e) => { this.workerP = undefined; throw e; }); } return this.workerP; } } export default class BaseRpcDriver { lastWorkerAssignment = -1; workerAssignments = new Map(); workerPool; config; constructor(args) { this.config = args.config; } filterArgs(thing, sessionId) { if (thing === null || thing === undefined) { return thing; } const type = typeof thing; if (type === 'string' || type === 'number' || type === 'boolean') { return thing; } if (type !== 'object') { return undefined; } if (Array.isArray(thing)) { return thing .filter(thing => isCloneable(thing)) .map(t => this.filterArgs(t, sessionId)); } else if (isStateTreeNode(thing) && !isAlive(thing)) { throw new Error('dead state tree node passed to RPC call'); } else if (thing instanceof File) { return thing; } else { return Object.fromEntries(Object.entries(thing) .filter(e => isCloneable(e[1])) .map(([k, v]) => [k, this.filterArgs(v, sessionId)])); } } async remoteAbort(sessionId, functionName, stopTokenId) { const worker = await this.getWorker(sessionId); await worker.call(functionName, { stopTokenId }, { rpcDriverClassName: this.name }); } createWorkerPool() { const hardwareConcurrency = detectHardwareConcurrency(); const workerCount = readConfObject(this.config, 'workerCount') || clamp(1, Math.max(1, hardwareConcurrency - 1), 5); const workers = []; for (let i = 0; i < workerCount; i++) { workers.push(new LazyWorker(this)); } return workers; } getWorkerPool() { if (!this.workerPool) { const res = this.createWorkerPool(); this.workerPool = res; return res; } return this.workerPool; } async getWorker(sessionId) { const workers = this.getWorkerPool(); let workerNumber = this.workerAssignments.get(sessionId); if (workerNumber === undefined) { const workerAssignment = (this.lastWorkerAssignment + 1) % workers.length; this.workerAssignments.set(sessionId, workerAssignment); this.lastWorkerAssignment = workerAssignment; workerNumber = workerAssignment; } return workers[workerNumber].getWorker(); } async call(pluginManager, sessionId, functionName, args, options = {}) { if (!sessionId) { throw new TypeError('sessionId is required'); } const unextendedWorker = await this.getWorker(sessionId); const worker = pluginManager.evaluateExtensionPoint('Core-extendWorker', unextendedWorker); const rpcMethod = pluginManager.getRpcMethodType(functionName); if (!rpcMethod) { throw new Error(`unknown RPC method ${functionName}`); } const serializedArgs = await rpcMethod.serializeArguments(args, this.name); const filteredAndSerializedArgs = this.filterArgs(serializedArgs, sessionId); const call = await worker.call(functionName, filteredAndSerializedArgs, { statusCallback: args.statusCallback, rpcDriverClassName: this.name, ...options, }); return rpcMethod.deserializeReturn(call, args, this.name); } }