Rbtz.PostgrexDisconnectTracker

CIHex versionHex downloadsLicense

Pins Postgrex disconnection errors to the ExUnit test that caused them.

When a test leaks a Postgrex connection — e.g. a spawned task outlives the test, a sandbox owner exits mid-query, or a driver crash hits the pool — the error logged by Postgrex looks like this:

[error] Postgrex.Protocol (#PID<0.512.0>) disconnected: ** (DBConnection.ConnectionError) client #PID<0.1234.0> exited

The message names the client PID, but that PID no longer exists and has no connection back to the test suite. You're left grepping for which test touched the affected code. In a CI run of a thousand tests that can be a bad afternoon.

This library attaches a telemetry handler to your repo and an OTP :logger handler, and maintains an ETS map from query-executing PIDs (and their ancestors) to the test that started them. When Postgrex logs a disconnect, the handler parses the client PID, looks up the owning test, and prints an annotated error like:

=== POSTGREX DISCONNECTION ===
Test: MyApp.AccountsTest - test registers a user
Client PID: #PID<0.1234.0>
Stacktrace: ...
Error: ...
==============================

Why / when to use this

Use it when:

Don't use it when:

It's a diagnostic aid, not a fix. Once it points you at the offending test, you still need to patch that test (close the connection, wait for the task, re-scope the sandbox, etc.).

Installation

Add rbtz_postgrex_disconnect_tracker to your mix.exs test deps:

def deps do
  [
    {:rbtz_postgrex_disconnect_tracker, "~> 0.1", only: :test}
  ]
end

Run mix deps.get.

Setup (recommended)

One call in test/test_helper.exs, before ExUnit.start/1:

Rbtz.PostgrexDisconnectTracker.setup(telemetry_event: [:my_app, :repo, :query])

ExUnit.start()

:telemetry_event is the Ecto query telemetry event for your repo — usually [:<your_otp_app>, :repo, :query]. Ecto fires it on every query; the tracker uses it to learn which process is running queries for which test.

Then in your DataCase (or whichever ExUnit.CaseTemplate sets up the sandbox), register the current test at the start of each setup:

defmodule MyApp.DataCase do
  use ExUnit.CaseTemplate

  setup tags do
    Rbtz.PostgrexDisconnectTracker.register_test("#{tags.module} - #{tags.test}")

    pid = Ecto.Adapters.SQL.Sandbox.start_owner!(MyApp.Repo, shared: not tags[:async])
    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)

    :ok
  end
end

That's it. When a disconnect is logged, you'll see the test name in the output.

Setup (individual functions)

If you need to wire the pieces yourself — for example, to attach the logger on a different subset of tests, or to hold off on the telemetry handler until a suite-level setup — call them separately instead of setup/1:

Rbtz.PostgrexDisconnectTracker.Tracker.init(telemetry_event: [:my_app, :repo, :query])
Rbtz.PostgrexDisconnectTracker.Logger.attach()

setup/1 is exactly equivalent to these two calls in this order.

License

MIT. See LICENSE.