UNPKG

@itwin/core-backend

Version:
159 lines • 7.56 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { assert, expect } from "chai"; import { BeDuration } from "@itwin/core-bentley"; import { Matrix4d } from "@itwin/core-geometry"; import { IModelNative } from "../../internal/NativePlatform"; import { StandaloneDb } from "../../IModelDb"; import { IModelTestUtils } from "../IModelTestUtils"; import { _nativeDb } from "../../internal/Symbols"; describe("DgnDbWorker", () => { let imodel; function openIModel() { const rootSubject = { name: "DgnDbWorker tests", description: "DgnDbWorker tests" }; imodel = StandaloneDb.createEmpty(IModelTestUtils.prepareOutputFile("DgnDbWorker", "DgnDbWorker.bim"), { rootSubject }); } before(() => { openIModel(); }); after(() => { imodel.close(); }); class Worker { promise; _worker; constructor() { this._worker = new IModelNative.platform.TestWorker(imodel[_nativeDb]); } queue() { this.promise = this._worker.queue(); } cancel() { this._worker.cancel(); } setReady() { this._worker.setReady(); } setThrow() { this._worker.setThrow(); } get isCanceled() { return this._worker.isCanceled(); } get wasExecuted() { return this._worker.wasExecuted(); } get state() { return this._worker.getState(); } get wasQueued() { return 0 /* IModelJsNative.TestWorkerState.NotQueued */ !== this.state; } get isQueued() { return 1 /* IModelJsNative.TestWorkerState.Queued */ === this.state; } get isRunning() { return 3 /* IModelJsNative.TestWorkerState.Running */ === this.state; } get isError() { return 5 /* IModelJsNative.TestWorkerState.Error */ === this.state; } get isOk() { return 4 /* IModelJsNative.TestWorkerState.Ok */ === this.state; } get isSkipped() { return 2 /* IModelJsNative.TestWorkerState.Skipped */ === this.state; } get isAborted() { return 6 /* IModelJsNative.TestWorkerState.Aborted */ === this.state; } } async function waitUntil(condition) { if (condition()) return; await new Promise((resolve) => setTimeout(resolve, 100)); return waitUntil(condition); } it("executes asynchronously", async () => { const worker = new Worker(); await BeDuration.wait(1000); expect(worker.wasQueued).to.be.false; expect(worker.isCanceled).to.be.false; worker.queue(); expect(worker.isQueued || worker.isRunning).to.be.true; worker.setReady(); await worker.promise; expect(worker.wasExecuted).to.be.true; expect(worker.isCanceled).to.be.false; expect(worker.isOk).to.be.true; }); it("executes a maximum of 4 simultaneously", async () => { // Create 4 workers. They should all start executing. const first = [new Worker(), new Worker(), new Worker(), new Worker()]; for (const worker of first) worker.queue(); await waitUntil(() => first.every((x) => x.isRunning)); // Queue up 2 more workers. They should not start executing until at least one of the first 4 resolves. const next = [new Worker(), new Worker()]; for (const worker of next) worker.queue(); await BeDuration.wait(1000); expect(first.every((x) => x.isRunning)).to.be.true; expect(next.every((x) => x.isQueued)).to.be.true; // Let the first worker resolve. first[0].setReady(); await first[0].promise; expect(first[0].isOk).to.be.true; // Exactly one of the 2 workers remaining in the queue should start executing. await waitUntil(() => next.some((x) => x.isRunning)); await BeDuration.wait(1000); expect(next.every((x) => x.isRunning)).to.be.false; // Clear the queue. const workers = first.concat(next); for (const worker of workers) worker.setReady(); await Promise.all(workers.map((x) => x.promise).filter((p) => p !== undefined)); // eslint-disable-line @typescript-eslint/promise-function-async expect(workers.every((x) => x.isOk)).to.be.true; }); it("skips execution if canceled before execution begins", async () => { const worker = new Worker(); worker.cancel(); expect(worker.isCanceled).to.be.true; worker.queue(); await assert.isRejected(worker.promise, "canceled"); expect(worker.isSkipped).to.be.true; expect(worker.wasExecuted).to.be.false; }); it("executes if canceled after execution begins", async () => { const worker = new Worker(); worker.queue(); await waitUntil(() => worker.isRunning); worker.cancel(); worker.setReady(); await worker.promise; expect(worker.isCanceled).to.be.true; expect(worker.isSkipped).to.be.false; expect(worker.wasExecuted).to.be.true; }); it("throws", async () => { const worker = new Worker(); worker.setThrow(); worker.queue(); await assert.isRejected(worker.promise, "throw"); expect(worker.isCanceled).to.be.false; expect(worker.isError).to.be.true; }); it("cancels all workers before iModel is closed", async () => { const resolve = new Worker(); resolve.setReady(); const reject = new Worker(); reject.setThrow(); // These 6 workers will never resolve nor reject explicitly. const cancel = [new Worker(), new Worker(), new Worker(), new Worker(), new Worker(), new Worker()]; // Queue up all the workers. const workers = cancel.concat([resolve, reject]); for (const worker of workers) worker.queue(); // Closing the iModel cancels all extant workers. imodel.close(); openIModel(); await expect(Promise.all(workers.map(async (x) => x.promise))).rejectedWith("canceled"); expect(cancel.every((x) => x.isCanceled)).to.be.true; expect(cancel.every((x) => x.isAborted || x.isSkipped)).to.be.true; expect(resolve.isOk || resolve.isSkipped || resolve.isAborted).to.be.true; expect(reject.isError || reject.isSkipped).to.be.true; }); it("cancels a snap request", async () => { // Block the worker thread pool with 4 workers so that our snap cannot complete before they complete. const blockers = [new Worker(), new Worker(), new Worker(), new Worker()]; blockers.forEach((w) => w.queue()); const sessionId = "0x222"; const snap = imodel.requestSnap(sessionId, { testPoint: { x: 1, y: 2, z: 3 }, closePoint: { x: 1, y: 2, z: 3 }, id: "0x111", worldToView: Matrix4d.createIdentity().toJSON(), }); const toBeRejected = expect(snap).to.be.rejectedWith("aborted"); imodel.cancelSnap(sessionId); // Clear the worker thread pool so the snap request (now canceled) can be processed. blockers.forEach((w) => w.setReady()); await Promise.all(blockers.map(async (w) => w.promise).filter((p) => p !== undefined)); await toBeRejected; }); }); //# sourceMappingURL=DgnDbWorker.test.js.map