mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-08 05:54:20 +08:00
Add dynamic supervisor for session processes
This commit is contained in:
parent
5877180934
commit
591476f618
6 changed files with 208 additions and 4 deletions
|
@ -12,9 +12,9 @@ defmodule LiveBook.Application do
|
|||
# Start the PubSub system
|
||||
{Phoenix.PubSub, name: LiveBook.PubSub},
|
||||
# Start the Endpoint (http/https)
|
||||
LiveBookWeb.Endpoint
|
||||
# Start a worker by calling: LiveBook.Worker.start_link(arg)
|
||||
# {LiveBook.Worker, arg}
|
||||
LiveBookWeb.Endpoint,
|
||||
# Start the supervisor dynamically managing sessions
|
||||
LiveBook.SessionSupervisor
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
|
|
47
lib/live_book/session.ex
Normal file
47
lib/live_book/session.ex
Normal file
|
@ -0,0 +1,47 @@
|
|||
defmodule LiveBook.Session do
|
||||
@moduledoc """
|
||||
Server corresponding to a single notebook session.
|
||||
|
||||
The process keeps the current notebook state and serves
|
||||
as a source of truth that multiple clients talk to.
|
||||
Receives update requests from the clients and notifies
|
||||
them of any changes applied to the notebook.
|
||||
"""
|
||||
|
||||
use GenServer, restart: :temporary
|
||||
|
||||
@typedoc """
|
||||
A UUID assigned to every running session process.
|
||||
"""
|
||||
@type session_id :: String.t()
|
||||
|
||||
## API
|
||||
|
||||
@doc """
|
||||
Starts the server process and registers it globally using the `:global` module,
|
||||
so that it's identifiable by the given id.
|
||||
"""
|
||||
@spec start_link(session_id()) :: GenServer.on_start()
|
||||
def start_link(session_id) do
|
||||
GenServer.start_link(__MODULE__, [session_id: session_id], name: name(session_id))
|
||||
end
|
||||
|
||||
defp name(session_id) do
|
||||
{:global, {:session, session_id}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Synchronously stops the server.
|
||||
"""
|
||||
@spec stop(session_id()) :: :ok
|
||||
def stop(session_id) do
|
||||
GenServer.stop(name(session_id))
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
@impl true
|
||||
def init(session_id: _id) do
|
||||
{:ok, %{}}
|
||||
end
|
||||
end
|
85
lib/live_book/session_supervisor.ex
Normal file
85
lib/live_book/session_supervisor.ex
Normal file
|
@ -0,0 +1,85 @@
|
|||
defmodule LiveBook.SessionSupervisor do
|
||||
@moduledoc """
|
||||
Supervisor responsible for managing running notebook sessions.
|
||||
|
||||
Allows for creating new session processes on demand
|
||||
and managing them using UUIDs.
|
||||
"""
|
||||
|
||||
use DynamicSupervisor
|
||||
|
||||
alias LiveBook.Session
|
||||
|
||||
@name __MODULE__
|
||||
|
||||
def start_link(opts \\ []) do
|
||||
DynamicSupervisor.start_link(__MODULE__, opts, name: @name)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
DynamicSupervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Spawns a new session process.
|
||||
|
||||
Broadcasts `{:session_created, id}` message under the `"sessions"` topic.
|
||||
"""
|
||||
@spec create_session() :: {:ok, Session.session_id()} | {:error, any()}
|
||||
def create_session() do
|
||||
id = UUID.uuid4()
|
||||
|
||||
case DynamicSupervisor.start_child(@name, {Session, id}) do
|
||||
{:ok, _} ->
|
||||
broadcast_sessions_message({:session_created, id})
|
||||
{:ok, id}
|
||||
|
||||
{:ok, _, _} ->
|
||||
broadcast_sessions_message({:session_created, id})
|
||||
{:ok, id}
|
||||
|
||||
:ignore ->
|
||||
{:error, :ignore}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Synchronously stops a session process identified by the given id.
|
||||
|
||||
Broadcasts `{:session_delete, id}` message under the `"sessions"` topic.
|
||||
"""
|
||||
@spec delete_session(Session.session_id()) :: :ok
|
||||
def delete_session(id) do
|
||||
Session.stop(id)
|
||||
broadcast_sessions_message({:session_deleted, id})
|
||||
:ok
|
||||
end
|
||||
|
||||
defp broadcast_sessions_message(message) do
|
||||
Phoenix.PubSub.broadcast(LiveBook.PubSub, "sessions", message)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns ids of all the running session processes.
|
||||
"""
|
||||
@spec get_session_ids() :: list(Session.session_id())
|
||||
def get_session_ids() do
|
||||
:global.registered_names()
|
||||
|> Enum.flat_map(fn
|
||||
{:session, id} -> [id]
|
||||
_ -> []
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a session process with the given id exists.
|
||||
"""
|
||||
@spec session_exists?(Session.session_id()) :: boolean()
|
||||
def session_exists?(id) do
|
||||
:global.whereis_name({:session, id}) != :undefined
|
||||
end
|
||||
end
|
3
mix.exs
3
mix.exs
|
@ -41,7 +41,8 @@ defmodule LiveBook.MixProject do
|
|||
{:telemetry_metrics, "~> 0.4"},
|
||||
{:telemetry_poller, "~> 0.4"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:plug_cowboy, "~> 2.0"}
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
{:elixir_uuid, "~> 1.2"}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -2,6 +2,7 @@
|
|||
"cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
|
||||
"cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
|
||||
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
|
||||
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
||||
"floki": {:hex, :floki, "0.29.0", "b1710d8c93a2f860dc2d7adc390dd808dc2fb8f78ee562304457b75f4c640881", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "008585ce64b9f74c07d32958ec9866f4b8a124bf4da1e2941b28e41384edaaad"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
||||
|
|
70
test/live_book/session_supervisor_test.exs
Normal file
70
test/live_book/session_supervisor_test.exs
Normal file
|
@ -0,0 +1,70 @@
|
|||
defmodule LiveBook.SessionSupervisorTest do
|
||||
use ExUnit.Case
|
||||
|
||||
alias LiveBook.SessionSupervisor
|
||||
|
||||
setup do
|
||||
on_exit(fn ->
|
||||
# Start a fresh SessionSupervisor for each test
|
||||
Supervisor.terminate_child(LiveBook.Supervisor, LiveBook.SessionSupervisor)
|
||||
Supervisor.restart_child(LiveBook.Supervisor, LiveBook.SessionSupervisor)
|
||||
end)
|
||||
end
|
||||
|
||||
describe "create_session/0" do
|
||||
test "creates a new session process and returns its id" do
|
||||
{:ok, id} = SessionSupervisor.create_session()
|
||||
|
||||
assert [{_, pid, _, [LiveBook.Session]}] =
|
||||
DynamicSupervisor.which_children(SessionSupervisor)
|
||||
|
||||
assert pid == :global.whereis_name({:session, id})
|
||||
end
|
||||
|
||||
test "broadcasts a message" do
|
||||
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions")
|
||||
{:ok, id} = SessionSupervisor.create_session()
|
||||
|
||||
assert_receive {:session_created, ^id}
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_session/1" do
|
||||
test "stops the session process identified by the given id" do
|
||||
{:ok, id} = SessionSupervisor.create_session()
|
||||
|
||||
SessionSupervisor.delete_session(id)
|
||||
|
||||
assert [] = DynamicSupervisor.which_children(SessionSupervisor)
|
||||
end
|
||||
|
||||
test "broadcasts a message" do
|
||||
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions")
|
||||
{:ok, id} = SessionSupervisor.create_session()
|
||||
|
||||
SessionSupervisor.delete_session(id)
|
||||
|
||||
assert_receive {:session_deleted, ^id}
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_session_ids/0" do
|
||||
test "lists ids identifying sessions running under the supervisor" do
|
||||
{:ok, id} = SessionSupervisor.create_session()
|
||||
|
||||
assert SessionSupervisor.get_session_ids() == [id]
|
||||
end
|
||||
end
|
||||
|
||||
describe "session_exists?/1" do
|
||||
test "returns true if a session process with the given id exists" do
|
||||
{:ok, id} = SessionSupervisor.create_session()
|
||||
|
||||
assert SessionSupervisor.session_exists?(id)
|
||||
end
|
||||
|
||||
test "returns false if a session process with the given id does not exist" do
|
||||
refute SessionSupervisor.session_exists?("nonexistent")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue