From d18ddf56815c61737388c76324e98ad7a001ffb2 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 29 Apr 2021 00:26:31 -0700 Subject: [PATCH] fix(swingset): do not record GC syscalls in the transcript Consensus mode will depend upon GC being deterministic, but solo mode does not. Solo mode requires GC be "sufficiently deterministic", which means a finalizer may or may not run in any given crank. To support this, we must not record the GC-related syscalls (dropImport, retireImport, retireExport) in the transcript. When replaying a transcript, we ignore these syscalls as well. closes #3146 refs #2615 refs #2660 refs #2724 --- .../src/kernel/vatManager/transcript.js | 10 +++ packages/SwingSet/test/test-gc-transcript.js | 85 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 packages/SwingSet/test/test-gc-transcript.js diff --git a/packages/SwingSet/src/kernel/vatManager/transcript.js b/packages/SwingSet/src/kernel/vatManager/transcript.js index 50b489adead..c9b4611a97f 100644 --- a/packages/SwingSet/src/kernel/vatManager/transcript.js +++ b/packages/SwingSet/src/kernel/vatManager/transcript.js @@ -26,7 +26,13 @@ export function makeTranscriptManager( }; } + const gcSyscalls = new Set(['dropImports', 'retireImports', 'retireExports']); + function addSyscall(d, response) { + const type = d[0]; + if (gcSyscalls.has(type)) { + return; + } if (currentEntry) { currentEntry.syscalls.push({ d, response }); } @@ -59,6 +65,10 @@ export function makeTranscriptManager( let replayError; function simulateSyscall(newSyscall) { + const type = newSyscall[0]; + if (gcSyscalls.has(type)) { + return undefined; + } const s = playbackSyscalls.shift(); const newReplayError = compareSyscalls(vatID, s.d, newSyscall); if (newReplayError) { diff --git a/packages/SwingSet/test/test-gc-transcript.js b/packages/SwingSet/test/test-gc-transcript.js new file mode 100644 index 00000000000..f5195104aef --- /dev/null +++ b/packages/SwingSet/test/test-gc-transcript.js @@ -0,0 +1,85 @@ +// eslint-disable-next-line import/order +import { test } from '../tools/prepare-test-env-ava'; + +import { makeDummySlogger } from '../src/kernel/slogger'; +import { makeManagerKit } from '../src/kernel/vatManager/manager-helper'; + +const m1 = ['message', { method: 'foo', args: { body: '', slots: [] } }]; + +function setup(storedTranscript = []) { + const vatID = 'vatID'; + const slog = makeDummySlogger({}, () => console); + const transcript = []; + const vatKeeper = { + addToTranscript(entry) { + transcript.push(entry); + }, + vatStats() { + return { transcriptCount: storedTranscript.length }; + }, + getTranscript() { + return storedTranscript; + }, + }; + const kernelKeeper = { + getVatKeeper() { + return vatKeeper; + }, + }; + function vatSyscallHandler(_vso) { + return ['ok', null]; + } + const workerCanBlock = false; + const mk = makeManagerKit( + vatID, + slog, + kernelKeeper, + vatSyscallHandler, + workerCanBlock, + ); + const { syscallFromWorker } = mk; + function deliver(_delivery) { + // a syscall.subscribe is included in the transcript + syscallFromWorker(['subscribe', 'p-1']); + // but GC syscalls are not + syscallFromWorker(['dropImports', ['o-1']]); + syscallFromWorker(['retireImports', ['o-1']]); + syscallFromWorker(['retireExports', ['o+2']]); + syscallFromWorker(['subscribe', 'p-2']); + return Promise.resolve(['ok', null, { usage: 0 }]); + } + mk.setDeliverToWorker(deliver); + function shutdown() {} + const manager = mk.getManager(shutdown); + return { manager, transcript }; +} + +test('gc syscalls are not included in transcript', async t => { + const { manager, transcript } = setup(); + await manager.deliver(m1); + + t.is(transcript.length, 1); + t.deepEqual(transcript[0], { + d: m1, + syscalls: [ + { d: ['subscribe', 'p-1'], response: null }, + { d: ['subscribe', 'p-2'], response: null }, + ], + }); +}); + +test('gc syscalls are ignored during replay', async t => { + const storedTranscript = [ + { + d: m1, + syscalls: [ + { d: ['subscribe', 'p-1'], response: null }, + { d: ['subscribe', 'p-2'], response: null }, + ], + }, + ]; + const { manager } = setup(storedTranscript); + await manager.replayTranscript(); + // success is that replayTranscript didn't throw anachrophobia error + t.pass(); +});