thenodebook.com
Lab 06 / Node Runtime Labs

Async Task Runtime Job Orchestrator.

Build a local task runtime that reads task definitions, schedules work, limits concurrency, records every attempt, handles failures as data, persists state, and writes reports that make async control flow visible.

Task commandsLocal orchestrator runs
  • 01Help
    $ npm run tasks -- --help

    Locks down the CLI surface before scheduling work.

  • 02Limit
    $ npm run tasks -- tasks/one-hundred-delays.json --concurrency 5

    Shows queue time and active task count.

  • 03Modes
    $ npm run tasks -- tasks/mixed-results.json --mode allSettled --concurrency 3

    Compares result semantics without changing fixtures.

  • 04Cancel
    $ npm run tasks -- tasks/cancel-after.json --concurrency 2 --cancel-after 250

    Tests abort propagation through running work.

  • 05Bench
    $ npm run bench

    Writes throughput and failure-handling reports.

Run task files under concurrency, cancellation, and retry modes.
14Phases
8/10Difficulty
0 depsPackage installs
What this lab builds

The finished tool runs real async work.

Validated task definitions

Read versioned JSON task files, normalize task fields, reject duplicate ids, validate dependencies, and turn user input into one scheduler-ready shape.

Bounded async scheduler

Run command and function tasks, enforce concurrency, measure queue time, block dependents after failures, and record run-level scheduling metrics.

Failure, timeout, and cancellation paths

Classify non-zero exits, spawn errors, thrown errors, rejected Promises, timeouts, cancellations, and retry attempts without losing task evidence.

Runtime events and durable state

Emit lifecycle events, expose progress through an async iterator, persist state during the run, resume interrupted work, and write final benchmark reports.

Complete phase plan

Every phase in Lab 06.

The build starts with a strict CLI and ends with benchmark reports. Each phase adds one observable runtime capability and one artifact the learner can inspect.

00

Create the project

Create the task runtime project shell with a CLI, folders, npm scripts, and the first task-file argument path.

  • Create package metadata with ESM enabled, a Node v24 engine declaration, and tasks, bench, and test scripts.
  • Separate src, tasks, state, and reports so runtime code, task inputs, mutable state, and output evidence have distinct paths.
  • Create the CLI entry file and print a short overview when no arguments are passed.
  • Make --help print the same overview and exit successfully before task-file validation runs.
  • Reject unknown flags on stderr with a non-zero exit code.
  • Parse the first positional argument as the task file path and keep flags separate from positional input.
A runnable project shell that exposes the CLI contract, accepts a task file path, and fails clearly on bad flags.
01

Define a task format

Define a versioned task file format and validate it before any task starts.

  • Create a task-format module that names the normalized internal shape: id, type, command, args, module, export, timeoutMs, retries, dependsOn, and metadata.
  • Create a loader that reads JSON from disk and wraps parse failures with the task file path.
  • Require a top-level object with version, tasks, and optional metadata.
  • Validate every task field, including command fields, function fields, timeout values, retry policy, dependency arrays, and metadata.
  • Validate relationships after all ids are known, including duplicate ids and dependencies that reference missing tasks.
  • Add success and invalid fixtures so the happy path and validation path stay repeatable.
  • Wire loading and validation into the CLI and print task counts and ids before execution exists.
A normalized task model plus validation errors that name the exact file, task index, field, or dependency that failed.
02

Run one async task

Execute one command task and write a report with status, timing, and child-process evidence.

  • Create the runner module and export one runTask(task, context) boundary.
  • Spawn command tasks with node:child_process using command and args as separate values.
  • Record startedAt, endedAt, and elapsedMs with wall-clock timestamps and monotonic elapsed time.
  • Stream child output through the parent terminal while counting stdoutBytes and stderrBytes.
  • Resolve successful command tasks with status success after the child close event.
  • Write reports/latest.json after the run with the task result and process evidence.
A first report that proves one async task ran, produced output, exited successfully, and left inspectable timing data.
03

Add error handling

Capture task failures as report data while keeping the runtime alive long enough to write evidence.

  • Extend task results with status failed and an error object containing kind, message, stack, exitCode, and signal where available.
  • Classify non-zero command exits as non_zero_exit while preserving the raw exit code.
  • Classify child-process error events as spawn_error because those failures happen before normal process exit.
  • Add function task support through dynamic import of the configured module and export.
  • Classify synchronous throws and awaited Promise rejections as distinct thrown and rejected outcomes.
  • Set the CLI exit code to 1 after the report is written when any task failed.
Failure reports that distinguish command exits, spawn errors, thrown errors, and rejected Promises without crashing the orchestrator.
04

Add concurrency limits

Run multiple tasks while keeping active work under the configured concurrency limit.

  • Create the scheduler module and move multi-task execution out of the CLI.
  • Parse --concurrency with a default of 1 and reject missing, zero, negative, and non-integer values.
  • Build the ready queue from tasks whose dependencies have already succeeded.
  • Start tasks only while activeCount stays below the concurrency value and update active count through one path.
  • Record queuedAt, startedAt, queueMs, and endedAt for every task.
  • Mark dependents as blocked when a dependency fails and include the failed dependency id.
  • Record run metrics such as maxActive, queueLengthMax, taskCount, completedCount, failedCount, and blockedCount.
A scheduler report that shows active-count enforcement, queue wait time, dependency ordering, blocked work, and aggregate run metrics.
05

Add Promise combinator modes

Compare all, allSettled, race, and any behavior inside the task runtime.

  • Parse --mode with all, allSettled, race, and any values, using allSettled as the default.
  • Move mode policy into helpers so scheduler mechanics stay separate from aggregate decision rules.
  • Implement allSettled by waiting until every runnable task reaches a terminal state.
  • Implement all by stopping the run after the first failed task while still tracking already-started work.
  • Implement race by stopping after the first task settles, regardless of success or failure.
  • Implement any by stopping after the first success or reporting aggregate failure when every candidate fails.
  • Write reports/modes.json and update the current mode entry without deleting entries from earlier mode runs.
A mode comparison report with started counts, settled counts, stopped reasons, and final status counts for each Promise policy.
06

Add timeouts

Fail tasks that exceed a configured deadline and clean up the work they started.

  • Read task-level timeoutMs and a CLI --timeout fallback, with task-level values taking precedence.
  • Create an AbortController for each task attempt and pass its signal through context.
  • Start a timeout timer when the attempt starts, abort when it fires, and clear it in the cleanup path.
  • Terminate command tasks on timeout and use a short kill grace period before finalizing the result.
  • Pass AbortSignal into function tasks so abort-aware APIs and fixtures can observe cancellation.
  • Report status timed_out, error.kind timeout, timedOutAt, and the effective timeout value.
Timeout evidence that separates deadline failures from ordinary failed tasks and proves cleanup happened.
07

Add cancellation

Stop pending and running tasks with a run-level AbortController.

  • Add a run-level AbortController that represents cancellation for the entire run.
  • Parse --cancel-after and trigger run cancellation after the configured delay.
  • Make every attempt observe both run cancellation and its own task timeout.
  • Stop dequeuing new work after cancellation begins and mark queued tasks as canceled.
  • Abort running attempts and let runners release child processes, timers, streams, and pending Promise work.
  • Record run.canceledAt, run.cancelReason, and the set of canceled task ids in the report.
A cancellation report that distinguishes canceled tasks from timed_out, failed, blocked, and queued states.
08

Add retries

Retry failed tasks with a bounded, inspectable policy.

  • Normalize retries.maxAttempts, retries.backoffMs, optional retries.factor, and optional retries.retryOn.
  • Create runTaskWithRetries(task, context) so the scheduler sees one final result while attempts remain visible.
  • Retry only statuses and error kinds allowed by policy, with canceled tasks excluded from retry.
  • Wait for backoffMs before the second attempt and multiply by factor for later attempts when configured.
  • Record attempt, status, startedAt, endedAt, elapsedMs, error, and nextDelayMs for each attempt.
  • Add deterministic flaky fixtures that fail the first N attempts based on metadata or small state files.
Attempt history that shows retry budgets, retryable outcomes, backoff delays, exhausted attempts, and eventual success.
09

Add EventEmitter lifecycle events

Emit task lifecycle events from the runtime so observers can subscribe without changing scheduler code.

  • Create a runtime event boundary or orchestrator that extends EventEmitter.
  • Emit task:start before each attempt begins with task id, attempt, timestamp, and current status.
  • Emit task:success, task:failure, and task:retry at the matching state transitions.
  • Emit task:finish exactly once for every terminal task state, including failed, timed_out, canceled, and blocked.
  • Add --log-events to print lifecycle events as they happen.
  • Test listener order and the EventEmitter error event contract without using task failure as a generic error event.
A lifecycle event stream that proves ordering around start, failure, retry, success, cancellation, and finish states.
10

Add async iterator progress

Expose runtime progress as an async iterator consumed by the CLI.

  • Create progressEvents(runtime, options) in a progress module.
  • Convert lifecycle events into progress records with type, taskId, status, attempt, timestamp, and message fields.
  • Buffer records that arrive before the consumer calls next so early events are preserved.
  • Resolve pending next calls when new progress arrives so for await...of waits without polling.
  • Close the iterator on normal completion, cancellation, or runtime error and remove listeners.
  • Add --progress and print live progress lines from a for await...of loop.
A progress stream that keeps event ordering, handles early events, closes cleanly, and leaves no attached listeners behind.
11

Persist task state

Save task state to disk while a run is still active.

  • Create a state-store module with save(snapshot) and load() operations.
  • Write state atomically through a temp file in the same directory and rename it to state/run.json.
  • Persist runId, taskFile, startedAt, updatedAt, status, options, tasksById, and events.
  • Save after every task state transition and retry event.
  • Add --status so the CLI can read state/run.json and print current counts without starting a new run.
  • Add --resume to reconcile interrupted state and mark previously running tasks as interrupted after restart.
A durable state file that distinguishes completed, failed, queued, running, interrupted, timed-out, canceled, and blocked tasks.
12

Add graceful shutdown

Handle SIGINT and SIGTERM while preserving state and writing shutdown evidence.

  • Install SIGINT and SIGTERM listeners before tasks begin.
  • Parse --shutdown with drain and cancel modes, defaulting to cancel.
  • On the first signal, record shutdownStartedAt and stop starting new queued work.
  • In drain mode, wait for running tasks; in cancel mode, abort running attempts and mark queued work as canceled.
  • Write state/run.json and reports/shutdown.json with signal name, mode, timestamps, and final counts.
  • On a second signal, write one best-effort state snapshot and choose a forced non-zero exit code.
  • Test shutdown decision functions without sending real process signals.
A shutdown report that names the signal, selected policy, task outcomes, incomplete work, and state saved before exit.
13

Add benchmark and final report

Compare concurrency, timeout, retry, and combinator settings with machine-readable and human-readable reports.

  • Create src/bench.js and make npm run bench call the same runtime path as the CLI.
  • Create deterministic benchmark tasks under tasks/bench or in memory.
  • Compare concurrency 1, 5, and 20 with duration, throughput, queue time, and max active count.
  • Compare no-timeout versus short-timeout and retry-disabled versus retry-enabled scenarios.
  • Compare all, allSettled, race, and any mode scenarios using the same result shape.
  • Write reports/bench.json with durationMs, throughputPerSecond, successCount, failedCount, timedOutCount, canceledCount, retryCount, queueMsAvg, queueMsP95, and maxActive.
  • Write reports/task-runtime.md from the same scenario objects, including command, Node version, platform, CPU count, task mix, and interpretation.
A final benchmark package with JSON data and a Markdown report that explain throughput, queueing, failures, retries, timeouts, modes, and max concurrency.
Runtime evidence

Reports are part of the orchestrator.

Lab 06 treats task state as evidence: latest run data, combinator mode comparison, live persistence, shutdown records, benchmark JSON, and a final Markdown report generated from the same scenario objects.

reports/latest.jsonlatest run status, task results, timings, process evidence, and scheduler metrics
reports/modes.jsonall, allSettled, race, and any behavior compared across runs
state/run.jsonlive state snapshots with task status, options, events, and resume data
reports/shutdown.jsonsignal, shutdown mode, terminal states, and forced-exit evidence
reports/bench.jsonmachine-readable scenario results for concurrency, timeouts, retries, and modes
reports/task-runtime.mdfinal human-readable benchmark report generated from the same scenario data

Choose your NodeBook package.

Switch between one-reader pricing and team licenses for up to 25 team members.

Individual pricing is for one reader and one personal purchase record.TEAM LICENSES INCLUDE UP TO 25 MEMBERS. DOWNLOADED CONTENT MAY BE
SHARED INTERNALLY WITH UP TO 30 PEOPLE TOTAL.
Downloadable book bundle

Digital Bundle

Volume I as EPUB, light/dark PDFs, slides, cheatsheets, and future updates.

$19.99$49.99
One-time purchase
  • Volume I EPUB for offline reading
  • Light and dark PDF editions
  • Slide decks for chapter review
  • Cheatsheets for quick lookup
  • Future Digital Bundle updates
  • Lifetime access to the files
Get Digital Bundle
This is the downloadable Volume I study bundle. It does not include Node Runtime Labs.
Best value
Everything together

NodeBook Pro

All Labs plus the downloadable Volume I bundle in one purchase. Save $9.99 vs buying separately.

$49.99$99.99
One-time purchase
Node Runtime Labs+Digital Bundle
  • Everything in Node Runtime Labs
  • Everything in the Digital Bundle
  • Seven included lab projects
  • Three upcoming labs when released
  • Future updates for both products
  • Lifetime access to purchased files
Get NodeBook Pro
This includes both paid products: Node Runtime Labs and the Digital Bundle.
Premium labs
Complex runtime projects

Node Runtime Labs

Seven long-form builds: recorder, binary store, stream workbench, resolver, watcher, task runtime, and protocol gateway.

$39.99$79.99
One-time purchase
  • Node Runtime Flight Recorder
  • Binary File Store / Append-Only Log Database
  • Stream Processing Workbench
  • Module Resolution Inspector
  • Atomic File Watcher + Incremental Build Cache
  • Async Task Runtime / Local Job Orchestrator
  • Custom Binary Protocol Gateway
  • Three more labs upcoming
Get Labs Bundle
This is the paid labs bundle. It does not include EPUB, PDFs, slides, or cheatsheets.
Team downloadable files

Digital Bundle Team

Volume I files, slides, cheatsheets, and updates for a small engineering team.

$59.99
One-time team license
  • Everything in the Digital Bundle
  • Supports up to 25 team members
  • Share internally with up to 30 people
  • Single purchase for the team
  • Future Digital Bundle updates
  • Lifetime access to purchased files
Get Team Bundle
Covers up to 25 team members and internal sharing with up to 30 people.
Team value
Everything for the team

NodeBook Pro Team

Node Runtime Labs plus the downloadable Volume I bundle in one team license.

$149.99
One-time team license
Node Runtime Labs+Digital Bundle
  • Everything in NodeBook Pro
  • Supports up to 25 team members
  • Share internally with up to 30 people
  • One receipt and license record
  • Future updates for both products
  • Save $29.99 vs team products separately.
Get Pro Team
Covers up to 25 team members and internal sharing with up to 30 people.
Team labs
Runtime training projects

Node Runtime Labs Team

Seven long-form builds for onboarding, study groups, and internal training.

$119.99
One-time team license
  • Node Runtime Flight Recorder
  • Binary File Store / Append-Only Log Database
  • Stream Processing Workbench
  • Module Resolution Inspector
  • Supports up to 25 team members
  • Share internally with up to 30 people
  • Three more labs upcoming
Get Team Labs
Covers up to 25 team members and internal sharing with up to 30 people.
See complete pricing breakdown
Other labs in the bundle

Lab 06 sits inside the full runtime set.

The bundle includes seven runtime projects covering process observation, binary storage, streams, module resolution, file watching, async orchestration, and custom protocols.

Async Task Runtime Lab | NodeBook Runtime Labs