Argus.Analyses.MonitorLeak (Panoptes v0.13.0)

Copy Markdown View Source

A monitor whose {:DOWN, ...} nobody is waiting for any more.

Process.monitor/1 is a promise the runtime keeps: a {:DOWN, ref, :process, object, reason} will arrive unless cancelled. Waiting for it in a receive with an after clause means the wait can end without the message — and the monitor is still live, so it arrives later, into a callback that has moved on, carrying a reason that no longer means anything.

Process.demonitor(ref) does not fix it. A {:DOWN, ...} already sent stays in the mailbox; only Process.demonitor(ref, [:flush]) removes it.

The timeout is the whole discriminator

A receive with no after consumes either the reply or the :DOWN and cannot leak. Every monitor-plus-receive in Livebook is that shape, and none is reported — which is what makes the one that is reported worth reading. The wait may sit a call below the monitor, in the same module and process; closure edges do not count.

Monitors over a server's lifetime

Two further shapes are about the process rather than one function, and are reported at :info because they are heuristics over a module's callbacks rather than proofs about one path:

  • monitor_never_released — a callback-loop module monitors from its callbacks, removes entries from its bookkeeping somewhere, and calls Process.demonitor nowhere. Postgrex's Parameters server.
  • monitor_ref_discarded — a callback calls Process.monitor/1 and drops the ref, so the monitor can only end with the monitored process. Phoenix PubSub's Local, for every subscriber.
  • deliberate_termination_while_monitored — the module terminates a child or stops a server it monitors, without demonitoring first, so the {:DOWN, ...} for a death it caused arrives in the clause written for crashes. Oban's producer on pkill; Redix's cluster manager on a departed node.