var state = {
isMutexBroken: false,
counter: 0,
worker1LastAction: '',
worker2LastAction: '',
worker1IsActive: false,
worker2IsActive: false,
}
class Mutex {
constructor() {
this.current = Promise.resolve();
}
async acquire() {
if (state.isMutexBroken) {
return () => {};
}
let release;
const next = new Promise(resolve => {
release = () => {
resolve();
};
});
const waiter = this.current.then(() => release);
this.current = next;
return await waiter;
}
}
var mutex = new Mutex();
const renderState = () => {
document.getElementById('mutex-status').textContent = state.isMutexBroken ? 'Mutex is *not* working correctly. Press "fix mutex" to fix it.' : 'Mutex is working correctly. Press "break mutex" to break it.';
document.getElementById('counter').textContent = `Counter value: ${state.counter}`;
document.getElementById('worker1').textContent = `Worker 1 - last action: ${state.worker1LastAction}`;
document.getElementById('worker2').textContent = `Worker 2 - last action: ${state.worker2LastAction}`;
document.getElementById('start-test').disabled = state.worker1IsActive || state.worker2IsActive;
document.getElementById('break-mutex').disabled = state.worker1IsActive || state.worker2IsActive;
document.getElementById('fix-mutex').disabled = state.worker1IsActive || state.worker2IsActive;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const worker = async(delay, count, id) => {
state[`${id}IsActive`] = true;
let workerCopyOfCounter;
for (let i = 0; i < count; i++) {
const unlock = await mutex.acquire();
state[`${id}LastAction`] = `Aquired lock.`;
renderState();
await sleep(delay);
workerCopyOfCounter = state.counter;
state[`${id}LastAction`] = `Acquired global counter: ${workerCopyOfCounter}`;
renderState();
await sleep(delay);
workerCopyOfCounter++;
state[`${id}LastAction`] = `Incremented counter: ${workerCopyOfCounter}`;
renderState();
await sleep(delay);
state.counter = workerCopyOfCounter;
state[`${id}LastAction`] = `Wrote ${workerCopyOfCounter} back to global counter.`;
renderState();
await sleep(delay);
unlock();
state[`${id}LastAction`] = `Released lock.`;
renderState();
await sleep(delay);
}
state[`${id}LastAction`] = `Finished.`;
state[`${id}IsActive`] = false;
renderState();
}
document.getElementById('break-mutex').onclick = () => {
state.isMutexBroken = true;
renderState();
}
document.getElementById('fix-mutex').onclick = () => {
state.isMutexBroken = false;
renderState();
}
document.getElementById('start-test').onclick = () => {
document.getElementById('test-result').textContent = '';
document.getElementById('start-test').textContent = 'Reset and start test';
state.counter = 0;
state.worker1LastAction = '';
state.worker2LastAction = '';
renderState();
const slow = document.getElementById('slow').checked;
const multiplier = slow ? 10 : 1;
Promise.all([
worker(20 * multiplier, 10, 'worker1'),
worker(55 * multiplier, 5, 'worker2')
]).then(() => {
const elem = document.getElementById('test-result');
elem.classList.remove('pass');
elem.classList.remove('fail');
elem.classList.add(state.counter === 15 ? 'pass' : 'fail');
elem.textContent = state.counter === 15 ? 'Test passed' : 'Test failed';
});
}
renderState();
.flex-column {
display: flex;
flex-direction: column;
}
.flex-row {
display: flex;
}
.top-padding {
padding-top: 8px;
}
.worker-state-container {
background-color: #0001;
margin-top: 8px;
padding: 5px;
}
.pass {
background-color: limegreen;
color: white;
}
.fail {
background-color: red;
color: white;
}
<div class="flex-column">
<div className="flex-row">
<button id="break-mutex">Break mutex</button>
<button id="fix-mutex">Fix mutex</button>
<div id="mutex-status"></div>
</div>
<div className="flex-row">
<input type="checkbox" id="slow" name="slow"><label for="slow">slow</label>
</div>
<div class="flex-row top-padding">
<button id="start-test">Start test</button>
</div>
<div id="counter"></div>
<div>Expected end value: 15</div>
<div id="test-result"></div>
<div class="top-padding">
<div id="worker1" class="worker-state-container">
</div>
<div id="worker2" class="worker-state-container">
</div>
</div>
</div>