diff --git a/stopify-continuations/src/runtime/abstractRuntime.ts b/stopify-continuations/src/runtime/abstractRuntime.ts index 2c415d83..60bb54bd 100644 --- a/stopify-continuations/src/runtime/abstractRuntime.ts +++ b/stopify-continuations/src/runtime/abstractRuntime.ts @@ -42,6 +42,11 @@ export abstract class RuntimeImpl implements Runtime { // represents 'restore' mode. mode: Mode; + // Represents whether the stack is currently active – that is, if you + // call a function if it can expect to do its capture/restore logic + // with the right things available on the stack. + stackActive: boolean; + /** * A saved stack trace. This field is only used when a user-mode exception * is thrown. @@ -68,6 +73,7 @@ export abstract class RuntimeImpl implements Runtime { this.stackSize = stackSize; this.remainingStack = stackSize; this.mode = true; + this.stackActive = false; this.kind = undefined as any; // the worst } @@ -77,6 +83,7 @@ export abstract class RuntimeImpl implements Runtime { f: () => { this.stack = []; this.mode = true; + this.stackActive = true; return f(); }, this: this, @@ -86,6 +93,7 @@ export abstract class RuntimeImpl implements Runtime { runtime(body: () => any, onDone: (x: Result) => T): T { while(true) { + this.stackActive = true; const result = this.abstractRun(body); if (result.type === 'normal' || result.type === 'exception') { @@ -110,19 +118,23 @@ export abstract class RuntimeImpl implements Runtime { } else if(result.type === 'normal') { assert(this.mode, 'execution completed in restore mode'); + this.stackActive = false; return onDone(result); } else if(result.type === 'exception') { assert(this.mode, `execution completed in restore mode, error was: ${result.value}`); const stack = this.stackTrace; this.stackTrace = []; + this.stackActive = false; return onDone({ type: 'exception', value: result.value, stack }); } } else if (result.type === 'capture') { + this.stackActive = false; body = () => result.f.call(global, this.makeCont(result.stack)); } else if (result.type === 'restore') { + this.stackActive = false; body = () => { if (result.stack.length === 0) { throw new Error(`Can't restore from empty stack`); @@ -135,6 +147,7 @@ export abstract class RuntimeImpl implements Runtime { }; } else if (result.type === 'end-turn') { + this.stackActive = false; return result.callback(onDone); } } diff --git a/stopify-continuations/src/types.ts b/stopify-continuations/src/types.ts index f7cc2fba..797cdf53 100644 --- a/stopify-continuations/src/types.ts +++ b/stopify-continuations/src/types.ts @@ -47,6 +47,7 @@ export interface Runtime { mode: Mode; stack: Stack; + stackActive: boolean; endTurn(callback: (onDone: (x: Result) => any) => any): never; diff --git a/stopify/src/runtime/abstractRunner.ts b/stopify/src/runtime/abstractRunner.ts index 0b78d7c1..60e7d265 100644 --- a/stopify/src/runtime/abstractRunner.ts +++ b/stopify/src/runtime/abstractRunner.ts @@ -245,6 +245,10 @@ export abstract class AbstractRunner implements AsyncRun { this.continuationsRTS.runtime(body, callback); } + isRunning(): boolean { + return this.continuationsRTS.stackActive; + } + processEvent(body: () => void, receiver: (x: Result) => void): void { this.eventQueue.push({ body, receiver } ); this.processQueuedEvents(); diff --git a/stopify/src/types.ts b/stopify/src/types.ts index 7b1a3fab..9b6d3202 100644 --- a/stopify/src/types.ts +++ b/stopify/src/types.ts @@ -45,6 +45,7 @@ export interface AsyncRun { onBreakpoint?: (line: number) => void): void; pause(onPaused: (line?: number) => void): void; resume(): void; + isRunning(): boolean; setBreakpoints(line: number[]): void; step(onStep: (line: number) => void): void; pauseK(callback: (k: (r: Result) => void) => void): void; diff --git a/stopify/test/semantics.test.ts b/stopify/test/semantics.test.ts index cb63b8d7..0c21389a 100644 --- a/stopify/test/semantics.test.ts +++ b/stopify/test/semantics.test.ts @@ -282,6 +282,38 @@ describe('integration tests', function () { } }); +describe('Test cases that check running status',() => { + test('Running status should be paused (not running) in synchronous code after starting to run', onDone => { + const runner = harness(` + function sum(x) { + if (x % 20 === 0) { checkRunning(); } + if (x % 30 === 0) { pauseAndCheckRunning(); } + if (x <= 1) { + return 1; + } else { + return x + sum(x-1); + } + } + assert.equal(sum(100), 5050); + `, { captureMethod: 'lazy' }); + runner.g.checkRunning = function() { + assert.equal(runner.isRunning(), true); + }; + runner.g.pauseAndCheckRunning = function() { + runner.pauseK(k => { + assert.equal(runner.isRunning(), false); + k({ type: 'normal', value: 'restart' }); + }); + }; + runner.run(result => { + expect(result).toEqual({ type: 'normal' }); + onDone(); + expect(runner.isRunning()).toBe(false); + }); + }, 10000); + +}); + describe('Test cases that require deep stacks',() => { const runtimeOpts: Partial = { stackSize: 100,