Skip to content

Commit e6584a1

Browse files
committed
fix: possible isRunning async race condition
1 parent ab2ebd3 commit e6584a1

File tree

1 file changed

+49
-14
lines changed

1 file changed

+49
-14
lines changed

wasm/grol_wasm.html

+49-14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!doctype html><html><head><meta charset="utf-8"><title>Grol</title></head>
12
<!--
23
Keep grol/wasm/grol_wasm.html and web-site/_includes/grol_wasm.html in sync
34
Using `make sync` in web-site/
@@ -38,12 +39,46 @@
3839
</style>
3940
<script src="wasm_exec.js"></script>
4041
<script>
41-
function debounce(func) {
42+
/**
43+
* A singular task ES6++ mutex
44+
* inspired by this https://blog.jcoglan.com/2016/07/12/mutexes-and-javascript/
45+
*/
46+
class Lock {
47+
#isLocked = false
48+
#queue = []
49+
constructor(){}
50+
51+
sync(task) {
52+
console.log("SYNC")
53+
if (this.#isLocked) {
54+
console.log("Lock already has a task, ommiting another one at " + new Date().toString())
55+
return
56+
}
57+
58+
this.#queue.push(task)
59+
if (!this.#isLocked) this.#dequeue()
60+
}
61+
62+
#dequeue() {
63+
this.#isLocked = true
64+
const next = this.#queue.shift()
65+
66+
if (next) this.#execute(next)
67+
else this.#isLocked = false
68+
}
69+
70+
async #execute(task) {
71+
const thisDequeue = () => this.#dequeue()
72+
task().then(thisDequeue, thisDequeue)
73+
}
74+
}
75+
76+
function debounce(func, ms = 100) { // 100ms debounce by default
4277
let timeout
4378
return function (...args) {
4479
const context = this
4580
clearTimeout(timeout)
46-
timeout = setTimeout(() => func.apply(context, args), 100) // 100ms debounce
81+
timeout = setTimeout(() => func.apply(context, args), ms)
4782
}
4883
}
4984
if (!WebAssembly.instantiateStreaming) { // polyfill
@@ -68,10 +103,8 @@
68103
function formatError(error) {
69104
return `Error: ${error.message}`;
70105
}
71-
let isRunning = false
72-
async function run() {
73-
if (isRunning) return; // Prevent running multiple times concurrently
74-
isRunning = true;
106+
107+
async function _run() {
75108
document.getElementById("runButton").disabled = true; // Disable button during execution
76109
try {
77110
// console.clear();
@@ -105,20 +138,21 @@
105138
} finally {
106139
inst = await WebAssembly.instantiate(mod, go.importObject)
107140
console.log('Instance reset:', inst)
108-
if (isRunning) {
109-
isRunning = false; // Allow running again after reset
110-
document.getElementById("runButton").disabled = false; // Re-enable the button
111-
}
141+
document.getElementById("runButton").disabled = false; // Re-enable the button
112142
}
113143
resizeTextarea(document.getElementById('input'));
114144
resizeTextarea(document.getElementById('output'));
115145
resizeTextarea(document.getElementById('errors'));
116146
}
117-
const debounceRun = debounce(run)
147+
148+
149+
const lock = new Lock()
150+
const run = debounce(() => lock.sync(_run), 100)
151+
118152
document.addEventListener('DOMContentLoaded', (event) => {
119153
document.getElementById('input').addEventListener('keydown', function (e) {
120-
if (e.key === 'Enter' && !isRunning) {
121-
debounceRun();
154+
if (e.key === 'Enter') {
155+
run()
122156
}
123157
});
124158
});
@@ -142,7 +176,7 @@
142176
m={"str key": a, PI: "pi", 42: "str val", 1e3: "a lot"}</textarea>
143177
</div>
144178
<div>
145-
Hit enter or click <button onClick="debounceRun()" id="runButton" disabled>Run</button> (will also format the code,
179+
Hit enter or click <button onClick="run()" id="runButton" disabled>Run</button> (will also format the code,
146180
also
147181
try <input type="checkbox" id="compact">compact)
148182
<button id="addParamButton">Share</button>
@@ -185,3 +219,4 @@
185219
document.getElementById('input').value = decodeURIComponent(paramValue)
186220
}
187221
</script>
222+
</html>

0 commit comments

Comments
 (0)