Volume II / CHAPTER 04
For understanding and contributing
When an operation has not finished
Closing the window does not make a pending read disappear. That is where concurrency questions begin.
Notation conventions
Function names, types, commands, options and filenames appear in monospace, for example describe, wf test or main.cpp. Italics introduce a term at its definition. Code blocks give the fragments to enter; the preceding text states their location and prerequisites.
While we wait
A connected browser may remain silent for a long time. Devoting a blocked thread to each such wait would be costly. HTTP and WebSocket loops therefore use coroutines, which suspend when waiting for a network-provider operation.
Suspension does not create a new thread. A long calculation in a bridge callback still occupies the server context. Other operations depending on that context wait their turn. Likewise, a blocking wait there can prevent the very reply it awaits from making progress.
Before moving work or sharing data, it helps to locate the context that will execute each part. Tasks started by WebFront resume on the server context; the frontend follows its toolkit’s constraints.
Keeping track of work we started
Knowing that an operation exists is not enough; we also need to know who will wait for it to finish. The server groups network operations and application tasks in a networking::TaskScope, based on a counting_scope.
An application task returns webfront::Task. After spawn, the application owns it. A TaskHandle can request stopping, but destroying the handle does not delete the task. Otherwise, discarding a handle could make work disappear while other objects still awaited its result.
If we use WebSocket or WebLink directly, we must supply an external scope. It must live long enough for tasks to finish their last accesses to connections and dependencies.
Closing without releasing too early
Now imagine the window closing while a read is pending. We want that read to finish and then release the objects it used. Reversing the order would let the operation resume with dangling references.
stop() requests stopping. shutdown() requests stopping and waits for owned work to finish. Call it outside server callbacks, after external callers of run() or runOne() have returned.
A request may come from another thread, but that does not authorise it to close network objects. Those objects complete their operations on their own context. Draining retains owners and advances that context until joining completes.
The reply arrives too late
A browser call may still be running when its C++ task is stopped. The sender then completes the local wait. JavaScript already launched continues, and a late reply is discarded. Ordinary futures and promises do not expose this cancellation.
The result and stop request may also arrive almost together. We need exactly one completion, whichever comes first. A test that imposes both orderings teaches us more than one hoping to encounter the race by chance.
Finally, stopping assumes that the operation can cooperate. One that never finishes can block destruction. Explicit shutdown reports a join that cannot progress; the destructor retains the owners and waits.
Check your understanding