QPlan Execution Control
Execution control allows you to manage the lifecycle of a QPlan script execution, including aborting, pausing, resuming, and monitoring its state.
Features
1. Abort
Forcibly stops a running Plan.
const qplan = new QPlan(script);
const runPromise = qplan.run();
// Abort
qplan.abort();
try {
await runPromise;
} catch (err) {
if (err instanceof AbortError) {
console.log("Execution aborted");
}
}
2. Pause/Resume
You can pause execution and resume it later.
const qplan = new QPlan(script);
const runPromise = qplan.run();
// Pause
qplan.pause();
console.log(qplan.getState()); // "paused"
// Resume
qplan.resume();
console.log(qplan.getState()); // "running"
await runPromise;
3. Timeout
You can limit the execution time.
const qplan = new QPlan(script);
try {
await qplan.run({
timeout: 5000 // 5 second timeout
});
} catch (err) {
if (err instanceof AbortError) {
console.log("Execution timed out");
}
}
4. Checkpoint
You can save the execution state as a snapshot and restore it.
const qplan = new QPlan(script);
// Auto checkpoint (before each Step)
await qplan.run({
autoCheckpoint: true,
maxSnapshots: 10 // Keep max 10 snapshots
});
// Get checkpoints
const checkpoints = qplan.getCheckpoints();
const lastCheckpoint = qplan.getLastCheckpoint();
// Restore checkpoint
if (lastCheckpoint) {
await qplan.restoreCheckpoint(lastCheckpoint);
}
// Create manual checkpoint
const snapshot = qplan.createCheckpoint("my-checkpoint");
5. State Monitoring
You can monitor the execution state in real-time.
const qplan = new QPlan(script);
// Monitor state
const monitor = setInterval(() => {
const status = qplan.getStatus();
console.log(`State: ${status.state}, Step: ${status.currentStepId}`);
console.log(`Elapsed: ${status.elapsedTime}ms`);
}, 1000);
await qplan.run();
clearInterval(monitor);
6. Execution Events
You can register events that react to state changes or control commands.
await qplan.run({
stepEvents: {
// Detect state changes
onStateChange: (newState, oldState, ctx) => {
console.log(`State changed: ${oldState} -> ${newState}`);
},
// Detect control commands
onPause: (ctx) => console.log("Execution paused"),
onResume: (ctx) => console.log("Execution resumed"),
onAbort: (ctx) => console.log("Execution aborted"),
// Detect timeout
onTimeout: (ctx) => console.log("Execution timed out"),
}
});
API
QPlan Methods
Execution Control
abort(): Stop executionpause(): Pause executionresume(): Resume executiongetState(): Get current state (idle/running/paused/completed/aborted/error)getStatus(): Get detailed status informationgetElapsedTime(): Get elapsed time (ms)
Checkpoints
createCheckpoint(label?): Create a checkpointrestoreCheckpoint(snapshot): Restore a checkpointgetCheckpoints(): Get all checkpointsgetCheckpoint(id): Get a specific checkpointgetLastCheckpoint(): Get the most recent checkpointclearCheckpoints(): Delete all checkpoints
RunOptions
interface QPlanRunOptions {
// Existing options
registry?: ModuleRegistry;
stepEvents?: StepEventEmitter;
env?: Record<string, any>;
metadata?: Record<string, any>;
runId?: string;
// Execution Control Options
timeout?: number; // Timeout (ms)
autoCheckpoint?: boolean; // Auto checkpoint
maxSnapshots?: number; // Max snapshots to keep
controller?: ExecutionController; // Custom controller
}
StepEventEmitter (Extended)
Execution control events have been added in addition to the existing Step events.
interface StepEventEmitter {
// Existing Events
onPlanStart?(plan: PlanEventInfo, context?: StepEventRunContext): Promise<void> | void;
onPlanEnd?(plan: PlanEventInfo, context?: StepEventRunContext): Promise<void> | void;
onStepStart?(info: StepEventInfo, context?: StepEventRunContext): Promise<void> | void;
onStepEnd?(info: StepEventInfo, result?: any, context?: StepEventRunContext): Promise<void> | void;
onStepError?(info: StepEventInfo, error: Error, context?: StepEventRunContext): Promise<void> | void;
onStepRetry?(info: StepEventInfo, attempt: number, error: Error, context?: StepEventRunContext): Promise<void> | void;
onStepJump?(info: StepEventInfo, targetStepId: string, context?: StepEventRunContext): Promise<void> | void;
// Execution Control Events
onAbort?(context?: StepEventRunContext): Promise<void> | void;
onPause?(context?: StepEventRunContext): Promise<void> | void;
onResume?(context?: StepEventRunContext): Promise<void> | void;
onTimeout?(context?: StepEventRunContext): Promise<void> | void;
onStateChange?(newState: ExecutionState, oldState: ExecutionState, context?: StepEventRunContext): Promise<void> | void;
}
ExecutionState
enum ExecutionState {
IDLE = "idle", // Idle
RUNNING = "running", // Running
PAUSED = "paused", // Paused
COMPLETED = "completed", // Completed
ABORTED = "aborted", // Aborted
ERROR = "error" // Error
}
ExecutionSnapshot
interface ExecutionSnapshot {
snapshotId: string;
runId: string;
timestamp: number;
state: ExecutionState;
currentStepId?: string;
context: Record<string, any>;
blockStack: BlockStackFrame[];
}
Examples
For the full example, please refer to examples/22_exam_execution_control.js.
node examples/22_exam_execution_control.js
Use Cases
1. Managing Long-Running Tasks
const qplan = new QPlan(longRunningScript);
// When stop button is clicked in UI
stopButton.onclick = () => qplan.abort();
// When pause button is clicked in UI
pauseButton.onclick = () => qplan.pause();
resumeButton.onclick = () => qplan.resume();
await qplan.run({ timeout: 300000 }); // 5 minutes timeout
2. Error Recovery
const qplan = new QPlan(script);
try {
await qplan.run({ autoCheckpoint: true });
} catch (err) {
// Restore to previous checkpoint on error
const lastGoodCheckpoint = qplan.getCheckpoints()
.filter(cp => cp.state === ExecutionState.RUNNING)
.pop();
if (lastGoodCheckpoint) {
await qplan.restoreCheckpoint(lastGoodCheckpoint);
// Retry logic
}
}
3. Progress Indication
const qplan = new QPlan(script);
const progressBar = setInterval(() => {
const status = qplan.getStatus();
const progress = calculateProgress(status);
updateUI(progress);
}, 500);
await qplan.run();
clearInterval(progressBar);
Caveats
- Abort might not be immediate: It waits for the currently running module to complete.
- Module-level control: Modules that run long loops or waits should call
await ctx.checkControl()periodically so pause/abort requests are honored inside the module. - Checkpoints consume memory: Limit them using the
maxSnapshotsoption. - Timeout might not be exact: It depends on the accuracy of JavaScript timers.
- Current node completes during Pause: Execution pauses before the next node starts.
Migration Guide
Existing code works without changes. Execution control features can be used optionally.
// Existing code (No changes)
const ctx = await runQplan(script);
// Using new features
const qplan = new QPlan(script);
setTimeout(() => qplan.abort(), 5000);
await qplan.run({ timeout: 10000 });