Current section
Files
Jump to
Current section
Files
lib/phoenix/socket_client/channel_manager.ex
defmodule Phoenix.SocketClient.ChannelManager do
@moduledoc """
Dynamic supervisor for managing channel processes.
This module supervises individual channel processes, handling their lifecycle
from creation to termination. It provides functionality for:
- Starting and supervising channel processes
- Finding channel processes by topic
- Managing channel lifecycle events
## Channel Lifecycle
Channels are started as child processes under this supervisor when:
- A client calls `Phoenix.SocketClient.Channel.join/3`
- The socket connection is active and the channel join is successful
Channels are terminated when:
- The channel is left explicitly via `Channel.leave/1`
- The socket connection is lost
- The supervisor terminates
"""
use DynamicSupervisor
alias Phoenix.SocketClient.Channel
import Phoenix.SocketClient, only: [get_process_pid: 2, get_state: 2]
@doc """
Starts the ChannelManager dynamic supervisor.
## Parameters
* `opts` - Configuration options (unused, required by DynamicSupervisor)
## Examples
{:ok, pid} = Phoenix.SocketClient.ChannelManager.start_link([])
"""
@spec start_link(keyword() | map()) :: {:ok, pid()} | {:error, term()}
def start_link(opts) do
opts = if Keyword.keyword?(opts), do: opts, else: Map.to_list(opts)
DynamicSupervisor.start_link(__MODULE__, opts)
end
@doc """
Finds the PID of a channel process by its topic.
## Parameters
* `sup_pid` - The supervisor PID
* `topic` - The channel topic to search for
## Returns
* `pid` - The channel PID if found
* `nil` - If no channel with the given topic exists
## Examples
channel_pid = Phoenix.SocketClient.ChannelManager.channel_pid(sup_pid, "rooms:lobby")
"""
@spec channel_pid(pid(), String.t()) :: pid() | nil
def channel_pid(sup_pid, topic) when is_pid(sup_pid) do
try do
case Process.alive?(sup_pid) do
false ->
nil
true ->
sup_pid
|> Supervisor.which_children()
|> Enum.find_value(fn
{_id, process_pid, _, _} ->
# id - it is always :undefined for dynamic supervisors
if :sys.get_state(process_pid).topic == topic do
process_pid
else
nil
end
_ ->
nil
end)
end
rescue
ArgumentError -> nil
_ -> nil
end
end
def start_channel(sup_pid, topic, params, channel_module \\ Channel) do
socket_pid = get_process_pid(sup_pid, :socket)
cm_pid = get_process_pid(sup_pid, :channel_manager)
registry_name = get_state(sup_pid, :registry_name)
channel_module =
(get_state(sup_pid, :topic_channel_map) || %{})
|> Map.get(topic, channel_module)
spec =
{channel_module, {sup_pid, socket_pid, topic, params, registry_name}}
|> Supervisor.child_spec(id: topic)
case DynamicSupervisor.start_child(cm_pid, spec) do
{:ok, channel_pid} ->
Phoenix.SocketClient.update_channel_status(sup_pid, topic, :joining, params)
{:ok, channel_pid}
{:error, {:already_started, channel_pid}} ->
{:error, {:already_started, channel_pid}}
{:error, {:already_started, _, channel_pid}} ->
{:error, {:already_started, channel_pid}}
{:error, {:already_registered, channel_pid}} ->
{:error, {:already_started, channel_pid}}
error ->
error
end
end
def terminate_channel(pid, channel) do
DynamicSupervisor.terminate_child(pid, channel)
end
def init(_opts) do
DynamicSupervisor.init(strategy: :one_for_one)
end
def terminate(cm_pid) when is_pid(cm_pid) do
DynamicSupervisor.which_children(cm_pid)
|> Enum.each(fn {_id, process_pid, _, _} ->
DynamicSupervisor.terminate_child(cm_pid, process_pid)
end)
end
end