Argus.Analyses.ReplyContract (Panoptes v0.13.0)

Copy Markdown View Source

A deferred reply that can never be sent.

handle_call/3 may answer immediately with {:reply, value, state}, or defer: return {:noreply, state} and call GenServer.reply/2 later. The second is a promise, and the only thing that can discharge it is the from term the callback was handed — an opaque {pid, tag} that exists nowhere else in the system.

A handle_call/3 that defers without keeping from has promised something it cannot deliver. No later event fixes it.

Why this needs an analysis

Not because the mistake is subtle, but because of where it surfaces. The process that got it wrong is fine — it returned a valid value and went back to its loop. The failure appears five seconds later, in a different process, in a different module:

** (exit) exited in: GenServer.call(pid, :thing, 5000)
     ** (EXIT) time out

Nothing in that message names the clause that failed to reply. Worse, under load it is indistinguishable from overload, which sends people to tune pool sizes and mailbox depths for a bug that has nothing to do with either.

Precision

Deliberately a floor rather than a census. Retaining from is recorded per function, not per clause, so a handle_call/3 whose other clauses defer correctly mentions the register and every clause in it goes unreported. Reporting is therefore rare and each report is strong.