From 435beae06a8e4dcaf74ea3598a5950d064f61f07 Mon Sep 17 00:00:00 2001 From: Christoph Reiter <reiter.christoph@gmail.com> Date: Thu, 22 Oct 2020 12:44:33 +0200 Subject: [PATCH] Use async-mutex to avoid start/stop being racy With both being async it's hard to follow what would happen if both run at the same time, so just prevent it and serialize start/stopping instead. --- packages/qr-code-scanner/package.json | 1 + .../qr-code-scanner/src/qr-code-scanner.js | 71 +++++++++++-------- yarn.lock | 12 ++++ 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/packages/qr-code-scanner/package.json b/packages/qr-code-scanner/package.json index 885c1ea3..9f3c6527 100644 --- a/packages/qr-code-scanner/package.json +++ b/packages/qr-code-scanner/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@open-wc/scoped-elements": "^1.1.1", + "async-mutex": "^0.2.4", "dbp-common": "^1.0.0", "jsqr": "^1.3.1", "lit-element": "^2.3.1", diff --git a/packages/qr-code-scanner/src/qr-code-scanner.js b/packages/qr-code-scanner/src/qr-code-scanner.js index 3f453caf..bfb785d2 100644 --- a/packages/qr-code-scanner/src/qr-code-scanner.js +++ b/packages/qr-code-scanner/src/qr-code-scanner.js @@ -7,6 +7,7 @@ import {Icon, MiniSpinner} from 'dbp-common'; import {classMap} from 'lit-html/directives/class-map.js'; import jsQR from "jsqr"; import {getIconSVGURL} from 'dbp-common'; +import {Mutex} from 'async-mutex'; /** @@ -118,6 +119,7 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) { this._videoElement = null; this._outputData = null; this._videoRunning = false; + this._lock = new Mutex(); } static get scopedElements() { @@ -143,23 +145,21 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) { }; } - connectedCallback() { + async connectedCallback() { super.connectedCallback(); i18n.changeLanguage(this.lang); - this.updateComplete.then(async ()=>{ - let devices = await getVideoDevices(); - this._activeCamera = getPrimaryDevice(devices) || ''; - this._devices = devices; + let devices = await getVideoDevices(); + this._activeCamera = getPrimaryDevice(devices) || ''; + this._devices = devices; - if (!this.stopScan) { - this.startScanning(); - } - }); + if (!this.stopScan) { + await this.startScanning(); + } } - disconnectedCallback() { - this.stopScanning(); + async disconnectedCallback() { + await this.stopScanning(); super.disconnectedCallback(); } @@ -187,9 +187,19 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) { * Init and start the video and QR code scan */ async startScanning() { - this.stopScanning(); + await this.stopScanning(); - this.stopScan = false; + const release = await this._lock.acquire(); + try { + await this._startScanning(); + } finally { + release(); + } + } + + async _startScanning() { + console.assert(this._lock.isLocked()); + await this.updateComplete; let canvasElement = this._("#canvas"); let firstDrawDone = false; @@ -322,24 +332,29 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) { * */ async stopScanning() { - if (this._videoElement !== null) { - let video = this._videoElement; - video.srcObject.getTracks().forEach(function(track) { - track.stop(); - }); - this._videoElement = null; - } + const release = await this._lock.acquire(); + try { + if (this._videoElement !== null) { + let video = this._videoElement; + video.srcObject.getTracks().forEach(function(track) { + track.stop(); + }); + this._videoElement = null; + } - if (this._requestID !== null) { - cancelAnimationFrame(this._requestID); - this._requestID = null; - } + if (this._requestID !== null) { + cancelAnimationFrame(this._requestID); + this._requestID = null; + } - this._askPermission = false; - this._videoRunning = false; - this._loading = false; + this._askPermission = false; + this._videoRunning = false; + this._loading = false; - this._loadingMessage = ''; + this._loadingMessage = ''; + } finally { + release(); + } } static get styles() { diff --git a/yarn.lock b/yarn.lock index 9dfc3266..7fd1b5ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1603,6 +1603,13 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-mutex@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.2.4.tgz#f6ea5f9cc73147f395f86fa573a2af039fe63082" + integrity sha512-fcQKOXUKMQc57JlmjBCHtkKNrfGpHyR7vu18RfuLfeTAf4hK9PgOadPR5cDrBQ682zasrLUhJFe7EKAHJOduDg== + dependencies: + tslib "^2.0.0" + async@0.9.x: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" @@ -8459,6 +8466,11 @@ tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" + integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" -- GitLab