I think I found a memory leak in Phoenix.PubSub.Local with monitor refs and long running subscribers that join topics frequently.
causing the Local GenServer to create a new monitor for the subscriber pid regardless of whether it's already monitoring that pid as a result of an earlier subscription from another (or the same) topic.
The ref isn't captured when it's created and isn't demonitored when the subscriber unsubscribes from the topic. It appears that the only way these monitors are cleaned up is if the subscriber or the Local GenServer process exits.
Below is an iex session using phoenix_pubsub master demonstrating the issue.
Interactive Elixir (1.2.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {:ok, pubsub} = Phoenix.PubSub.LocalSupervisor.start_link(:monitor_leak_test, 1, [])
{:ok, #PID<0.128.0>}
iex(2)> [{_, shard?, _, _}] = Supervisor.which_children(pubsub)
[{0, #PID<0.129.0>, :supervisor, [Supervisor]}]
iex(3)> [{Phoenix.PubSub.Local, local, _, _}, _gc] = Supervisor.which_children(shard?)
[{Phoenix.PubSub.Local, #PID<0.131.0>, :worker, [Phoenix.PubSub.Local]},
{Phoenix.PubSub.GC, #PID<0.130.0>, :worker, [Phoenix.PubSub.GC]}]
iex(4)> subscriber = spawn fn -> receive do end end
#PID<0.135.0>
iex(5)> :erlang.process_info(local, :memory)
{:memory, 2824}
iex(6)> (1..10000) |> Enum.each(fn(_) -> Phoenix.PubSub.subscribe(:monitor_leak_test, subscriber, "foo") && Phoenix.PubSub.unsubscribe(:monitor_leak_test, subscriber, "foo") end)
:ok
iex(7)> :erlang.process_info(local, :memory)
{:memory, 736752}
iex(8)> :erlang.garbage_collect(local)
true
iex(9)> :erlang.process_info(local, :memory)
{:memory, 722824}
iex(10)> Process.exit(subscriber, :kill)
true
iex(11)> :erlang.garbage_collect(local)
true
iex(12)> :erlang.process_info(local, :memory)
{:memory, 2824}