Skip to content

Commit

Permalink
Add super simple deadlock detection scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
JothamWong committed Apr 11, 2024
1 parent 0051e93 commit 0eb03fa
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 11 deletions.
11 changes: 11 additions & 0 deletions src/tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1278,3 +1278,14 @@ print("This is the end");
10; // do not give time for foo to read
`, 10,
'"This will show"\n"This is the end"', defaultNumWords);

// Simple test for deadlock detection
testProgram(`
func foo(x chan int) {
x <- 5;
}
var x chan int = make(chan int); // unbuffered channel
go foo(x);
x <- 6; // will deadlock here
`, 'Deadlock detected!', '', defaultNumWords);
51 changes: 40 additions & 11 deletions src/vm/oogavm-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ let tempRoots: number[][] = [];
// Flag to indicate "true concurrency"
let isAtomicSection: boolean = false;

enum ThreadState {
BLOCKING, // whether thread was blocked
RUNNING, // if thread is currently running
YIELDED, // if thread naturally gave up control
}

enum ProgramState {
NORMAL,
// TODO: Would this actually be useful? We should make the assumption that this was done at compile time
Expand All @@ -103,12 +109,14 @@ export class Thread {
_E: number[];
_PC: number;
_RTS: number[];
state: ThreadState;
// TODO: Do this properly
constructor(OS: number[], E: number[], PC: number, RTS: number[]) {
constructor(OS: number[], E: number[], PC: number, RTS: number[], state: ThreadState) {
this._OS = OS;
this._E = E;
this._PC = PC;
this._RTS = RTS;
this.state = state;
}
}

Expand All @@ -129,18 +137,18 @@ function initScheduler() {
const [newMainThreadId, newTimeQuanta] = scheduler.runThread(); // main thread
mainThreadId = newMainThreadId;
TimeQuanta = newTimeQuanta;
threads.set(mainThreadId, new Thread(OS, E, PC, RTS));
threads.set(mainThreadId, new Thread(OS, E, PC, RTS, ThreadState.RUNNING));
currentThreadId = mainThreadId;
}

function newThread(newOS: number[], newRTS: number[], newPC: number, newE: number[]) {
const newThreadId = scheduler.newThread();
threads.set(newThreadId, new Thread(newOS, newE, newPC, newRTS));
threads.set(newThreadId, new Thread(newOS, newE, newPC, newRTS, ThreadState.RUNNING));
}

function pauseThread() {
function pauseThread(state: ThreadState) {
// save current state
threads.set(currentThreadId, new Thread(OS, E, PC, RTS));
threads.set(currentThreadId, new Thread(OS, E, PC, RTS, state));
scheduler.pauseThread(currentThreadId);
}

Expand All @@ -166,6 +174,27 @@ function runThread() {
PC = thread._PC;
RTS = thread._RTS;
E = thread._E;
// there is definitely a deadlock if we are back to mainThreadId and every other thread is blocking
if (currentThreadId === mainThreadId) {
let deadlocked = true;
// @ts-ignore
for (let [threadId, thread] of threads) {
if (thread.state !== ThreadState.BLOCKING) {
deadlocked = false;
}
}
if (deadlocked) {
throw new OogaError("Deadlock detected!");
}
}
}

function blockThread() {
if (threads.size === 1) {
throw new OogaError("Stuck forever!");
}
pauseThread(ThreadState.BLOCKING);
runThread();
}

function timeoutThread() {
Expand All @@ -174,7 +203,7 @@ function timeoutThread() {
TimeQuanta = scheduler.getMaxTimeQuanta();
return;
}
pauseThread();
pauseThread(ThreadState.YIELDED);
runThread();
}

Expand Down Expand Up @@ -744,7 +773,7 @@ const microcode = {
// we need to push the chan onto the goroutine's OS again
pushAddressOS(chan);
// now 'block'
timeoutThread();
blockThread();
// realise that if nothing ever pushes onto the channel, this goes on forever
} else { // unblocking read from buffered channel is simply a pop
let value = [];
Expand All @@ -761,7 +790,7 @@ const microcode = {
tempRoots.push(chan);
pushAddressOS(chan);
tempRoots.pop();
timeoutThread();
blockThread();
} else {
let value = [];
value[0] = popUnbufferedChannel(chan[0]);
Expand Down Expand Up @@ -800,7 +829,7 @@ const microcode = {
tempRoots.pop();
tempRoots.pop();

timeoutThread();
blockThread();
} else {
// write and move on
// push the chan onto OS so that CHECK_CHANNEL works
Expand Down Expand Up @@ -842,7 +871,7 @@ const microcode = {
tempRoots.pop();
tempRoots.pop();
// now give up thread
timeoutThread();
blockThread();
}
}
},
Expand All @@ -867,7 +896,7 @@ const microcode = {
tempRoots.push(chan);
pushAddressOS(chan);
tempRoots.pop();
timeoutThread();
blockThread();
}
}
};
Expand Down

0 comments on commit 0eb03fa

Please sign in to comment.