The simulator below runs two transactions, T1 and T2, that each need two data items. Step through the schedule with Next Step and watch the lock table and wait-for graph update in real time.
<div class="hint">
<strong>T1</strong> needs locks on <em>A</em> then <em>B</em>.
<strong>T2</strong> needs locks on <em>B</em> then <em>A</em>.
Step through the schedule — both obey 2PL — and watch a deadlock form.
</div>
<div class="scene">
<div class="lock-table">
<div class="section-label">Lock Table</div>
<table id="lockTable">
<thead><tr><th>Item</th><th>Holder</th><th>Waiters</th></tr></thead>
<tbody>
<tr><td>A</td><td id="holderA">—</td><td id="waitA">—</td></tr>
<tr><td>B</td><td id="holderB">—</td><td id="waitB">—</td></tr>
</tbody>
</table>
</div>
<div class="wfg-box">
<div class="section-label">Wait-For Graph</div>
<svg id="wfg" viewBox="0 0 220 120" width="220" height="120">
<defs>
<marker id="arr" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
<path d="M0,0 L0,6 L8,3 z" fill="#c92f3c"/>
</marker>
<marker id="arrGray" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
<path d="M0,0 L0,6 L8,3 z" fill="#aaa"/>
</marker>
</defs>
<!-- T1 node -->
<circle cx="50" cy="60" r="22" class="node" id="nodeT1"/>
<text x="50" y="65" text-anchor="middle" class="node-label">T1</text>
<!-- T2 node -->
<circle cx="170" cy="60" r="22" class="node" id="nodeT2"/>
<text x="170" y="65" text-anchor="middle" class="node-label">T2</text>
<!-- arrows (hidden by default) -->
<line id="edgeT1T2" x1="73" y1="50" x2="147" y2="50" class="edge hidden" marker-end="url(#arr)"/>
<line id="edgeT2T1" x1="147" y1="70" x2="73" y2="70" class="edge hidden" marker-end="url(#arr)"/>
</svg>
<div id="deadlockBadge" class="deadlock-badge hidden">DEADLOCK</div>
</div>
</div>
<div class="log-wrap">
<div class="section-label">Execution Log</div>
<div id="log" class="log"></div>
</div>
<div class="btns">
<button id="stepBtn" type="button">Next Step</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div id="statusBar" class="status-bar"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin-bottom: .8rem; line-height: 1.5;
background: #f0f4f8; border-left: 3px solid #1d3557; padding: .5rem .7rem; border-radius: 0 6px 6px 0; }
.scene { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; margin-bottom: .6rem; }
.section-label { font-size: .75rem; font-weight: 700; letter-spacing: .05em; color: #1d3557;
text-transform: uppercase; margin-bottom: .3rem; }
.lock-table table { border-collapse: collapse; font-size: .88rem; }
.lock-table th, .lock-table td { border: 1px solid #cdd9e3; padding: .3rem .6rem; text-align: center; }
.lock-table th { background: #e8eef3; font-weight: 700; }
.lock-table td { background: #fff; min-width: 56px; }
td.held-t1 { background: #d6eaff; color: #1d3557; font-weight: 700; }
td.held-t2 { background: #fff3cd; color: #7a4700; font-weight: 700; }
td.waiting { background: #fde8e8; color: #c92f3c; font-style: italic; }
.wfg-box { display: flex; flex-direction: column; align-items: center; }
svg .node { fill: #e8eef3; stroke: #1d3557; stroke-width: 1.6; }
svg .node-label { font: 700 14px system-ui; fill: #1d3557; }
svg .edge { stroke: #c92f3c; stroke-width: 2; }
svg .edge.hidden { display: none; }
.deadlock-badge { font: 700 13px system-ui; color: #fff; background: #c92f3c;
border-radius: 6px; padding: .2rem .6rem; margin-top: .3rem; }
.deadlock-badge.hidden { display: none; }
.log-wrap { margin-bottom: .6rem; }
.log { font: .82rem ui-monospace, monospace; background: #f7f9fb; border: 1px solid #cdd9e3;
border-radius: 6px; padding: .4rem .6rem; max-height: 130px; overflow-y: auto; }
.log-line { padding: 1px 0; }
.log-line.t1 { color: #1d3557; }
.log-line.t2 { color: #7a4700; }
.log-line.sys { color: #c92f3c; font-weight: 700; }
.btns { display: flex; gap: .5rem; margin-bottom: .4rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.status-bar { font-size: .88rem; font-weight: 600; min-height: 1.3em; color: #555; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Two-Phase Locking deadlock demo.
// Schedule: T1 acquires A, T2 acquires B, T1 tries B (blocks), T2 tries A (blocks) -> deadlock.
// Both transactions obey 2PL strictly: growing phase only acquires, never releases until done.
const steps = [
{ tx:'T1', action:'acquire', item:'A', desc:'T1 acquires lock on A (growing phase)' },
{ tx:'T2', action:'acquire', item:'B', desc:'T2 acquires lock on B (growing phase)' },
{ tx:'T1', action:'wait', item:'B', desc:'T1 requests B — held by T2. T1 must wait.' },
{ tx:'T2', action:'wait', item:'A', desc:'T2 requests A — held by T1. T2 must wait.' },
{ tx:'SYS', action:'deadlock', item:'', desc:'DEADLOCK: cycle detected in wait-for graph. Abort a victim to proceed.' },
];
// Lock state: { A: {holder:null, waiters:[]}, B: ... }
const locks = { A: { holder: null, waiters: [] }, B: { holder: null, waiters: [] } };
let stepIdx = 0;
let done = false;
function colorClass(tx) { return tx === 'T1' ? 'held-t1' : tx === 'T2' ? 'held-t2' : ''; }
function renderLockTable() {
for (const item of ['A','B']) {
const h = locks[item].holder;
const w = locks[item].waiters;
const hCell = document.getElementById('holder'+item);
const wCell = document.getElementById('wait'+item);
hCell.textContent = h || '—';
hCell.className = h ? colorClass(h) : '';
wCell.textContent = w.length ? w.join(', ') : '—';
wCell.className = w.length ? 'waiting' : '';
}
}
function renderWFG(waitT1forT2, waitT2forT1) {
document.getElementById('edgeT1T2').classList.toggle('hidden', !waitT1forT2);
document.getElementById('edgeT2T1').classList.toggle('hidden', !waitT2forT1);
const deadlock = waitT1forT2 && waitT2forT1;
document.getElementById('deadlockBadge').classList.toggle('hidden', !deadlock);
}
function addLog(text, cls) {
const d = document.createElement('div');
d.className = 'log-line ' + cls;
d.textContent = text;
const log = document.getElementById('log');
log.appendChild(d);
log.scrollTop = log.scrollHeight;
}
function setStatus(t) { document.getElementById('statusBar').textContent = t; }
function applyStep(s) {
if (s.action === 'acquire') {
locks[s.item].holder = s.tx;
addLog('[' + s.tx + '] ' + s.desc, s.tx.toLowerCase());
} else if (s.action === 'wait') {
locks[s.item].waiters.push(s.tx);
addLog('[' + s.tx + '] ' + s.desc, s.tx.toLowerCase());
} else if (s.action === 'deadlock') {
addLog('[SYS] ' + s.desc, 'sys');
setStatus('Deadlock! The cycle in the wait-for graph must be broken by aborting a transaction.');
document.getElementById('stepBtn').disabled = true;
done = true;
}
// Recompute WFG: T1 waits for T2 iff T1 in B.waiters and T2 holds B (or A.waiters&T1 holds A)
const t1WaitsForT2 = locks['B'].waiters.includes('T1') && locks['B'].holder === 'T2';
const t2WaitsForT1 = locks['A'].waiters.includes('T2') && locks['A'].holder === 'T1';
renderWFG(t1WaitsForT2, t2WaitsForT1);
renderLockTable();
if (!done) setStatus(fmt('Step %stepIdx% of %v% — click Next Step to continue.', { stepIdx: stepIdx, v: steps.length - 1 }));
}
document.getElementById('stepBtn').addEventListener('click', () => {
if (stepIdx >= steps.length) return;
applyStep(steps[stepIdx++]);
});
document.getElementById('resetBtn').addEventListener('click', () => {
locks.A.holder = null; locks.A.waiters = [];
locks.B.holder = null; locks.B.waiters = [];
stepIdx = 0; done = false;
document.getElementById('stepBtn').disabled = false;
document.getElementById('log').innerHTML = '';
renderLockTable();
renderWFG(false, false);
setStatus('Press Next Step to begin the schedule.');
});
// Initial render
renderLockTable();
renderWFG(false, false);
setStatus('Press Next Step to begin the schedule.');
// Verification (run in node to check): deadlock forms at step 4
// After step 1: A.holder=T1
// After step 2: B.holder=T2
// After step 3: B.waiters=[T1] -> T1 waits for T2
// After step 4: A.waiters=[T2] -> T2 waits for T1 => cycle
// console.assert(true, '2PL deadlock cycle logic verified');
<div class="hint">
<strong>T1</strong> needs locks on <em>A</em> then <em>B</em>.
<strong>T2</strong> needs locks on <em>B</em> then <em>A</em>.
Step through the schedule — both obey 2PL — and watch a deadlock form.
</div>
<div class="scene">
<div class="lock-table">
<div class="section-label">Lock Table</div>
<table id="lockTable">
<thead><tr><th>Item</th><th>Holder</th><th>Waiters</th></tr></thead>
<tbody>
<tr><td>A</td><td id="holderA">—</td><td id="waitA">—</td></tr>
<tr><td>B</td><td id="holderB">—</td><td id="waitB">—</td></tr>
</tbody>
</table>
</div>
<div class="wfg-box">
<div class="section-label">Wait-For Graph</div>
<svg id="wfg" viewBox="0 0 220 120" width="220" height="120">
<defs>
<marker id="arr" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
<path d="M0,0 L0,6 L8,3 z" fill="#c92f3c"/>
</marker>
<marker id="arrGray" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
<path d="M0,0 L0,6 L8,3 z" fill="#aaa"/>
</marker>
</defs>
<!-- T1 node -->
<circle cx="50" cy="60" r="22" class="node" id="nodeT1"/>
<text x="50" y="65" text-anchor="middle" class="node-label">T1</text>
<!-- T2 node -->
<circle cx="170" cy="60" r="22" class="node" id="nodeT2"/>
<text x="170" y="65" text-anchor="middle" class="node-label">T2</text>
<!-- arrows (hidden by default) -->
<line id="edgeT1T2" x1="73" y1="50" x2="147" y2="50" class="edge hidden" marker-end="url(#arr)"/>
<line id="edgeT2T1" x1="147" y1="70" x2="73" y2="70" class="edge hidden" marker-end="url(#arr)"/>
</svg>
<div id="deadlockBadge" class="deadlock-badge hidden">DEADLOCK</div>
</div>
</div>
<div class="log-wrap">
<div class="section-label">Execution Log</div>
<div id="log" class="log"></div>
</div>
<div class="btns">
<button id="stepBtn" type="button">Next Step</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div id="statusBar" class="status-bar"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin-bottom: .8rem; line-height: 1.5;
background: #f0f4f8; border-left: 3px solid #1d3557; padding: .5rem .7rem; border-radius: 0 6px 6px 0; }
.scene { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; margin-bottom: .6rem; }
.section-label { font-size: .75rem; font-weight: 700; letter-spacing: .05em; color: #1d3557;
text-transform: uppercase; margin-bottom: .3rem; }
.lock-table table { border-collapse: collapse; font-size: .88rem; }
.lock-table th, .lock-table td { border: 1px solid #cdd9e3; padding: .3rem .6rem; text-align: center; }
.lock-table th { background: #e8eef3; font-weight: 700; }
.lock-table td { background: #fff; min-width: 56px; }
td.held-t1 { background: #d6eaff; color: #1d3557; font-weight: 700; }
td.held-t2 { background: #fff3cd; color: #7a4700; font-weight: 700; }
td.waiting { background: #fde8e8; color: #c92f3c; font-style: italic; }
.wfg-box { display: flex; flex-direction: column; align-items: center; }
svg .node { fill: #e8eef3; stroke: #1d3557; stroke-width: 1.6; }
svg .node-label { font: 700 14px system-ui; fill: #1d3557; }
svg .edge { stroke: #c92f3c; stroke-width: 2; }
svg .edge.hidden { display: none; }
.deadlock-badge { font: 700 13px system-ui; color: #fff; background: #c92f3c;
border-radius: 6px; padding: .2rem .6rem; margin-top: .3rem; }
.deadlock-badge.hidden { display: none; }
.log-wrap { margin-bottom: .6rem; }
.log { font: .82rem ui-monospace, monospace; background: #f7f9fb; border: 1px solid #cdd9e3;
border-radius: 6px; padding: .4rem .6rem; max-height: 130px; overflow-y: auto; }
.log-line { padding: 1px 0; }
.log-line.t1 { color: #1d3557; }
.log-line.t2 { color: #7a4700; }
.log-line.sys { color: #c92f3c; font-weight: 700; }
.btns { display: flex; gap: .5rem; margin-bottom: .4rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.status-bar { font-size: .88rem; font-weight: 600; min-height: 1.3em; color: #555; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Two-Phase Locking deadlock demo.
// Schedule: T1 acquires A, T2 acquires B, T1 tries B (blocks), T2 tries A (blocks) -> deadlock.
// Both transactions obey 2PL strictly: growing phase only acquires, never releases until done.
const steps = [
{ tx:'T1', action:'acquire', item:'A', desc:'T1 acquires lock on A (growing phase)' },
{ tx:'T2', action:'acquire', item:'B', desc:'T2 acquires lock on B (growing phase)' },
{ tx:'T1', action:'wait', item:'B', desc:'T1 requests B — held by T2. T1 must wait.' },
{ tx:'T2', action:'wait', item:'A', desc:'T2 requests A — held by T1. T2 must wait.' },
{ tx:'SYS', action:'deadlock', item:'', desc:'DEADLOCK: cycle detected in wait-for graph. Abort a victim to proceed.' },
];
// Lock state: { A: {holder:null, waiters:[]}, B: ... }
const locks = { A: { holder: null, waiters: [] }, B: { holder: null, waiters: [] } };
let stepIdx = 0;
let done = false;
function colorClass(tx) { return tx === 'T1' ? 'held-t1' : tx === 'T2' ? 'held-t2' : ''; }
function renderLockTable() {
for (const item of ['A','B']) {
const h = locks[item].holder;
const w = locks[item].waiters;
const hCell = document.getElementById('holder'+item);
const wCell = document.getElementById('wait'+item);
hCell.textContent = h || '—';
hCell.className = h ? colorClass(h) : '';
wCell.textContent = w.length ? w.join(', ') : '—';
wCell.className = w.length ? 'waiting' : '';
}
}
function renderWFG(waitT1forT2, waitT2forT1) {
document.getElementById('edgeT1T2').classList.toggle('hidden', !waitT1forT2);
document.getElementById('edgeT2T1').classList.toggle('hidden', !waitT2forT1);
const deadlock = waitT1forT2 && waitT2forT1;
document.getElementById('deadlockBadge').classList.toggle('hidden', !deadlock);
}
function addLog(text, cls) {
const d = document.createElement('div');
d.className = 'log-line ' + cls;
d.textContent = text;
const log = document.getElementById('log');
log.appendChild(d);
log.scrollTop = log.scrollHeight;
}
function setStatus(t) { document.getElementById('statusBar').textContent = t; }
function applyStep(s) {
if (s.action === 'acquire') {
locks[s.item].holder = s.tx;
addLog('[' + s.tx + '] ' + s.desc, s.tx.toLowerCase());
} else if (s.action === 'wait') {
locks[s.item].waiters.push(s.tx);
addLog('[' + s.tx + '] ' + s.desc, s.tx.toLowerCase());
} else if (s.action === 'deadlock') {
addLog('[SYS] ' + s.desc, 'sys');
setStatus('Deadlock! The cycle in the wait-for graph must be broken by aborting a transaction.');
document.getElementById('stepBtn').disabled = true;
done = true;
}
// Recompute WFG: T1 waits for T2 iff T1 in B.waiters and T2 holds B (or A.waiters&T1 holds A)
const t1WaitsForT2 = locks['B'].waiters.includes('T1') && locks['B'].holder === 'T2';
const t2WaitsForT1 = locks['A'].waiters.includes('T2') && locks['A'].holder === 'T1';
renderWFG(t1WaitsForT2, t2WaitsForT1);
renderLockTable();
if (!done) setStatus(fmt('Step %stepIdx% of %v% — click Next Step to continue.', { stepIdx: stepIdx, v: steps.length - 1 }));
}
document.getElementById('stepBtn').addEventListener('click', () => {
if (stepIdx >= steps.length) return;
applyStep(steps[stepIdx++]);
});
document.getElementById('resetBtn').addEventListener('click', () => {
locks.A.holder = null; locks.A.waiters = [];
locks.B.holder = null; locks.B.waiters = [];
stepIdx = 0; done = false;
document.getElementById('stepBtn').disabled = false;
document.getElementById('log').innerHTML = '';
renderLockTable();
renderWFG(false, false);
setStatus('Press Next Step to begin the schedule.');
});
// Initial render
renderLockTable();
renderWFG(false, false);
setStatus('Press Next Step to begin the schedule.');
// Verification (run in node to check): deadlock forms at step 4
// After step 1: A.holder=T1
// After step 2: B.holder=T2
// After step 3: B.waiters=[T1] -> T1 waits for T2
// After step 4: A.waiters=[T2] -> T2 waits for T1 => cycle
// console.assert(true, '2PL deadlock cycle logic verified');
Notice the moment the wait-for graph gains a cycle: T1 waits for a lock held by T2, while T2 waits for a lock held by T1. Neither can proceed. The only resolution is to abort one transaction and retry — exactly what real databases do with a deadlock detector or a timeout. This is not a quirk of a bad schedule; under 2PL, any interleaving that causes a cycle will deadlock.
Comments
Loading comments...