DEV Community

Matheus de Camargo Marques
Matheus de Camargo Marques

Posted on

OTP Supervision Is Not a Pattern You Can Copy: Lessons from *Supervision Without Blocking* (2026)

OTP Supervision Is Not a Pattern You Can Copy: Lessons from Supervision Without Blocking (2026)

Actor frameworks for Rust, the JVM, and .NET all advertise “Erlang/OTP-style supervision.” It has become a headline feature, a selling point, a box to check. But a 2026 paper by Sam Uwejan and Erdogan Dogdu (Angelo State University), Supervision Without Blocking: An Evidence-Driven Redesign of OTP-Style Fault Tolerance for Cooperative Runtimes, delivers an uncomfortable finding: many of those implementations inherit OTP’s semantics without understanding the contracts that make them work — and that gap produces real, catalogued defects .

This article unpacks what the paper found, why OTP’s supervision guarantees are harder to port than they look, and what it means for anyone building fault-tolerant systems outside the BEAM.


The audit: 24 defect classes, one root cause

Uwejan and Dogdu did not set out to criticize ports. They set out to audit every supervision guarantee against OTP’s own documentation and source code, read verbatim at pinned versions, and then against the source of three further Rust actor frameworks at fixed revisions . Blind adversarial audits and the cross-framework study surfaced 24 distinct defect classes .

The striking result is not the number. It is the concentration: eight of the 24 defect classes reduce to a single root cause — “reconstructing, by explicit mechanism, the atomicity a synchronous supervisor gets implicitly” .

In other words: the ports copied the shape of OTP supervision without copying the substrate that makes the shape consistent.


Why OTP supervision works: the blocking supervisor

An OTP supervisor is a single-threaded process. It blocks for the duration of every restart and termination step it performs. Its consistency contracts — the guarantees that make “let it crash” safe — are silent consequences of that blocking .

Consider what this means concretely. When an OTP supervisor restarts a child:

  1. It receives the exit signal.
  2. It synchronously terminates the remaining children according to the shutdown strategy.
  3. It synchronously starts the new child.
  4. It only then returns to its message loop.

Because the supervisor is single-threaded and blocked, no other lifecycle message can interleave with those steps. The atomicity is not enforced by locks or transactions. It is a natural consequence of the supervisor doing one thing at a time and not processing its mailbox until that thing is done.

This is the contract that ports miss. A cooperative runtime like Tokio has an asynchronous scheduler. To keep the supervisor responsive, frameworks drop the blocking . But once the supervisor no longer blocks, the atomicity disappears — and the port must reconstruct it explicitly, or inherit the absence as “unexamined risk” .


The unpaid price: push-based lifecycle signaling

The paper identifies a second pattern across all four examined codebases: they independently adopted push-based lifecycle signaling, “whose losses and reorderings are the unpaid price of an asynchronous supervisor” .

In a blocking supervisor, lifecycle events are processed in order because the supervisor processes one message at a time. In an asynchronous supervisor, lifecycle signals are pushed to the supervisor while it may be busy doing something else. Signals can be lost under backpressure. They can be reordered relative to ordinary messages. And the supervisor may make decisions based on a lifecycle state that has already changed.

These are not hypothetical bugs. They are structural consequences of removing the blocking without replacing the atomicity it provided.


The redesign: paying for the trade deliberately

Uwejan and Dogdu do not argue that blocking is the only correct design. They argue that the trade must be priced. Their proposed runtime, ANON, closes all 24 catalogued defect classes through three mechanisms :

  • Supervisor-owned completion values. Instead of pushing lifecycle signals to the supervisor and hoping they arrive in order, the supervisor holds each child’s completion as a value returned by a task set it owns. Completion is pulled, not pushed — and pulling from an owned set preserves ordering by construction.
  • Per-incarnation identity. Every process instance carries an identity drawn from a monotonic counter. A late signal from a previous incarnation cannot be mistaken for a signal from the current one.
  • A single lifecycle state machine. All liveness bookkeeping collapses into one state machine with a single transition function. There are no ad-hoc flags to fall out of sync.

The result is validated by 241 tests, including regression reproductions that were verified to fail against pre-fix code by temporarily reverting the fix, then re-applied and observed to pass across 10 runs .


What ANON adds — and what it cannot

ANON holds OTP’s verbatim-verified contracts while adding two properties OTP does not offer :

  1. A supervisor that remains responsive throughout group restarts. Because it never blocks, it can answer status queries and manual commands even while a restart group is in progress.
  2. Message handlers that are never cancelled mid-execution. On the BEAM, an untrappable kill can arrive preemptively. ANON declines to imitate that, because cancelling a handler mid-execution drops local state at an arbitrary await point and exports a cancellation-safety obligation into every user handler .

But the paper is careful about the forcing boundary. Erlang’s kill is guaranteed by a virtual machine the language owns. An in-process runtime on a host language owns neither the scheduler nor the process. Its strongest primitive is task abort at a yield point. A handler that never yields defeats both the signal and the abort backstop. And an operating-system kill of the host runs no teardown at all .

This is the honest limit of any OTP-style supervision built outside the BEAM: some guarantees cannot be reconstructed, because the substrate does not permit them.


The academic connection: why this matters for teaching and for engineering

This paper connects directly to the pedagogical work of Staroletov (2026), which emphasizes that Erlang’s supervision model is not a set of APIs to be memorized but a set of contracts to be understood. The course described in that paper moves students from simple processes to OTP supervision to verification, treating each step as an exercise in reasoning about consistency, not just syntax.

Supervision Without Blocking makes the same point from the engineering side. Frameworks that copy the OTP style without evaluating which guarantees deserve keeping are not implementing supervision. They are implementing a resemblance of supervision . The 24 defect classes are the cost of that resemblance.

The lesson is not “never port OTP.” The lesson is: if you port it, you must reconstruct the atomicity explicitly — and you must know which guarantees your runtime can actually honor.


What this means for practitioners

If you are building on the BEAM, this paper reinforces a simple principle: use native OTP. The synchronous supervisor is not a legacy constraint. It is the mechanism that makes the consistency contracts true. Replacing it with an asynchronous variant inside Erlang would require the same reconstruction ANON performs — and OTP already does it correctly.

If you are evaluating an actor framework on another runtime, ask three questions:

  • What happens to lifecycle signals under backpressure? If the answer involves “push” without an owned completion value, ordering is not guaranteed.
  • How does the framework distinguish incarnations? Without per-incarnation identity, a late signal from a dead process can be mistaken for a live one.
  • What atomicity does the supervisor provide? If the supervisor is asynchronous and does not explicitly reconstruct atomicity, the consistency contracts are not the same as OTP’s — regardless of what the README says.

The paper’s contribution is to make those questions askable, with evidence behind each answer.


Conclusion: contracts, not cosmetics

OTP supervision looks simple. A supervisor starts children, restarts them when they die, and escalates when it cannot recover. The simplicity is real — but it is earned by a runtime that blocks. Ports that remove the blocking without replacing the atomicity are not simplifying supervision. They are removing the foundation and hoping the building stays up.

Uwejan and Dogdu’s audit shows what happens when it does not. The 24 defect classes are not exotic edge cases. They are the predictable consequences of copying a pattern without its substrate. And the fix is not a patch — it is a redesign that treats every guarantee as a design decision that must earn its place.

That is a lesson that applies far beyond Erlang.

Main reference: UWEJAN, Sam; DOGDU, Erdogan. Supervision Without Blocking: An Evidence-Driven Redesign of OTP-Style Fault Tolerance for Cooperative Runtimes. arXiv:2610.34521 [cs.DC], 25 Oct 2026. Available at: https://arxiv.org/abs/2610.34521

Related pedagogical context: STAROLETOV, Sergey. Modern Erlang Teaching from Code Refactoring to Actor System Verification. In: Proceedings of the 25th ACM SIGPLAN International Workshop on Erlang (Erlang ‘26), 2026. DOI: 10.1145/3830434.3830946


Have you evaluated an “OTP-style” framework on a non-BEAM runtime? What guarantees did it actually provide — and what did you have to reconstruct yourself? Let me know in the comments.

Top comments (0)