Skip to practice questions

Part 3 · Cancellation and debug

Process control, cancellation, and liveness

Terminate only owned work, release every held resource, and diagnose deadlock, leaked processes, and lost progress systematically.

Reading path
3 of 3
Concept chapters
2
Practice links
5

Question-first preparation

Practice the mechanisms on this page.

Each question maps directly to one of the chapters below, so you can test the contract before reading the explanation.

Recommended start

One representative prompt from each major mechanism. Open any card in the live question bank.

  1. Q313Nested disable forkSystemVerilog · HardPremium
  2. Q967Race a transaction against a scoped watchdogTestbench Concurrency · MediumPremium
  3. Q987Control a background process lifecycleTemporal Checks · MediumPremium
  4. Q886Stop the losing forked processSystemVerilog · MediumPremium
  5. Q277UVM global heartbeat componentTemporal Checks · MediumPractice
Complete question directoryFilter every stable practice link by mechanism.5 questions
Showing 5 questions across every track.Choose a track to narrow the practice set without changing its stable question links.
  1. Q313Nested disable forkSystemVerilog · HardPremium
  2. Q967Race a transaction against a scoped watchdogTestbench Concurrency · MediumPremium
  3. Q987Control a background process lifecycleTemporal Checks · MediumPremium
  4. Q886Stop the losing forked processSystemVerilog · MediumPremium
  5. Q277UVM global heartbeat componentTemporal Checks · MediumPractice

05 · Handles and cancellation

Cancel the process you own, not every child in reach.

process handles allow targeted status, await, suspend, resume, kill, and random-state control. disable fork is broader: it kills all active child processes of the caller.

Rule to say firstPrefer stored process handles or a narrow wrapper scope for cancellation; use disable fork only after auditing the caller's entire live child set.

Targeted lifecycle

Capture → inspect → await or kill → verify

  1. 01Captureprocess::self()

    Child publishes its handle before the controller uses it.

  2. 02Inspectstatus()

    FINISHED, RUNNING, WAITING, SUSPENDED, or KILLED guide legal action.

  3. 03Controlawait() / kill()

    Wait naturally or terminate the exact process.

  4. 04Verifyterminal status

    No live child or held resource remains.

Captureprocess::self()

Child publishes its handle before the controller uses it.

Method
await()
The process API uses await, not a join method.
Initialization
Synchronize first
A controller must not call a null handle before its child publishes it.
disable label
Terminates the named scope
Code after a self-terminating named scope may never run.

A process handle turns implicit broad cancellation into a reviewed ownership operation with observable status.

01

Observe

Record when handles are assigned, which process owns them, every possible status, and all siblings that a broad cancellation could reach.

02

Decide

Use await for natural completion, kill for targeted cancellation, and a named wrapper process when a group must stop together.

03

Failure

A watchdog child's disable fork kills its own children, not its siblings; a parent's disable fork can kill unrelated background workers.

04

Prove

Assert handles are non-null before use, check status transitions, run winner and timeout paths, and verify no orphan remains after phase shutdown.

Publish handles before the controller races ahead

A forked child often assigns process::self() after it starts. The parent must wait for that publication or use a handshake before calling methods. With join_none, child startup itself is deferred until the parent yields.

Race work against timeout with targeted cleanupsystemverilog
process worker_p, timer_p;
event handles_ready;

fork
  begin
    worker_p = process::self();
    -> handles_ready;
    do_work();
  end
  begin
    timer_p = process::self();
    #100ns;
  end
join_none

wait (worker_p != null && timer_p != null);
wait (worker_p.status() == process::FINISHED ||
      timer_p.status()  == process::FINISHED);

if (worker_p.status() != process::FINISHED) worker_p.kill();
if (timer_p.status()  != process::FINISHED) timer_p.kill();
wait fork;

disable fork follows caller ownership

When a process executes disable fork, it terminates all active descendants spawned directly by that process, with cascading effects. If a watchdog is merely a sibling of workers, its own disable fork does not reach those siblings. Put the race inside an owning wrapper or retain handles.

Cancellation choices
MechanismTargetPrimary risk
process.kill()One known processHandle publication and resource cleanup
disable forkCaller's active child treeKills unrelated children
disable block_nameAll activations of named scope in reachLexical breadth and self-termination
Cooperative stop flagCode that checks itLatency or blocked wait
Mailbox shutdown tokenChannel consumerOne token per consumer or broadcast design

Cleanup must release resources

Killing a process does not automatically return a semaphore permit, retract a mailbox message, or restore shared state. Structure critical sections so cancellation occurs outside them, or add a controller-owned cleanup protocol.

  • Do not suspend a process while it owns a shared lock unless the design explicitly tolerates it.
  • Use get_randstate/set_randstate only for intentional reproducibility; process random state is part of debug determinism.
  • A disable of a named top scope terminates that scope, so a later display in the killed process does not execute.
  • Avoid nested initial blocks; initial is a module/program/interface construct, not a procedural statement.

Explain it out loud

Interview reasoning checkpoints

Strong answer

It kills all active child processes of the caller, not only the branches from the nearest-looking fork. That may include earlier background children and their descendants.

Reason it through
  1. Identify the process executing disable fork.
  2. Enumerate every live immediate child it owns.
  3. Use a wrapper or handles if the intended target is narrower.
Interviewer lens

A strong answer describes runtime parentage and broad scope.

Common trap

Assuming a watchdog sibling can use disable fork to kill its sibling workers.

Strong answer

First synchronize until the child has published a non-null handle, then call await() or inspect status. The process API method is await(), not join().

Reason it through
  1. Account for deferred child startup under join_none.
  2. Protect the handle publication with a wait, event plus state, or mailbox.
  3. After completion, verify resources and terminal status.
Interviewer lens

This tests both API knowledge and the initialization race.

Common trap

Calling await on a handle before the forked child assigns it.

06 · Integrated concurrency design

Design phases around persistent state, bounded waits, and owned cleanup.

Robust environments combine channels, permits, events, counters, and process handles. The goal is not maximum parallel syntax; it is deterministic progress under every legal interleaving.

Rule to say firstFor each parallel phase, specify entry, progress, completion, timeout, cancellation, and cleanup—and make completion persistent until every required observer acknowledges it.

Phase-control stack

Payload, progress, wakeup, and ownership are separate layers

  1. 01Payloadmailbox

    Ordered work and completion tokens.

  2. 02Resourcesemaphore

    Bounded parallel use of ports, models, or licenses.

  3. 03Progresscount / phase

    Persistent record of arrivals and completion.

  4. 04Wakeupevent

    Efficient notification after state changes.

  5. 05Ownershipprocess handles

    Targeted timeout and shutdown control.

Payloadmailbox

Ordered work and completion tokens.

Safety
Nothing bad happens
No double-consume, permit leak, shared-state race, or invalid access.
Liveness
Something good happens
Every accepted item completes or is explicitly cancelled.
Determinism
All legal schedules agree
Results do not depend on same-region process order.

Different primitives solve different dimensions. Combining them explicitly is clearer than asking one event or one disable statement to carry the whole protocol.

01

Observe

Build a wait-for graph containing processes, mailboxes, permits, event conditions, barriers, timeouts, and shutdown dependencies.

02

Decide

Use messages for payload, counters or barriers for N-way rendezvous, events for wakeup, and handles for ownership-specific cancellation.

03

Failure

A one-shot event loses a late worker, a barrier count never reaches N after a killed process, or a timeout kills the controller before its diagnostic can print.

04

Prove

Run systematic delay permutations, inject cancellation at every blocking point, assert bounded completion, and leave a final zero-live-process and zero-held-resource invariant.

A barrier is persistent progress plus notification

For N participants, store an arrival count or generation number under a synchronization policy. The last arrival advances the generation and triggers a wakeup. Waiters test the persistent generation before and after waiting so an early trigger cannot be lost.

Generation-based rendezvous sketchsystemverilog
class phase_barrier;
  local int target, arrived, generation;
  local semaphore lock = new(1);
  local event advanced;

  function new(int n); target = n; endfunction

  task wait_phase();
    int my_generation;
    lock.get();
    my_generation = generation;
    arrived++;
    if (arrived == target) begin
      arrived = 0;
      generation++;
      -> advanced;
    end
    lock.put();
    wait (generation != my_generation || advanced.triggered);
  endtask
endclass

Debug with absolute time and ownership

Write absolute wakeup times, event regions, and process parentage before reading printed output. For a sequence of #12, then #5, then #0 in one branch, completion times are t12, t17, and t17—not independent delays from t0.

Concurrency debug checklist
DimensionQuestionTypical symptom
TimeAbsolute or relative?Wrong output order prediction
RegionWho wakes in the same region?Nondeterministic captured value
TopologyWho spawned whom?wait/disable reaches wrong processes
PersistenceCan a late waiter observe state?Lost event and hang
CapacityCan every blocking put/get proceed?Terminal deadlock
CleanupWho releases permits and handles?Next test hangs

A timeout is a protocol participant

Place worker and timer under a controller that survives either outcome, records the winner, cancels the loser, releases resources, and reports. If the watchdog disables the controller's named scope from inside it, any reporting code after that disable is also terminated.

  • Use a heartbeat counter or timestamp for persistent health; an event alone can be missed.
  • Bound every wait in testbench infrastructure or pair it with a top-level watchdog that preserves diagnostics.
  • Record message IDs, process IDs, phase generation, and absolute time in debug logs.
  • At end of test, assert no outstanding mailbox work, no held permits, and no live owned processes.

Explain it out loud

Interview reasoning checkpoints

Strong answer

An event records no arrival count or generation and can be missed by late waiters. A barrier needs persistent progress state, with an event used only as a wakeup optimization.

Reason it through
  1. Count how many participants must arrive.
  2. Store the current generation and arrival count.
  3. Recheck persistent state after every wakeup.
Interviewer lens

The key distinction is notification versus remembered condition.

Common trap

Triggering one event N times and assuming each worker receives one trigger.

Strong answer

Define its safety and liveness invariants, remove same-region order dependencies, randomize delays and phase, inject boundary failures, and show every legal schedule yields the same externally visible result.

Reason it through
  1. Model communication and resource ownership separately.
  2. Assert counts, capacity, and bounded completion.
  3. Check final cleanup: no live process, held permit, or pending item.
Interviewer lens

Strong answers go beyond “run it many times” to invariants and schedule independence.

Common trap

Treating a stable result in one simulator seed as proof of race freedom.