Skip to content

Commit

Permalink
fix: remove nondeterminism from ag-solo replay
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Mar 17, 2020
1 parent 9568483 commit 2855b34
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 33 deletions.
5 changes: 4 additions & 1 deletion packages/SwingSet/src/devices/timer-src.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ export default function setup(syscall, state, helpers, endowments) {
// The latest time poll() was called. This might be a block height or it
// might be a time from Date.now(). The current time is not reflected back
// to the user.
let lastPolled = restart ? restart.lastPolled : 0;
//
// Note that during a replay, we may be replaying an older time,
// so we always need to start at 0.
let lastPolled = 0;
let nextRepeater = restart ? restart.nextRepeater : 0;

function getLastPolled() {
Expand Down
17 changes: 10 additions & 7 deletions packages/SwingSet/src/kernel/vatManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,11 @@ export default function makeVatManager(
return process(
() => dispatch[dispatchOp](...dispatchArgs),
() => transcriptFinishDispatch(),
err => console.log(`doProcess: ${errmsg}:`, err),
err => {
if (errmsg !== null) {
console.log(`doProcess: ${errmsg}:`, err);
}
},
);
}

Expand Down Expand Up @@ -382,13 +386,12 @@ export default function makeVatManager(
throw replayAbandonShip;
}
playbackSyscalls = Array.from(t.syscalls);
// We really don't care about "failed replays" because they're just
// exceptions that have been raised in a normal event.
//
// If we really fail, replayAbandonShip is set.
// eslint-disable-next-line no-await-in-loop
await doProcess(
t.d,
`Replay failed: [${t.d[0]}, ${t.d[1]}, ${t.d[2]}, ${JSON.stringify(
t.d[3],
)}]`,
);
await doProcess(t.d, null);
}

if (replayAbandonShip) {
Expand Down
58 changes: 33 additions & 25 deletions packages/cosmic-swingset/lib/ag-solo/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,23 +125,25 @@ async function buildSwingset(
// ensures only one of them (in order) is running at a time.
const makeWithCriticalSection = () => {
let runQueueP = Promise.resolve();
return function withCriticalSection(thunk) {
// Ignore arguments (leftover results of other thunks) and
// resolve/reject with whatever the thunk does.
const wrapThunk = _ => thunk();

// Atomically replace the runQueue by appending us to it.
runQueueP = runQueueP.then(wrapThunk, wrapThunk);

// Return the promisified version of the thunk.
return runQueueP;
return function withCriticalSection(inner) {
return function wrappedCall(...args) {
// Curry the arguments into the inner function, and
// resolve/reject with whatever the inner function does.
const thunk = _ => inner(...args);

// Atomically replace the runQueue by appending us to it.
runQueueP = runQueueP.then(thunk, thunk);

// Return the promisified version of the thunk.
return runQueueP;
};
};
};

const withInboundCriticalSection = makeWithCriticalSection();

// Use the critical section to make sure it doesn't overlap with
// deliverInboundCommand.
// other inbound messages.
const deliverInboundToMbx = withInboundCriticalSection(
async (sender, messages, ack) => {
if (!(messages instanceof Array)) {
Expand All @@ -155,7 +157,7 @@ async function buildSwingset(
);

// Use the critical section to make sure it doesn't overlap with
// deliverInboundToMbx.
// other inbound messages.
const deliverInboundCommand = withInboundCriticalSection(async obj => {
// this promise could take an arbitrarily long time to resolve, so don't
// wait on it
Expand All @@ -176,25 +178,31 @@ async function buildSwingset(
});

const intervalMillis = 1200;
// TODO(hibbert) protect against kernel turns that take too long
// drop calls to moveTimeForward if it's fallen behind, to make sure we don't
// have two copies of controller.run() executing at the same time.
function moveTimeForward() {

// Use the critical section to make sure it doesn't overlap with
// other inbound messages.
const moveTimeForward = withInboundCriticalSection(async () => {
const now = Math.floor(Date.now() / intervalMillis);
if (timer.poll(now)) {
const p = processKernel();
p.then(
_ => console.log(`timer-provoked kernel crank complete ${now}`),
err =>
console.log(`timer-provoked kernel crank failed at ${now}:`, err),
);
try {
if (timer.poll(now)) {
await processKernel();
console.log(`timer-provoked kernel crank complete ${now}`);
}
} catch (err) {
console.log(`timer-provoked kernel crank failed at ${now}:`, err);
} finally {
// We only rearm the timeout if moveTimeForward has completed, to
// make sure we don't have two copies of controller.run() executing
// at the same time.
setTimeout(moveTimeForward, intervalMillis);
}
}
setInterval(moveTimeForward, intervalMillis);
});

// now let the bootstrap functions run
await processKernel();

setTimeout(moveTimeForward, intervalMillis);

return {
deliverInboundToMbx,
deliverInboundCommand,
Expand Down

0 comments on commit 2855b34

Please sign in to comment.