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