Skip to content

Tensions in Practice: Implicit return ownership in tension with ending the initiating call

Asynchronous work · one awaited event

A task pauses until an event arrives. A synchronous function can keep its call frame open and resume the remaining work there. An asynchronous design instead records that remaining work as an explicit object, returns from the initiating call, and later runs a callback. The work has not vanished; responsibility for resumption and cleanup has moved out of that call’s lifetime.

Keep return and cleanup nested

Let one open call scope own the pending continuation and its cleanup.

End the initiating call

Let the initiating function return while the event and remaining work are still outstanding.

Why these aims pull against each other

Returning early removes the live call frame that would otherwise track the obligation. The pending task must retain its own control state and cleanup responsibility.

Compare the arrangements

Retain the call

The call stays open during the wait; the task resumes in that frame or unwinds it on failure.

The call retains the continuation
CallWorkCleanup
WaitOpenCall frameCall scope
EventActiveSame frameCall scope
FailureUnwindsStoppedCall scope
What it protects
Return and failure cleanup stay attached to the same call scope.
What it costs
The original call cannot return during the wait and its pending state remains retained.
When it fits
Waiting within the call’s lifetime is acceptable and the required obligations genuinely nest.

Illustration note: The finite setting and values are editorial assumptions, not measured effects or recommended operating settings. “Waiting” does not assert that a physical processor spins or that an operating-system thread must remain busy.

Retain a task

Create a task object holding the continuation and cleanup responsibility, then return from the initiating call. An event handler later runs its callback.

The continuation survives the call
CallWorkCleanup
WaitCall endedWork objectTask scope
EventCall endedCallbackTask scope
FailureCall endedStoppedTask scope
What it protects
The initiating call’s lifetime is no longer extended by the event wait.
What it costs
The task object needs explicit retention, dispatch and failure cleanup; unwinding the already-returned call cannot perform that cleanup.
When it fits
An event dispatcher and task-lifecycle contract can own the work after the initiating call has ended.

Illustration note: The finite setting and values are editorial assumptions, not measured effects or recommended operating settings. The task-scope cleanup contract is editorial and must be implemented; creating a callback does not supply it automatically.

What this illustration does—and does not—establish

Stack: Explicit Stack versus Reified Continuation (coupling) supplies reified continuation and the loss of automatic call-stack cleanup. The finite lifecycle table makes the changed owner explicit.

  • Both options retain enough information to resume; no memory saving or timing guarantee is claimed.
  • The table shows one event and one continuation, not a complete cancellation or coroutine implementation.
  • Task failure means abandoning this remaining computation after its required cleanup; irreversible effects are not undone merely by either scope ending.
  • Ending this call does not imply that its caller, thread or process has ended.

Source entries

Stack

Prime · Source of the tension

Stack: Explicit Stack versus Reified Continuation (coupling) supplies the conflict examined here.

Explicit Stack versus Reified Continuation (coupling)

Stack discipline can be traded for explicit continuations or an event-driven work-list, decoupling the rest-of-computation from a single LIFO spine.

Read the source section

Structural Tensions

The failure is assuming stack-based reasoning (a clean caller/callee return) in a system that has gone event-driven — control returns to no caller, and the implicit unwinding the stack provided no longer happens.

Read the source section