Argus.Analyses.ShutdownSafety (Panoptes v0.13.0)

Copy Markdown View Source

Cleanup in terminate/2 that will not run when it matters.

Another contract nobody writes down. Putting cleanup in terminate/2 looks like saying "run this on the way out". OTP's actual rule is narrower, and the gap is silent:

terminate/2 runs when a callback returns {:stop, ...}, or raises. On a supervisor shutdown — which is how processes normally stop, and the case the cleanup was written for — the parent sends an exit signal, and a process that is not trapping exits simply dies. terminate/2 is never called, nothing is logged, and the buffer is not flushed.

This is documented GenServer behaviour and still one of the most reliably-made mistakes on the BEAM, because the code reads correctly and the tests pass: a test calling GenServer.stop/1 exercises the path that does run terminate, so the one path that matters in production is the one never exercised.

A second obligation applies once you are trapping: terminate/2 must finish inside the child's shutdown timeout (5000 ms by default) or the supervisor brutal-kills it and the cleanup is truncated anyway.

Scope

Writes only, and not logging. A terminate/2 that logs "shutting down" loses nothing when skipped; one that flushes a buffer, releases a lease, or tells another system it is going away loses something real. That distinction comes from the mode dimension in Argus.Purity.Effects — the same one that keeps config reads out of the transaction analysis.