From 01c4c42e117636cdba2d5278c851fbe7a3b39bc0 Mon Sep 17 00:00:00 2001 From: Michal Muskala Date: Fri, 13 May 2016 20:51:08 +0200 Subject: Monitor owner of the connection The connection process should monitor the process that started it and die with the same reason that the owner process did. This should solve the issue of zombie connections laying around after the processes that started them die. --- lib/exirc/client.ex | 13 +++++++++++-- lib/exirc/exirc.ex | 2 +- test/client_test.exs | 25 ++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/exirc/client.ex b/lib/exirc/client.ex index cde4f1a..81a2f0a 100644 --- a/lib/exirc/client.ex +++ b/lib/exirc/client.ex @@ -32,7 +32,8 @@ defmodule ExIrc.Client do channels: [], debug?: false, retries: 0, - inet: :inet + inet: :inet, + owner: nil end ################# @@ -55,6 +56,7 @@ defmodule ExIrc.Client do """ @spec start_link(options :: list() | nil, process_opts :: list() | nil) :: {:ok, pid} | {:error, term} def start_link(options \\ [], process_opts \\ []) do + options = Keyword.put_new(options, :owner, self()) GenServer.start_link(__MODULE__, options, process_opts) end @doc """ @@ -285,17 +287,20 @@ defmodule ExIrc.Client do def init(options \\ []) do autoping = Keyword.get(options, :autoping, true) debug = Keyword.get(options, :debug, false) + owner = Keyword.fetch!(options, :owner) # Add event handlers handlers = Keyword.get(options, :event_handlers, []) |> List.foldl([], &do_add_handler/2) + ref = Process.monitor(owner) # Return initial state {:ok, %ClientState{ event_handlers: handlers, autoping: autoping, logged_on?: false, debug?: debug, - channels: ExIrc.Channels.init()}} + channels: ExIrc.Channels.init(), + owner: {owner, ref}}} end @doc """ Handle calls from the external API. It is not recommended to call these directly. @@ -497,6 +502,10 @@ defmodule ExIrc.Client do def handle_info({:ssl, socket, data}, state) do handle_info({:tcp, socket, data}, state) end + # If the owner process dies, we should die as well + def handle_info({:DOWN, ref, _, pid, reason}, %{owner: {pid, ref}} = state) do + {:stop, reason, state} + end # If an event handler process dies, remove it from the list of event handlers def handle_info({:DOWN, _, _, pid, _}, state) do handlers = do_remove_handler(pid, state.event_handlers) diff --git a/lib/exirc/exirc.ex b/lib/exirc/exirc.ex index cfa2654..72be33c 100644 --- a/lib/exirc/exirc.ex +++ b/lib/exirc/exirc.ex @@ -50,7 +50,7 @@ defmodule ExIrc do @spec start_client! :: {:ok, pid} | {:error, term} def start_client! do # Start the client worker - Supervisor.start_child(:exirc, []) + Supervisor.start_child(:exirc, [[owner: self()]]) end ############## diff --git a/test/client_test.exs b/test/client_test.exs index 6cff0b7..b7277b6 100644 --- a/test/client_test.exs +++ b/test/client_test.exs @@ -1,11 +1,30 @@ defmodule ExIrc.ClientTest do use ExUnit.Case - test "start multiple clients" do - {:ok, pid} = ExIrc.start_client! - {:ok, pid2} = ExIrc.start_client! + assert {:ok, pid} = ExIrc.start_client! + assert {:ok, pid2} = ExIrc.start_client! assert pid != pid2 end + test "client dies if owner process dies" do + test_pid = self() + + pid = spawn_link(fn -> + assert {:ok, pid} = ExIrc.start_client! + send(test_pid, {:client, pid}) + receive do + :stop -> :ok + end + end) + + client_pid = receive do + {:client, pid} -> pid + end + + assert Process.alive?(client_pid) + send(pid, :stop) + :timer.sleep(1) + refute Process.alive?(client_pid) + end end -- cgit v1.2.3