The demo below shows a single shared value protected by a seqlock. Click Start writer to begin writing new values. Click Read value to attempt a read — the reader snapshots the sequence counter, reads the data, then checks whether the counter changed.
<!-- Seqlock demo — shared value + sequence counter -->
<div class="panel">
<div class="seq-row">
<span class="label">Sequence counter</span>
<span id="seqDisplay" class="seq-val">0</span>
<span id="seqStatus" class="seq-note"></span>
</div>
<div class="data-row">
<span class="label">Shared value</span>
<span id="dataDisplay" class="data-val">—</span>
</div>
</div>
<div class="log-area" id="log" aria-label="Event log"></div>
<div class="btns">
<button id="btnWrite" type="button">Start writer</button>
<button id="btnRead" type="button">Read value</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
/* Seqlock demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.panel { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 10px;
padding: .7rem 1rem; margin-bottom: .6rem; display: flex; flex-direction: column; gap: .35rem; }
.seq-row, .data-row { display: flex; align-items: baseline; gap: .5rem; }
.label { font-size: .8rem; color: #556; min-width: 5rem; }
.seq-val { font: 700 1.6rem ui-monospace, monospace; color: #1d3557; min-width: 2.5rem; }
.seq-note { font-size: .8rem; color: #c92f3c; font-weight: 600; }
.seq-note.ok { color: #0a7d33; }
.data-val { font: 600 1.1rem ui-monospace, monospace; color: #444; }
.log-area { height: 180px; overflow-y: auto; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .4rem .6rem; font-size: .82rem; font-family: ui-monospace, monospace;
background: #f8fafc; margin-bottom: .6rem; }
.log-area p { margin: .15rem 0; line-height: 1.35; }
.log-area p.ok { color: #0a7d33; }
.log-area p.bad { color: #c92f3c; }
.log-area p.info { color: #555; }
.log-area p.write { color: #1d3557; font-weight: 600; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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; }
button:disabled { opacity: .45; cursor: not-allowed; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Seqlock: a sequence counter that lets writers never block on readers.
// Writers increment seq before and after each write (odd=writing, even=stable).
// Readers snapshot seq, read data, snapshot seq again — retry if either check fails.
let seq = 0; // even = stable, odd = write in progress
let sharedData = 0; // the protected shared value
let writerTimer = null;
let writeInterval = 1400; // ms between writer ticks
const seqDisplay = document.getElementById('seqDisplay');
const seqStatus = document.getElementById('seqStatus');
const dataDisplay = document.getElementById('dataDisplay');
const log = document.getElementById('log');
const btnWrite = document.getElementById('btnWrite');
const btnRead = document.getElementById('btnRead');
const btnReset = document.getElementById('btnReset');
function updatePanel() {
seqDisplay.textContent = seq;
// odd counter means write in progress
if (seq % 2 !== 0) {
seqStatus.textContent = '(writing…)';
seqStatus.className = 'seq-note';
} else {
seqStatus.textContent = seq === 0 ? '' : '(stable)';
seqStatus.className = 'seq-note ok';
}
}
function addLog(text, cls) {
const p = document.createElement('p');
p.textContent = text;
p.className = cls || '';
log.appendChild(p);
log.scrollTop = log.scrollHeight;
}
// Writer: bump seq to odd, update data, bump seq to even.
function doWrite() {
seq++; // mark start of write (seq becomes odd)
updatePanel();
const newVal = Math.floor(Math.random() * 900) + 100;
addLog(i18nFmt('WRITE begin (seq=%seq%, value will be %val%)', { seq: seq, val: newVal }), 'write');
// simulate a multi-step write taking ~350 ms
setTimeout(() => {
sharedData = newVal;
dataDisplay.textContent = sharedData;
seq++; // mark end of write (seq becomes even)
updatePanel();
addLog(i18nFmt('WRITE done (seq=%seq%)', { seq: seq }), 'write');
}, 350);
}
// Reader: snapshot-check-snapshot, retry on any mismatch.
function doRead() {
// Step 1 — snapshot counter; abort if write in progress
const seq1 = seq;
if (seq1 % 2 !== 0) {
addLog(i18nFmt('READ retry — counter %seq% is odd (write in progress)', { seq: seq1 }), 'bad');
return;
}
// Step 2 — read the data
const readVal = sharedData;
// Step 3 — snapshot counter again
const seq2 = seq;
if (seq1 !== seq2) {
addLog(i18nFmt('READ retry — counter changed from %seq1% to %seq2% during read', { seq1: seq1, seq2: seq2 }), 'bad');
return;
}
// Both snapshots equal and even — accept the value
addLog(i18nFmt('READ ok — value %val% (seq=%seq%)', { val: readVal, seq: seq1 }), 'ok');
}
function startWriter() {
if (writerTimer) return;
doWrite();
writerTimer = setInterval(doWrite, writeInterval);
btnWrite.textContent = 'Stop writer';
addLog('Writer started.', 'info');
}
function stopWriter() {
clearInterval(writerTimer);
writerTimer = null;
btnWrite.textContent = 'Start writer';
addLog('Writer stopped.', 'info');
}
function reset() {
stopWriter();
seq = 0;
sharedData = 0;
dataDisplay.textContent = '—';
log.innerHTML = '';
updatePanel();
}
btnWrite.addEventListener('click', () => { writerTimer ? stopWriter() : startWriter(); });
btnRead.addEventListener('click', doRead);
btnReset.addEventListener('click', reset);
updatePanel();
<!-- Seqlock demo — shared value + sequence counter -->
<div class="panel">
<div class="seq-row">
<span class="label">Sequence counter</span>
<span id="seqDisplay" class="seq-val">0</span>
<span id="seqStatus" class="seq-note"></span>
</div>
<div class="data-row">
<span class="label">Shared value</span>
<span id="dataDisplay" class="data-val">—</span>
</div>
</div>
<div class="log-area" id="log" aria-label="Event log"></div>
<div class="btns">
<button id="btnWrite" type="button">Start writer</button>
<button id="btnRead" type="button">Read value</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
/* Seqlock demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.panel { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 10px;
padding: .7rem 1rem; margin-bottom: .6rem; display: flex; flex-direction: column; gap: .35rem; }
.seq-row, .data-row { display: flex; align-items: baseline; gap: .5rem; }
.label { font-size: .8rem; color: #556; min-width: 5rem; }
.seq-val { font: 700 1.6rem ui-monospace, monospace; color: #1d3557; min-width: 2.5rem; }
.seq-note { font-size: .8rem; color: #c92f3c; font-weight: 600; }
.seq-note.ok { color: #0a7d33; }
.data-val { font: 600 1.1rem ui-monospace, monospace; color: #444; }
.log-area { height: 180px; overflow-y: auto; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .4rem .6rem; font-size: .82rem; font-family: ui-monospace, monospace;
background: #f8fafc; margin-bottom: .6rem; }
.log-area p { margin: .15rem 0; line-height: 1.35; }
.log-area p.ok { color: #0a7d33; }
.log-area p.bad { color: #c92f3c; }
.log-area p.info { color: #555; }
.log-area p.write { color: #1d3557; font-weight: 600; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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; }
button:disabled { opacity: .45; cursor: not-allowed; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Seqlock: a sequence counter that lets writers never block on readers.
// Writers increment seq before and after each write (odd=writing, even=stable).
// Readers snapshot seq, read data, snapshot seq again — retry if either check fails.
let seq = 0; // even = stable, odd = write in progress
let sharedData = 0; // the protected shared value
let writerTimer = null;
let writeInterval = 1400; // ms between writer ticks
const seqDisplay = document.getElementById('seqDisplay');
const seqStatus = document.getElementById('seqStatus');
const dataDisplay = document.getElementById('dataDisplay');
const log = document.getElementById('log');
const btnWrite = document.getElementById('btnWrite');
const btnRead = document.getElementById('btnRead');
const btnReset = document.getElementById('btnReset');
function updatePanel() {
seqDisplay.textContent = seq;
// odd counter means write in progress
if (seq % 2 !== 0) {
seqStatus.textContent = '(writing…)';
seqStatus.className = 'seq-note';
} else {
seqStatus.textContent = seq === 0 ? '' : '(stable)';
seqStatus.className = 'seq-note ok';
}
}
function addLog(text, cls) {
const p = document.createElement('p');
p.textContent = text;
p.className = cls || '';
log.appendChild(p);
log.scrollTop = log.scrollHeight;
}
// Writer: bump seq to odd, update data, bump seq to even.
function doWrite() {
seq++; // mark start of write (seq becomes odd)
updatePanel();
const newVal = Math.floor(Math.random() * 900) + 100;
addLog(i18nFmt('WRITE begin (seq=%seq%, value will be %val%)', { seq: seq, val: newVal }), 'write');
// simulate a multi-step write taking ~350 ms
setTimeout(() => {
sharedData = newVal;
dataDisplay.textContent = sharedData;
seq++; // mark end of write (seq becomes even)
updatePanel();
addLog(i18nFmt('WRITE done (seq=%seq%)', { seq: seq }), 'write');
}, 350);
}
// Reader: snapshot-check-snapshot, retry on any mismatch.
function doRead() {
// Step 1 — snapshot counter; abort if write in progress
const seq1 = seq;
if (seq1 % 2 !== 0) {
addLog(i18nFmt('READ retry — counter %seq% is odd (write in progress)', { seq: seq1 }), 'bad');
return;
}
// Step 2 — read the data
const readVal = sharedData;
// Step 3 — snapshot counter again
const seq2 = seq;
if (seq1 !== seq2) {
addLog(i18nFmt('READ retry — counter changed from %seq1% to %seq2% during read', { seq1: seq1, seq2: seq2 }), 'bad');
return;
}
// Both snapshots equal and even — accept the value
addLog(i18nFmt('READ ok — value %val% (seq=%seq%)', { val: readVal, seq: seq1 }), 'ok');
}
function startWriter() {
if (writerTimer) return;
doWrite();
writerTimer = setInterval(doWrite, writeInterval);
btnWrite.textContent = 'Stop writer';
addLog('Writer started.', 'info');
}
function stopWriter() {
clearInterval(writerTimer);
writerTimer = null;
btnWrite.textContent = 'Start writer';
addLog('Writer stopped.', 'info');
}
function reset() {
stopWriter();
seq = 0;
sharedData = 0;
dataDisplay.textContent = '—';
log.innerHTML = '';
updatePanel();
}
btnWrite.addEventListener('click', () => { writerTimer ? stopWriter() : startWriter(); });
btnRead.addEventListener('click', doRead);
btnReset.addEventListener('click', reset);
updatePanel();
Notice: when the counter is odd at snapshot time, a write is in progress and the reader immediately retries without using the data. When the two counter snapshots differ, a write completed during the read and the reader retries again. Only when both snapshots are equal and even does the reader accept the value.
Comments
Loading comments...