forked from mystiq/hydrogen-web
load worker in main and pass paths so it works both on compiled and non-compiled
This commit is contained in:
parent
de1cc0d739
commit
af36c71a59
9 changed files with 268 additions and 200 deletions
|
@ -19,9 +19,12 @@
|
||||||
<script id="main" type="module">
|
<script id="main" type="module">
|
||||||
import {main} from "./src/main.js";
|
import {main} from "./src/main.js";
|
||||||
main(document.body, {
|
main(document.body, {
|
||||||
|
worker: "src/worker.js",
|
||||||
|
olm: {
|
||||||
wasm: "lib/olm/olm.wasm",
|
wasm: "lib/olm/olm.wasm",
|
||||||
legacyBundle: "lib/olm/olm_legacy.js",
|
legacyBundle: "lib/olm/olm_legacy.js",
|
||||||
wasmBundle: "lib/olm/olm.js",
|
wasmBundle: "lib/olm/olm.js",
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script id="service-worker" type="disabled">
|
<script id="service-worker" type="disabled">
|
||||||
|
|
|
@ -163,10 +163,14 @@ async function buildHtml(doc, version, assetPaths, manifestPath) {
|
||||||
findThemes(doc, (themeName, theme) => {
|
findThemes(doc, (themeName, theme) => {
|
||||||
theme.attr("href", assetPaths.cssThemeBundle(themeName));
|
theme.attr("href", assetPaths.cssThemeBundle(themeName));
|
||||||
});
|
});
|
||||||
|
const pathsJSON = JSON.stringify({
|
||||||
|
worker: assetPaths.jsWorker(),
|
||||||
|
olm: olmFiles
|
||||||
|
});
|
||||||
doc("script#main").replaceWith(
|
doc("script#main").replaceWith(
|
||||||
`<script type="module">import {main} from "./${assetPaths.jsBundle()}"; main(document.body, ${JSON.stringify(olmFiles)});</script>` +
|
`<script type="module">import {main} from "./${assetPaths.jsBundle()}"; main(document.body, ${pathsJSON});</script>` +
|
||||||
`<script type="text/javascript" nomodule src="${assetPaths.jsLegacyBundle()}"></script>` +
|
`<script type="text/javascript" nomodule src="${assetPaths.jsLegacyBundle()}"></script>` +
|
||||||
`<script type="text/javascript" nomodule>${PROJECT_ID}Bundle.main(document.body, ${JSON.stringify(olmFiles)});</script>`);
|
`<script type="text/javascript" nomodule>${PROJECT_ID}Bundle.main(document.body, ${pathsJSON});</script>`);
|
||||||
removeOrEnableScript(doc("script#service-worker"), offline);
|
removeOrEnableScript(doc("script#service-worker"), offline);
|
||||||
|
|
||||||
const versionScript = doc("script#version");
|
const versionScript = doc("script#version");
|
||||||
|
|
30
src/main.js
30
src/main.js
|
@ -25,6 +25,7 @@ import {BrawlViewModel} from "./domain/BrawlViewModel.js";
|
||||||
import {BrawlView} from "./ui/web/BrawlView.js";
|
import {BrawlView} from "./ui/web/BrawlView.js";
|
||||||
import {Clock} from "./ui/web/dom/Clock.js";
|
import {Clock} from "./ui/web/dom/Clock.js";
|
||||||
import {OnlineStatus} from "./ui/web/dom/OnlineStatus.js";
|
import {OnlineStatus} from "./ui/web/dom/OnlineStatus.js";
|
||||||
|
import {WorkerPool} from "./utils/WorkerPool.js";
|
||||||
|
|
||||||
function addScript(src) {
|
function addScript(src) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
|
@ -55,10 +56,27 @@ async function loadOlm(olmPaths) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make path relative to basePath,
|
||||||
|
// assuming it and basePath are relative to document
|
||||||
|
function relPath(path, basePath) {
|
||||||
|
const idx = basePath.lastIndexOf("/");
|
||||||
|
const dir = idx === -1 ? "" : basePath.slice(0, idx);
|
||||||
|
const dirCount = dir.length ? dir.split("/").length : 0;
|
||||||
|
return "../".repeat(dirCount) + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWorker(paths) {
|
||||||
|
const workerPool = new WorkerPool(paths.worker, 4);
|
||||||
|
await workerPool.init();
|
||||||
|
const path = relPath(paths.olm.legacyBundle, paths.worker);
|
||||||
|
await workerPool.sendAll({type: "load_olm", path});
|
||||||
|
return workerPool;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't use a default export here, as we use multiple entries during legacy build,
|
// Don't use a default export here, as we use multiple entries during legacy build,
|
||||||
// which does not support default exports,
|
// which does not support default exports,
|
||||||
// see https://github.com/rollup/plugins/tree/master/packages/multi-entry
|
// see https://github.com/rollup/plugins/tree/master/packages/multi-entry
|
||||||
export async function main(container, olmPaths) {
|
export async function main(container, paths) {
|
||||||
try {
|
try {
|
||||||
// to replay:
|
// to replay:
|
||||||
// const fetchLog = await (await fetch("/fetchlogs/constrainterror.json")).json();
|
// const fetchLog = await (await fetch("/fetchlogs/constrainterror.json")).json();
|
||||||
|
@ -79,6 +97,13 @@ export async function main(container, olmPaths) {
|
||||||
const sessionInfoStorage = new SessionInfoStorage("brawl_sessions_v1");
|
const sessionInfoStorage = new SessionInfoStorage("brawl_sessions_v1");
|
||||||
const storageFactory = new StorageFactory();
|
const storageFactory = new StorageFactory();
|
||||||
|
|
||||||
|
// if wasm is not supported, we'll want
|
||||||
|
// to run some olm operations in a worker (mainly for IE11)
|
||||||
|
let workerPromise;
|
||||||
|
if (!window.WebAssembly) {
|
||||||
|
workerPromise = loadWorker(paths);
|
||||||
|
}
|
||||||
|
|
||||||
const vm = new BrawlViewModel({
|
const vm = new BrawlViewModel({
|
||||||
createSessionContainer: () => {
|
createSessionContainer: () => {
|
||||||
return new SessionContainer({
|
return new SessionContainer({
|
||||||
|
@ -88,7 +113,8 @@ export async function main(container, olmPaths) {
|
||||||
sessionInfoStorage,
|
sessionInfoStorage,
|
||||||
request,
|
request,
|
||||||
clock,
|
clock,
|
||||||
olmPromise: loadOlm(olmPaths),
|
olmPromise: loadOlm(paths.olm),
|
||||||
|
workerPromise,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
sessionInfoStorage,
|
sessionInfoStorage,
|
||||||
|
|
|
@ -33,7 +33,7 @@ const PICKLE_KEY = "DEFAULT_KEY";
|
||||||
|
|
||||||
export class Session {
|
export class Session {
|
||||||
// sessionInfo contains deviceId, userId and homeServer
|
// sessionInfo contains deviceId, userId and homeServer
|
||||||
constructor({clock, storage, hsApi, sessionInfo, olm}) {
|
constructor({clock, storage, hsApi, sessionInfo, olm, workerPool}) {
|
||||||
this._clock = clock;
|
this._clock = clock;
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
this._hsApi = hsApi;
|
this._hsApi = hsApi;
|
||||||
|
@ -52,6 +52,7 @@ export class Session {
|
||||||
this._megolmEncryption = null;
|
this._megolmEncryption = null;
|
||||||
this._megolmDecryption = null;
|
this._megolmDecryption = null;
|
||||||
this._getSyncToken = () => this.syncToken;
|
this._getSyncToken = () => this.syncToken;
|
||||||
|
this._workerPool = workerPool;
|
||||||
|
|
||||||
if (olm) {
|
if (olm) {
|
||||||
this._olmUtil = new olm.Utility();
|
this._olmUtil = new olm.Utility();
|
||||||
|
@ -100,6 +101,7 @@ export class Session {
|
||||||
this._megolmDecryption = new MegOlmDecryption({
|
this._megolmDecryption = new MegOlmDecryption({
|
||||||
pickleKey: PICKLE_KEY,
|
pickleKey: PICKLE_KEY,
|
||||||
olm: this._olm,
|
olm: this._olm,
|
||||||
|
workerPool: this._workerPool,
|
||||||
});
|
});
|
||||||
this._deviceMessageHandler.enableEncryption({olmDecryption, megolmDecryption: this._megolmDecryption});
|
this._deviceMessageHandler.enableEncryption({olmDecryption, megolmDecryption: this._megolmDecryption});
|
||||||
}
|
}
|
||||||
|
@ -202,6 +204,7 @@ export class Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
this._workerPool?.dispose();
|
||||||
this._sendScheduler.stop();
|
this._sendScheduler.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ export const LoginFailure = createEnum(
|
||||||
);
|
);
|
||||||
|
|
||||||
export class SessionContainer {
|
export class SessionContainer {
|
||||||
constructor({clock, random, onlineStatus, request, storageFactory, sessionInfoStorage, olmPromise}) {
|
constructor({clock, random, onlineStatus, request, storageFactory, sessionInfoStorage, olmPromise, workerPromise}) {
|
||||||
this._random = random;
|
this._random = random;
|
||||||
this._clock = clock;
|
this._clock = clock;
|
||||||
this._onlineStatus = onlineStatus;
|
this._onlineStatus = onlineStatus;
|
||||||
|
@ -59,6 +59,7 @@ export class SessionContainer {
|
||||||
this._sessionId = null;
|
this._sessionId = null;
|
||||||
this._storage = null;
|
this._storage = null;
|
||||||
this._olmPromise = olmPromise;
|
this._olmPromise = olmPromise;
|
||||||
|
this._workerPromise = workerPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewSessionId() {
|
createNewSessionId() {
|
||||||
|
@ -152,8 +153,13 @@ export class SessionContainer {
|
||||||
homeServer: sessionInfo.homeServer,
|
homeServer: sessionInfo.homeServer,
|
||||||
};
|
};
|
||||||
const olm = await this._olmPromise;
|
const olm = await this._olmPromise;
|
||||||
|
let workerPool = null;
|
||||||
|
if (this._workerPromise) {
|
||||||
|
workerPool = await this._workerPromise;
|
||||||
|
}
|
||||||
this._session = new Session({storage: this._storage,
|
this._session = new Session({storage: this._storage,
|
||||||
sessionInfo: filteredSessionInfo, hsApi, olm, clock: this._clock});
|
sessionInfo: filteredSessionInfo, hsApi, olm,
|
||||||
|
clock: this._clock, workerPool});
|
||||||
await this._session.load();
|
await this._session.load();
|
||||||
this._status.set(LoadStatus.SessionSetup);
|
this._status.set(LoadStatus.SessionSetup);
|
||||||
await this._session.beforeFirstSync(isNewLogin);
|
await this._session.beforeFirstSync(isNewLogin);
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {SessionInfo} from "./decryption/SessionInfo.js";
|
||||||
import {DecryptionPreparation} from "./decryption/DecryptionPreparation.js";
|
import {DecryptionPreparation} from "./decryption/DecryptionPreparation.js";
|
||||||
import {SessionDecryption} from "./decryption/SessionDecryption.js";
|
import {SessionDecryption} from "./decryption/SessionDecryption.js";
|
||||||
import {SessionCache} from "./decryption/SessionCache.js";
|
import {SessionCache} from "./decryption/SessionCache.js";
|
||||||
import {DecryptionWorker, WorkerPool} from "./decryption/DecryptionWorker.js";
|
import {DecryptionWorker} from "./decryption/DecryptionWorker.js";
|
||||||
|
|
||||||
function getSenderKey(event) {
|
function getSenderKey(event) {
|
||||||
return event.content?.["sender_key"];
|
return event.content?.["sender_key"];
|
||||||
|
@ -36,12 +36,10 @@ function getCiphertext(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Decryption {
|
export class Decryption {
|
||||||
constructor({pickleKey, olm}) {
|
constructor({pickleKey, olm, workerPool}) {
|
||||||
this._pickleKey = pickleKey;
|
this._pickleKey = pickleKey;
|
||||||
this._olm = olm;
|
this._olm = olm;
|
||||||
this._decryptor = new DecryptionWorker(new WorkerPool("worker-1039452087.js", 4));
|
this._decryptor = workerPool ? new DecryptionWorker(workerPool) : null;
|
||||||
//this._decryptor = new DecryptionWorker(new WorkerPool("./src/worker.js", 4));
|
|
||||||
this._initPromise = this._decryptor.init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createSessionCache(fallback) {
|
createSessionCache(fallback) {
|
||||||
|
@ -58,7 +56,6 @@ export class Decryption {
|
||||||
* @return {DecryptionPreparation}
|
* @return {DecryptionPreparation}
|
||||||
*/
|
*/
|
||||||
async prepareDecryptAll(roomId, events, sessionCache, txn) {
|
async prepareDecryptAll(roomId, events, sessionCache, txn) {
|
||||||
await this._initPromise;
|
|
||||||
const errors = new Map();
|
const errors = new Map();
|
||||||
const validEvents = [];
|
const validEvents = [];
|
||||||
|
|
||||||
|
|
|
@ -14,184 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AbortError} from "../../../../utils/error.js";
|
|
||||||
|
|
||||||
class WorkerState {
|
|
||||||
constructor(worker) {
|
|
||||||
this.worker = worker;
|
|
||||||
this.busy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
attach(pool) {
|
|
||||||
this.worker.addEventListener("message", pool);
|
|
||||||
this.worker.addEventListener("error", pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
detach(pool) {
|
|
||||||
this.worker.removeEventListener("message", pool);
|
|
||||||
this.worker.removeEventListener("error", pool);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Request {
|
|
||||||
constructor(message, pool) {
|
|
||||||
this._promise = new Promise((_resolve, _reject) => {
|
|
||||||
this._resolve = _resolve;
|
|
||||||
this._reject = _reject;
|
|
||||||
});
|
|
||||||
this._message = message;
|
|
||||||
this._pool = pool;
|
|
||||||
this._worker = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
abort() {
|
|
||||||
if (this._isNotDisposed) {
|
|
||||||
this._pool._abortRequest(this);
|
|
||||||
this._dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response() {
|
|
||||||
return this._promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
_dispose() {
|
|
||||||
this._reject = null;
|
|
||||||
this._resolve = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get _isNotDisposed() {
|
|
||||||
return this._resolve && this._reject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WorkerPool {
|
|
||||||
constructor(path, amount) {
|
|
||||||
this._workers = [];
|
|
||||||
for (let i = 0; i < amount ; ++i) {
|
|
||||||
const worker = new WorkerState(new Worker(path));
|
|
||||||
worker.attach(this);
|
|
||||||
this._workers[i] = worker;
|
|
||||||
}
|
|
||||||
this._requests = new Map();
|
|
||||||
this._counter = 0;
|
|
||||||
this._pendingFlag = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEvent(e) {
|
|
||||||
if (e.type === "message") {
|
|
||||||
const message = e.data;
|
|
||||||
const request = this._requests.get(message.replyToId);
|
|
||||||
if (request) {
|
|
||||||
request._worker.busy = false;
|
|
||||||
if (request._isNotDisposed) {
|
|
||||||
if (message.type === "success") {
|
|
||||||
request._resolve(message.payload);
|
|
||||||
} else if (message.type === "error") {
|
|
||||||
request._reject(new Error(message.stack));
|
|
||||||
}
|
|
||||||
request._dispose();
|
|
||||||
}
|
|
||||||
this._requests.delete(message.replyToId);
|
|
||||||
}
|
|
||||||
this._sendPending();
|
|
||||||
} else if (e.type === "error") {
|
|
||||||
console.error("worker error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getPendingRequest() {
|
|
||||||
for (const r of this._requests.values()) {
|
|
||||||
if (!r._worker) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getFreeWorker() {
|
|
||||||
for (const w of this._workers) {
|
|
||||||
if (!w.busy) {
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendPending() {
|
|
||||||
this._pendingFlag = false;
|
|
||||||
let success;
|
|
||||||
do {
|
|
||||||
success = false;
|
|
||||||
const request = this._getPendingRequest();
|
|
||||||
if (request) {
|
|
||||||
const worker = this._getFreeWorker();
|
|
||||||
if (worker) {
|
|
||||||
this._sendWith(request, worker);
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (success);
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendWith(request, worker) {
|
|
||||||
request._worker = worker;
|
|
||||||
worker.busy = true;
|
|
||||||
worker.worker.postMessage(request._message);
|
|
||||||
}
|
|
||||||
|
|
||||||
_enqueueRequest(message) {
|
|
||||||
this._counter += 1;
|
|
||||||
message.id = this._counter;
|
|
||||||
const request = new Request(message, this);
|
|
||||||
this._requests.set(message.id, request);
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
send(message) {
|
|
||||||
const request = this._enqueueRequest(message);
|
|
||||||
const worker = this._getFreeWorker();
|
|
||||||
if (worker) {
|
|
||||||
this._sendWith(request, worker);
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
// assumes all workers are free atm
|
|
||||||
sendAll(message) {
|
|
||||||
const promises = this._workers.map(worker => {
|
|
||||||
const request = this._enqueueRequest(Object.assign({}, message));
|
|
||||||
this._sendWith(request, worker);
|
|
||||||
return request.response();
|
|
||||||
});
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
for (const w of this._workers) {
|
|
||||||
w.worker.terminate();
|
|
||||||
w.detach(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_trySendPendingInNextTick() {
|
|
||||||
if (!this._pendingFlag) {
|
|
||||||
this._pendingFlag = true;
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
this._sendPending();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_abortRequest(request) {
|
|
||||||
request._reject(new AbortError());
|
|
||||||
if (request._worker) {
|
|
||||||
request._worker.busy = false;
|
|
||||||
}
|
|
||||||
this._requests.delete(request._message.id);
|
|
||||||
// allow more requests to be aborted before trying to send other pending
|
|
||||||
this._trySendPendingInNextTick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DecryptionWorker {
|
export class DecryptionWorker {
|
||||||
constructor(workerPool) {
|
constructor(workerPool) {
|
||||||
this._workerPool = workerPool;
|
this._workerPool = workerPool;
|
||||||
|
@ -201,9 +23,4 @@ export class DecryptionWorker {
|
||||||
const sessionKey = session.export_session(session.first_known_index());
|
const sessionKey = session.export_session(session.first_known_index());
|
||||||
return this._workerPool.send({type: "megolm_decrypt", ciphertext, sessionKey});
|
return this._workerPool.send({type: "megolm_decrypt", ciphertext, sessionKey});
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
|
||||||
await this._workerPool.sendAll({type: "load_olm", path: "olm_legacy-3232457086.js"});
|
|
||||||
//await this._workerPool.sendAll({type: "load_olm", path: "../lib/olm/olm_legacy.js"});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
212
src/utils/WorkerPool.js
Normal file
212
src/utils/WorkerPool.js
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {AbortError} from "./error.js";
|
||||||
|
|
||||||
|
class WorkerState {
|
||||||
|
constructor(worker) {
|
||||||
|
this.worker = worker;
|
||||||
|
this.busy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
attach(pool) {
|
||||||
|
this.worker.addEventListener("message", pool);
|
||||||
|
this.worker.addEventListener("error", pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
detach(pool) {
|
||||||
|
this.worker.removeEventListener("message", pool);
|
||||||
|
this.worker.removeEventListener("error", pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
constructor(message, pool) {
|
||||||
|
this._promise = new Promise((_resolve, _reject) => {
|
||||||
|
this._resolve = _resolve;
|
||||||
|
this._reject = _reject;
|
||||||
|
});
|
||||||
|
this._message = message;
|
||||||
|
this._pool = pool;
|
||||||
|
this._worker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
if (this._isNotDisposed) {
|
||||||
|
this._pool._abortRequest(this);
|
||||||
|
this._dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response() {
|
||||||
|
return this._promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dispose() {
|
||||||
|
this._reject = null;
|
||||||
|
this._resolve = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _isNotDisposed() {
|
||||||
|
return this._resolve && this._reject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkerPool {
|
||||||
|
// TODO: extract DOM specific bits and write unit tests
|
||||||
|
constructor(path, amount) {
|
||||||
|
this._workers = [];
|
||||||
|
for (let i = 0; i < amount ; ++i) {
|
||||||
|
const worker = new WorkerState(new Worker(path));
|
||||||
|
worker.attach(this);
|
||||||
|
this._workers[i] = worker;
|
||||||
|
}
|
||||||
|
this._requests = new Map();
|
||||||
|
this._counter = 0;
|
||||||
|
this._pendingFlag = false;
|
||||||
|
this._init = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
this._init = {resolve, reject};
|
||||||
|
});
|
||||||
|
this.sendAll({type: "ping"})
|
||||||
|
.then(this._init.resolve, this._init.reject)
|
||||||
|
.finally(() => {
|
||||||
|
this._init = null;
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(e) {
|
||||||
|
console.log("WorkerPool event", e);
|
||||||
|
if (e.type === "message") {
|
||||||
|
const message = e.data;
|
||||||
|
const request = this._requests.get(message.replyToId);
|
||||||
|
if (request) {
|
||||||
|
request._worker.busy = false;
|
||||||
|
if (request._isNotDisposed) {
|
||||||
|
if (message.type === "success") {
|
||||||
|
request._resolve(message.payload);
|
||||||
|
} else if (message.type === "error") {
|
||||||
|
request._reject(new Error(message.stack));
|
||||||
|
}
|
||||||
|
request._dispose();
|
||||||
|
}
|
||||||
|
this._requests.delete(message.replyToId);
|
||||||
|
}
|
||||||
|
this._sendPending();
|
||||||
|
} else if (e.type === "error") {
|
||||||
|
if (this._init) {
|
||||||
|
this._init.reject(new Error("worker error during init"));
|
||||||
|
}
|
||||||
|
console.error("worker error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getPendingRequest() {
|
||||||
|
for (const r of this._requests.values()) {
|
||||||
|
if (!r._worker) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFreeWorker() {
|
||||||
|
for (const w of this._workers) {
|
||||||
|
if (!w.busy) {
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendPending() {
|
||||||
|
this._pendingFlag = false;
|
||||||
|
let success;
|
||||||
|
do {
|
||||||
|
success = false;
|
||||||
|
const request = this._getPendingRequest();
|
||||||
|
if (request) {
|
||||||
|
const worker = this._getFreeWorker();
|
||||||
|
if (worker) {
|
||||||
|
this._sendWith(request, worker);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (success);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendWith(request, worker) {
|
||||||
|
request._worker = worker;
|
||||||
|
worker.busy = true;
|
||||||
|
worker.worker.postMessage(request._message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_enqueueRequest(message) {
|
||||||
|
this._counter += 1;
|
||||||
|
message.id = this._counter;
|
||||||
|
const request = new Request(message, this);
|
||||||
|
this._requests.set(message.id, request);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(message) {
|
||||||
|
const request = this._enqueueRequest(message);
|
||||||
|
const worker = this._getFreeWorker();
|
||||||
|
if (worker) {
|
||||||
|
this._sendWith(request, worker);
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes all workers are free atm
|
||||||
|
sendAll(message) {
|
||||||
|
const promises = this._workers.map(worker => {
|
||||||
|
const request = this._enqueueRequest(Object.assign({}, message));
|
||||||
|
this._sendWith(request, worker);
|
||||||
|
return request.response();
|
||||||
|
});
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
for (const w of this._workers) {
|
||||||
|
w.detach(this);
|
||||||
|
w.worker.terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_trySendPendingInNextTick() {
|
||||||
|
if (!this._pendingFlag) {
|
||||||
|
this._pendingFlag = true;
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
this._sendPending();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_abortRequest(request) {
|
||||||
|
request._reject(new AbortError());
|
||||||
|
if (request._worker) {
|
||||||
|
request._worker.busy = false;
|
||||||
|
}
|
||||||
|
this._requests.delete(request._message.id);
|
||||||
|
// allow more requests to be aborted before trying to send other pending
|
||||||
|
this._trySendPendingInNextTick();
|
||||||
|
}
|
||||||
|
}
|
|
@ -96,7 +96,7 @@ class MessageHandler {
|
||||||
async _handleMessage(message) {
|
async _handleMessage(message) {
|
||||||
const {type} = message;
|
const {type} = message;
|
||||||
if (type === "ping") {
|
if (type === "ping") {
|
||||||
this._sendReply(message, {type: "pong"});
|
this._sendReply(message, {type: "success"});
|
||||||
} else if (type === "load_olm") {
|
} else if (type === "load_olm") {
|
||||||
this._sendReply(message, await this._loadOlm(message.path));
|
this._sendReply(message, await this._loadOlm(message.path));
|
||||||
} else if (type === "megolm_decrypt") {
|
} else if (type === "megolm_decrypt") {
|
||||||
|
|
Loading…
Reference in a new issue