@jbrowse/core
Version:
JBrowse 2 core libraries used by plugins
117 lines (116 loc) • 4.33 kB
JavaScript
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);
}
}