Skip to content

Commit

Permalink
Avoid cyclical deadlocks in the verifier (#144)
Browse files Browse the repository at this point in the history
* Avoid cyclical deadlocks in the verifier

By always incrementing targets when we are trying to hit something we know to be unlocked, we can avoid cycles in the waits-for graph

* Add comment for target
  • Loading branch information
jackkleeman authored Jun 15, 2023
1 parent 4ab918a commit fe2d609
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 14 deletions.
1 change: 1 addition & 0 deletions contracts/src/main/proto/interpreter.proto
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ message TestParams {

message Key {
TestParams params = 1;
// target is an arbitrary integer allowing us to select different service keys
int32 target = 2;
}

Expand Down
21 changes: 7 additions & 14 deletions services/node-services/src/verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
import seedrandom from "seedrandom";
import { useContext } from "@restatedev/restate-sdk";

const MAX_TARGET = 1024;
const DEFAULT_MAX_SLEEP = 32768;

export class CommandBuilder {
Expand All @@ -38,15 +37,6 @@ export class CommandBuilder {
return Math.floor(Math.abs(this.random() * max));
}

randomTarget(...lockedTargets: Array<number>): number {
let target = this.randomInt(MAX_TARGET);
// rejection sampling
while (lockedTargets.includes(target)) {
target = this.randomInt(MAX_TARGET);
}
return target;
}

normaliseSleeps(commands: Commands | undefined, factor: number) {
if (commands == undefined) {
return;
Expand Down Expand Up @@ -99,7 +89,7 @@ export class CommandBuilder {
maxSleepMillis: number,
depth: number
): { target: number; commands: Commands } {
const call = this._buildCommands(this.randomTarget(), depth, []);
const call = this._buildCommands(0, depth, []);
const duration = this.durationUpperBound(call.commands);
// normalise so that the entire job takes less time than the max sleep
this.normaliseSleeps(call.commands, maxSleepMillis / duration);
Expand Down Expand Up @@ -133,7 +123,10 @@ export class CommandBuilder {
() => ({
// hit a known-unlocked target with a sync call, and pass on the lock list for future blocking calls
syncCall: this._buildCommands(
this.randomTarget(target, ...lockedTargets),
// jump to a target between 1 and 32 ahead
// by only going upwards we avoid cycles
// by skipping up to 32 we avoid all paths landing on the same few keys
target + 1 + this.randomInt(32),
depth - 1,
[target, ...lockedTargets]
),
Expand All @@ -143,7 +136,7 @@ export class CommandBuilder {
callId: asyncUnlockedCounter++,
// hit a known-unlocked target with an async call that may be awaited, and pass on the lock list for future blocking calls
...this._buildCommands(
this.randomTarget(target, ...lockedTargets),
target + 1 + this.randomInt(32),
depth - 1,
[target, ...lockedTargets]
),
Expand Down Expand Up @@ -182,7 +175,7 @@ export class CommandBuilder {
() => ({
// deliberately hit a known-unlocked target with a background call (the call should schedule asap)
backgroundCall: this._buildCommands(
this.randomTarget(target, ...lockedTargets),
target + 1 + this.randomInt(32),
depth - 1,
[]
),
Expand Down

0 comments on commit fe2d609

Please sign in to comment.