Browse Source

bugfix: #401 Duplicate lines in output (#623)

James Read 11 months ago
parent
commit
af75afa82f
4 changed files with 75 additions and 18 deletions
  1. 8 12
      webui.dev/js/ExecutionDialog.js
  2. 24 0
      webui.dev/js/Mutex.js
  3. 43 4
      webui.dev/js/OutputTerminal.js
  4. 0 2
      webui.dev/js/marshaller.js

+ 8 - 12
webui.dev/js/ExecutionDialog.js

@@ -28,24 +28,20 @@ export class ExecutionDialog {
 
     window.terminal = new OutputTerminal()
     window.terminal.open(this.domOutput)
-  }
-
-  showOutput () {
-    this.domOutput.hidden = false
-    this.domOutput.hidden = false
+    window.terminal.resize(80, 24)
   }
 
   toggleSize () {
     if (this.dlg.classList.contains('big')) {
       this.dlg.classList.remove('big')
+      window.terminal.resize(80, 24)
     } else {
       this.dlg.classList.add('big')
+      window.terminal.fit()
     }
-
-    window.terminal.fit()
   }
 
-  reset () {
+  async reset () {
     this.executionSeconds = 0
     this.executionTrackingId = 'notset'
 
@@ -69,7 +65,7 @@ export class ExecutionDialog {
 
     this.domExecutionDetails.hidden = true
 
-    window.terminal.reset()
+    await window.terminal.reset()
     window.terminal.fit()
   }
 
@@ -193,7 +189,7 @@ export class ExecutionDialog {
     }
   }
 
-  renderExecutionResult (res) {
+  async renderExecutionResult (res) {
     this.res = res
 
     clearInterval(window.executionDialogTicker)
@@ -229,8 +225,8 @@ export class ExecutionDialog {
 
     this.updateDuration(res.logEntry)
 
-    window.terminal.reset()
-    window.terminal.write(res.logEntry.output, () => {
+    await window.terminal.reset()
+    await window.terminal.write(res.logEntry.output, () => {
       window.terminal.fit()
     })
   }

+ 24 - 0
webui.dev/js/Mutex.js

@@ -0,0 +1,24 @@
+export class Mutex {
+  constructor() {
+    this._locked = false;
+    this._waiting = [];
+  }
+
+  lock() {
+    const unlock = () => {
+      const next = this._waiting.shift();
+      if (next) {
+        next(unlock);
+      } else {
+        this._locked = false;
+      }
+    };
+
+    if (this._locked) {
+      return new Promise(resolve => this._waiting.push(resolve)).then(() => unlock);
+    } else {
+      this._locked = true;
+      return Promise.resolve(unlock);
+    }
+  }
+}

+ 43 - 4
webui.dev/js/OutputTerminal.js

@@ -1,8 +1,22 @@
 import { Terminal } from '@xterm/xterm'
 import { FitAddon } from '@xterm/addon-fit'
+import { Mutex } from './Mutex.js'
 
+/** 
+ * xterm.js based terminal output for the execution dialog.
+ *
+ * the xterm.js methods for write(), reset() and clear() appear to be async,
+ * but they do not return a Promise and instead use a callback. When calling
+ * these methods in quick succession, the output can get garbled due to race 
+ * conditions. 
+ *
+ * To avoid this, this class uses Mutex around those methods to ensure that 
+ * only one write OR reset is executed at a time, is completed, and the calls
+ * occour in sequential order.
+ */
 export class OutputTerminal {
   constructor () {
+    this.writeMutex = new Mutex()
     this.terminal = new Terminal({
       convertEol: true
     })
@@ -12,8 +26,33 @@ export class OutputTerminal {
     this.terminal.fit = fitAddon
   }
 
-  write (out, then) {
-    this.terminal.write(out, then)
+  async write (out, then) {
+    const unlock = await this.writeMutex.lock()
+
+    try {
+      await new Promise(resolve => {
+        this.terminal.write(out, () => {
+          resolve()
+        })
+      })
+    } finally {
+      unlock()
+      then()
+    }
+  }
+
+  async reset () {
+    const unlock = await this.writeMutex.lock()
+
+    try {
+      await new Promise(resolve => {
+        this.terminal.clear()
+        this.terminal.reset()
+        resolve()
+      })
+    } finally {
+      unlock()
+    }
   }
 
   fit () {
@@ -24,7 +63,7 @@ export class OutputTerminal {
     this.terminal.open(el)
   }
 
-  reset () {
-    this.terminal.reset()
+  resize (cols, rows) {
+    this.terminal.resize(cols, rows)
   }
 }

+ 0 - 2
webui.dev/js/marshaller.js

@@ -170,8 +170,6 @@ function onOutputChunk (evt) {
 
   if (chunk.executionTrackingId === window.executionDialog.executionTrackingId) {
     window.terminal.write(chunk.output)
-
-    window.executionDialog.showOutput()
   }
 }