The demo below shows three tasks sharing one lock. Watch what happens without protection, then toggle priority inheritance and run it again.
<!-- Priority Inversion demo -->
<p class="hint">Three tasks share one mutex. H (high priority) needs the lock that L (low priority) holds. Without inheritance, M (medium) keeps preempting L, stalling H indefinitely. Toggle <b>priority inheritance</b> to see the fix.</p>
<div class="controls">
<label class="toggle-label">
<input type="checkbox" id="inherit-toggle">
Priority inheritance ON
</label>
</div>
<div id="timeline" class="timeline" aria-live="polite"></div>
<div class="status" id="status"></div>
<div class="btns">
<button id="run-btn" type="button">Run simulation</button>
<button id="reset-btn" type="button" class="ghost">Reset</button>
</div>
<div class="legend">
<span class="legend-box high"></span> H running
<span class="legend-box medium"></span> M running
<span class="legend-box low"></span> L running (or boosted)
<span class="legend-box blocked"></span> H blocked
</div>
/* Priority Inversion demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .6rem; line-height: 1.5; }
.controls { margin-bottom: .6rem; }
.toggle-label { display: flex; align-items: center; gap: .45rem; font-size: .9rem;
font-weight: 600; cursor: pointer; user-select: none; }
.toggle-label input { width: 1.1rem; height: 1.1rem; cursor: pointer; }
.timeline { display: flex; flex-direction: column; gap: 6px; margin: .4rem 0 .5rem; min-height: 110px; }
.task-row { display: flex; align-items: center; gap: 6px; }
.task-label { font: 700 13px ui-monospace, monospace; width: 26px; text-align: right; flex-shrink: 0; }
.task-bar-wrap { display: flex; gap: 2px; flex: 1; }
.tick { height: 28px; border-radius: 5px; min-width: 26px; flex: 1;
display: flex; align-items: center; justify-content: center;
font: 600 11px ui-monospace, monospace; color: #fff; transition: background .25s; }
.tick.idle { background: #dde3ea; color: #888; }
.tick.running.high { background: #1d6fa4; }
.tick.running.medium { background: #2a9d5c; }
.tick.running.low { background: #e07e00; }
.tick.blocked { background: #c92f3c; color: #fff; }
.tick.boosted { background: #c97e00; outline: 2px solid #1d6fa4; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .3rem 0 .5rem; }
.status.ok { color: #0a7d33; }
.status.warn { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.legend { display: flex; gap: .7rem; flex-wrap: wrap; font-size: .8rem; align-items: center; }
.legend-box { display: inline-block; width: 14px; height: 14px; border-radius: 3px; }
.legend-box.high { background: #1d6fa4; }
.legend-box.medium { background: #2a9d5c; }
.legend-box.low { background: #e07e00; }
.legend-box.blocked { background: #c92f3c; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simulate priority inversion with 3 tasks and 1 shared mutex.
// H=high priority, M=medium priority, L=low priority
// L acquires the lock at the start and holds it for several ticks.
const TICKS = 14; // total simulation ticks
// Run a step-by-step tick simulation.
// Returns a timeline map of states per task per tick.
function simulate(inherit) {
// Track lock holder and boost state
// lock: null or 'L' (only L acquires it in this scenario)
let lockHolder = null;
let lockBoosted = false;
const timeline = { H: [], M: [], L: [] };
// Task definitions with arrival times and work amounts
// Each task: { arrives, workLeft, blocked, done, boosted }
// H: arrives at tick 2, needs lock, 3 ticks work total
// M: arrives at tick 3, no lock, 5 ticks work total
// L: arrives at tick 0, holds lock for 6 ticks, then 2 more ticks
const tasks = {
H: { arrives: 2, workLeft: 3, needsLock: true, blocked: false, done: false, priority: 3 },
M: { arrives: 3, workLeft: 5, needsLock: false, blocked: false, done: false, priority: 2 },
L: { arrives: 0, workLeft: 8, needsLock: true, blocked: false, done: false, priority: 1, lockReleaseAt: 6 }
};
let lLockReleased = false;
let lWorkDone = 0;
for (let t = 0; t < TICKS; t++) {
// Per-tick simulation
// Determine effective priorities (with inheritance)
const effPri = { H: 3, M: 2, L: 1 };
if (inherit && lockHolder === 'L' && !tasks.H.done && tasks.H.blocked) {
effPri.L = 3; // raise L to H priority via inheritance
lockBoosted = true;
} else {
lockBoosted = false;
}
// Pick the highest-priority runnable task
const candidates = ['H', 'M', 'L'].filter(name => {
const tk = tasks[name];
return !tk.done && tk.arrives <= t && !tk.blocked;
});
candidates.sort((a, b) => effPri[b] - effPri[a]);
const running = candidates[0] || null;
// Record state for each task this tick
for (const name of ['H', 'M', 'L']) {
const tk = tasks[name];
let state;
if (tk.done || tk.arrives > t) {
state = 'idle';
} else if (tk.blocked) {
state = 'blocked';
} else if (name === running) {
state = (name === 'L' && lockBoosted) ? 'boosted' : 'running';
} else {
state = 'idle';
}
timeline[name].push(state);
}
// Advance work for the running task
if (running === 'L') {
lWorkDone++;
// L releases the lock after its critical section
if (!lLockReleased && lWorkDone >= tasks.L.lockReleaseAt) {
lLockReleased = true;
lockHolder = null;
tasks.H.blocked = false; // H can now acquire the lock
}
if (lWorkDone >= tasks.L.workLeft) tasks.L.done = true;
}
if (running === 'M') { tasks.M.workLeft--; if (tasks.M.workLeft <= 0) tasks.M.done = true; }
if (running === 'H') { tasks.H.workLeft--; if (tasks.H.workLeft <= 0) tasks.H.done = true; }
// H tries to acquire the lock when it arrives
if (!tasks.H.done && tasks.H.arrives <= t && !tasks.H.blocked) {
if (!lLockReleased) {
tasks.H.blocked = true; // H blocked waiting for L to release the mutex
lockHolder = 'L';
}
}
// L acquires the lock if free at arrival
if (!lLockReleased && lockHolder === null && tasks.L.arrives <= t) {
lockHolder = 'L';
}
}
return timeline;
}
function render(timeline, inherit) {
const wrap = document.getElementById('timeline');
wrap.innerHTML = '';
const labels = { H: 'H', M: 'M', L: 'L' };
for (const name of ['H', 'M', 'L']) {
const row = document.createElement('div');
row.className = 'task-row';
const lbl = document.createElement('div');
lbl.className = 'task-label';
lbl.textContent = labels[name];
row.appendChild(lbl);
const barWrap = document.createElement('div');
barWrap.className = 'task-bar-wrap';
timeline[name].forEach((state, i) => {
const tick = document.createElement('div');
const taskClass = name === 'H' ? 'high' : name === 'M' ? 'medium' : 'low';
if (state === 'idle') {
tick.className = 'tick idle';
} else if (state === 'blocked') {
tick.className = 'tick blocked';
tick.textContent = '✕';
} else if (state === 'boosted') {
tick.className = 'tick boosted';
tick.textContent = '↑';
tick.title = 'L boosted to H priority';
} else {
tick.className = `tick running ${taskClass}`;
tick.textContent = name;
}
barWrap.appendChild(tick);
});
row.appendChild(barWrap);
wrap.appendChild(row);
}
// Show inversion summary
const hBlocks = timeline.H.filter(s => s === 'blocked').length;
const statusEl = document.getElementById('status');
if (hBlocks === 0) {
statusEl.textContent = 'No inversion — H was never blocked by low-priority work.';
statusEl.className = 'status ok';
} else {
statusEl.textContent = i18nFmt('Priority inversion: H was blocked for %n% tick(s) by lower-priority tasks.', { n: hBlocks });
statusEl.className = 'status warn';
}
}
function run() {
const inherit = document.getElementById('inherit-toggle').checked;
const timeline = simulate(inherit);
render(timeline, inherit);
}
document.getElementById('run-btn').addEventListener('click', run);
document.getElementById('reset-btn').addEventListener('click', () => {
document.getElementById('timeline').innerHTML = '';
document.getElementById('status').textContent = '';
document.getElementById('status').className = 'status';
document.getElementById('inherit-toggle').checked = false;
});
// Auto-run once on load
run();
<!-- Priority Inversion demo -->
<p class="hint">Three tasks share one mutex. H (high priority) needs the lock that L (low priority) holds. Without inheritance, M (medium) keeps preempting L, stalling H indefinitely. Toggle <b>priority inheritance</b> to see the fix.</p>
<div class="controls">
<label class="toggle-label">
<input type="checkbox" id="inherit-toggle">
Priority inheritance ON
</label>
</div>
<div id="timeline" class="timeline" aria-live="polite"></div>
<div class="status" id="status"></div>
<div class="btns">
<button id="run-btn" type="button">Run simulation</button>
<button id="reset-btn" type="button" class="ghost">Reset</button>
</div>
<div class="legend">
<span class="legend-box high"></span> H running
<span class="legend-box medium"></span> M running
<span class="legend-box low"></span> L running (or boosted)
<span class="legend-box blocked"></span> H blocked
</div>
/* Priority Inversion demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .6rem; line-height: 1.5; }
.controls { margin-bottom: .6rem; }
.toggle-label { display: flex; align-items: center; gap: .45rem; font-size: .9rem;
font-weight: 600; cursor: pointer; user-select: none; }
.toggle-label input { width: 1.1rem; height: 1.1rem; cursor: pointer; }
.timeline { display: flex; flex-direction: column; gap: 6px; margin: .4rem 0 .5rem; min-height: 110px; }
.task-row { display: flex; align-items: center; gap: 6px; }
.task-label { font: 700 13px ui-monospace, monospace; width: 26px; text-align: right; flex-shrink: 0; }
.task-bar-wrap { display: flex; gap: 2px; flex: 1; }
.tick { height: 28px; border-radius: 5px; min-width: 26px; flex: 1;
display: flex; align-items: center; justify-content: center;
font: 600 11px ui-monospace, monospace; color: #fff; transition: background .25s; }
.tick.idle { background: #dde3ea; color: #888; }
.tick.running.high { background: #1d6fa4; }
.tick.running.medium { background: #2a9d5c; }
.tick.running.low { background: #e07e00; }
.tick.blocked { background: #c92f3c; color: #fff; }
.tick.boosted { background: #c97e00; outline: 2px solid #1d6fa4; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .3rem 0 .5rem; }
.status.ok { color: #0a7d33; }
.status.warn { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.legend { display: flex; gap: .7rem; flex-wrap: wrap; font-size: .8rem; align-items: center; }
.legend-box { display: inline-block; width: 14px; height: 14px; border-radius: 3px; }
.legend-box.high { background: #1d6fa4; }
.legend-box.medium { background: #2a9d5c; }
.legend-box.low { background: #e07e00; }
.legend-box.blocked { background: #c92f3c; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simulate priority inversion with 3 tasks and 1 shared mutex.
// H=high priority, M=medium priority, L=low priority
// L acquires the lock at the start and holds it for several ticks.
const TICKS = 14; // total simulation ticks
// Run a step-by-step tick simulation.
// Returns a timeline map of states per task per tick.
function simulate(inherit) {
// Track lock holder and boost state
// lock: null or 'L' (only L acquires it in this scenario)
let lockHolder = null;
let lockBoosted = false;
const timeline = { H: [], M: [], L: [] };
// Task definitions with arrival times and work amounts
// Each task: { arrives, workLeft, blocked, done, boosted }
// H: arrives at tick 2, needs lock, 3 ticks work total
// M: arrives at tick 3, no lock, 5 ticks work total
// L: arrives at tick 0, holds lock for 6 ticks, then 2 more ticks
const tasks = {
H: { arrives: 2, workLeft: 3, needsLock: true, blocked: false, done: false, priority: 3 },
M: { arrives: 3, workLeft: 5, needsLock: false, blocked: false, done: false, priority: 2 },
L: { arrives: 0, workLeft: 8, needsLock: true, blocked: false, done: false, priority: 1, lockReleaseAt: 6 }
};
let lLockReleased = false;
let lWorkDone = 0;
for (let t = 0; t < TICKS; t++) {
// Per-tick simulation
// Determine effective priorities (with inheritance)
const effPri = { H: 3, M: 2, L: 1 };
if (inherit && lockHolder === 'L' && !tasks.H.done && tasks.H.blocked) {
effPri.L = 3; // raise L to H priority via inheritance
lockBoosted = true;
} else {
lockBoosted = false;
}
// Pick the highest-priority runnable task
const candidates = ['H', 'M', 'L'].filter(name => {
const tk = tasks[name];
return !tk.done && tk.arrives <= t && !tk.blocked;
});
candidates.sort((a, b) => effPri[b] - effPri[a]);
const running = candidates[0] || null;
// Record state for each task this tick
for (const name of ['H', 'M', 'L']) {
const tk = tasks[name];
let state;
if (tk.done || tk.arrives > t) {
state = 'idle';
} else if (tk.blocked) {
state = 'blocked';
} else if (name === running) {
state = (name === 'L' && lockBoosted) ? 'boosted' : 'running';
} else {
state = 'idle';
}
timeline[name].push(state);
}
// Advance work for the running task
if (running === 'L') {
lWorkDone++;
// L releases the lock after its critical section
if (!lLockReleased && lWorkDone >= tasks.L.lockReleaseAt) {
lLockReleased = true;
lockHolder = null;
tasks.H.blocked = false; // H can now acquire the lock
}
if (lWorkDone >= tasks.L.workLeft) tasks.L.done = true;
}
if (running === 'M') { tasks.M.workLeft--; if (tasks.M.workLeft <= 0) tasks.M.done = true; }
if (running === 'H') { tasks.H.workLeft--; if (tasks.H.workLeft <= 0) tasks.H.done = true; }
// H tries to acquire the lock when it arrives
if (!tasks.H.done && tasks.H.arrives <= t && !tasks.H.blocked) {
if (!lLockReleased) {
tasks.H.blocked = true; // H blocked waiting for L to release the mutex
lockHolder = 'L';
}
}
// L acquires the lock if free at arrival
if (!lLockReleased && lockHolder === null && tasks.L.arrives <= t) {
lockHolder = 'L';
}
}
return timeline;
}
function render(timeline, inherit) {
const wrap = document.getElementById('timeline');
wrap.innerHTML = '';
const labels = { H: 'H', M: 'M', L: 'L' };
for (const name of ['H', 'M', 'L']) {
const row = document.createElement('div');
row.className = 'task-row';
const lbl = document.createElement('div');
lbl.className = 'task-label';
lbl.textContent = labels[name];
row.appendChild(lbl);
const barWrap = document.createElement('div');
barWrap.className = 'task-bar-wrap';
timeline[name].forEach((state, i) => {
const tick = document.createElement('div');
const taskClass = name === 'H' ? 'high' : name === 'M' ? 'medium' : 'low';
if (state === 'idle') {
tick.className = 'tick idle';
} else if (state === 'blocked') {
tick.className = 'tick blocked';
tick.textContent = '✕';
} else if (state === 'boosted') {
tick.className = 'tick boosted';
tick.textContent = '↑';
tick.title = 'L boosted to H priority';
} else {
tick.className = `tick running ${taskClass}`;
tick.textContent = name;
}
barWrap.appendChild(tick);
});
row.appendChild(barWrap);
wrap.appendChild(row);
}
// Show inversion summary
const hBlocks = timeline.H.filter(s => s === 'blocked').length;
const statusEl = document.getElementById('status');
if (hBlocks === 0) {
statusEl.textContent = 'No inversion — H was never blocked by low-priority work.';
statusEl.className = 'status ok';
} else {
statusEl.textContent = i18nFmt('Priority inversion: H was blocked for %n% tick(s) by lower-priority tasks.', { n: hBlocks });
statusEl.className = 'status warn';
}
}
function run() {
const inherit = document.getElementById('inherit-toggle').checked;
const timeline = simulate(inherit);
render(timeline, inherit);
}
document.getElementById('run-btn').addEventListener('click', run);
document.getElementById('reset-btn').addEventListener('click', () => {
document.getElementById('timeline').innerHTML = '';
document.getElementById('status').textContent = '';
document.getElementById('status').className = 'status';
document.getElementById('inherit-toggle').checked = false;
});
// Auto-run once on load
run();
Without inheritance the medium-priority task (M) keeps preempting the low-priority lock holder (L), so the high-priority task (H) waits far longer than its priority warrants. With inheritance, the moment H blocks on the lock, L is temporarily lifted to H's priority — M can no longer preempt it, L finishes quickly, and H resumes without the anomaly.
Comments
Loading comments...