Stabilize preview and sandbox cleanup
This commit is contained in:
@@ -4,6 +4,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
|
||||
use Phoenix.Component
|
||||
|
||||
alias BDS.Metadata
|
||||
alias BDS.Preview
|
||||
use Gettext, backend: BDS.Gettext
|
||||
|
||||
@themes [
|
||||
@@ -42,7 +43,8 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
|
||||
applied_theme: current_theme(assigns),
|
||||
preview_mode: preview_mode,
|
||||
preview_url:
|
||||
"http://127.0.0.1:4123/__style-preview?theme=#{selected_theme}&mode=#{preview_mode}"
|
||||
Preview.base_url() <>
|
||||
"/__style-preview?theme=#{selected_theme}&mode=#{preview_mode}"
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -12,12 +12,14 @@ defmodule BDS.Preview do
|
||||
alias BDS.Rendering.TemplateSelection
|
||||
|
||||
@host "127.0.0.1"
|
||||
@port 4123
|
||||
@preferred_port 4123
|
||||
@server_table __MODULE__.ServerTable
|
||||
|
||||
# Max time to wait for inflight requests to finish during graceful shutdown
|
||||
# before remaining request tasks are forcibly terminated.
|
||||
@drain_timeout 5_000
|
||||
@listen_retry_attempts 10
|
||||
@listen_retry_delay_ms 50
|
||||
|
||||
def start_link(_opts) do
|
||||
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
|
||||
@@ -41,7 +43,17 @@ defmodule BDS.Preview do
|
||||
)
|
||||
end
|
||||
|
||||
def base_url, do: "http://#{@host}:#{@port}"
|
||||
def base_url do
|
||||
case :ets.whereis(@server_table) do
|
||||
:undefined -> "http://#{@host}:#{@preferred_port}"
|
||||
|
||||
_table ->
|
||||
case :ets.lookup(@server_table, :current) do
|
||||
[{:current, %{host: host, port: port}}] -> "http://#{host}:#{port}"
|
||||
_other -> "http://#{@host}:#{@preferred_port}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stop_preview(project_id) when is_binary(project_id) do
|
||||
GenServer.call(__MODULE__, {:stop_preview, project_id})
|
||||
@@ -501,14 +513,7 @@ defmodule BDS.Preview do
|
||||
state = stop_current_server(state)
|
||||
maybe_allow_repo(owner_pid)
|
||||
|
||||
{:ok, listener} =
|
||||
:gen_tcp.listen(@port, [
|
||||
:binary,
|
||||
packet: :raw,
|
||||
active: false,
|
||||
reuseaddr: true,
|
||||
ip: {127, 0, 0, 1}
|
||||
])
|
||||
{:ok, listener, port} = listen_with_retry(@listen_retry_attempts)
|
||||
|
||||
acceptor_pid = spawn_link(fn -> accept_loop(listener, project_id, owner_pid) end)
|
||||
|
||||
@@ -516,7 +521,7 @@ defmodule BDS.Preview do
|
||||
project_id: project_id,
|
||||
data_dir: data_dir,
|
||||
host: @host,
|
||||
port: @port,
|
||||
port: port,
|
||||
is_running: true,
|
||||
owner_pid: owner_pid,
|
||||
listener: listener,
|
||||
@@ -528,6 +533,40 @@ defmodule BDS.Preview do
|
||||
{{:ok, public_server(server)}, %{state | current: server}}
|
||||
end
|
||||
|
||||
defp listen_with_retry(attempts_left) when attempts_left > 0 do
|
||||
case listen_on_port(@preferred_port) do
|
||||
{:ok, listener, port} ->
|
||||
{:ok, listener, port}
|
||||
|
||||
{:error, :eaddrinuse} when attempts_left > 1 ->
|
||||
Process.sleep(@listen_retry_delay_ms)
|
||||
listen_with_retry(attempts_left - 1)
|
||||
|
||||
{:error, :eaddrinuse} ->
|
||||
listen_on_port(0)
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp listen_on_port(port) do
|
||||
case :gen_tcp.listen(port, [
|
||||
:binary,
|
||||
packet: :raw,
|
||||
active: false,
|
||||
reuseaddr: true,
|
||||
ip: {127, 0, 0, 1}
|
||||
]) do
|
||||
{:ok, listener} ->
|
||||
{:ok, {_host, actual_port}} = :inet.sockname(listener)
|
||||
{:ok, listener, actual_port}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp run_tracked_request(fun) when is_function(fun, 0) do
|
||||
owner_pid = self()
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ defmodule BDS.Projects do
|
||||
|
||||
@default_project_id "default"
|
||||
@default_project_name "My Blog"
|
||||
@create_project_retry_attempts 5
|
||||
@create_project_retry_delay_ms 50
|
||||
|
||||
@typedoc "An attribute map that may use atom or string keys."
|
||||
@type attrs :: %{optional(atom()) => term(), optional(String.t()) => term()}
|
||||
@@ -152,22 +154,24 @@ defmodule BDS.Projects do
|
||||
name = attr(attrs, :name) || ""
|
||||
slug = unique_slug(attr(attrs, :slug) || Slug.slugify(name))
|
||||
|
||||
Repo.transaction(fn ->
|
||||
project =
|
||||
%Project{}
|
||||
|> Project.changeset(%{
|
||||
id: Ecto.UUID.generate(),
|
||||
name: name,
|
||||
slug: slug,
|
||||
description: attr(attrs, :description),
|
||||
data_path: attr(attrs, :data_path),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
is_active: false
|
||||
})
|
||||
|> Repo.insert!()
|
||||
retry_create_project(fn ->
|
||||
Repo.transaction(fn ->
|
||||
project =
|
||||
%Project{}
|
||||
|> Project.changeset(%{
|
||||
id: Ecto.UUID.generate(),
|
||||
name: name,
|
||||
slug: slug,
|
||||
description: attr(attrs, :description),
|
||||
data_path: attr(attrs, :data_path),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
is_active: false
|
||||
})
|
||||
|> Repo.insert!()
|
||||
|
||||
project
|
||||
project
|
||||
end)
|
||||
end)
|
||||
|> case do
|
||||
{:ok, project} ->
|
||||
@@ -182,6 +186,22 @@ defmodule BDS.Projects do
|
||||
end
|
||||
end
|
||||
|
||||
defp retry_create_project(fun, attempts_left \\ @create_project_retry_attempts)
|
||||
|
||||
defp retry_create_project(fun, attempts_left) when attempts_left > 1 do
|
||||
fun.()
|
||||
rescue
|
||||
error in [Exqlite.Error] ->
|
||||
if String.contains?(Exception.message(error), "Database busy") do
|
||||
Process.sleep(@create_project_retry_delay_ms)
|
||||
retry_create_project(fun, attempts_left - 1)
|
||||
else
|
||||
reraise error, __STACKTRACE__
|
||||
end
|
||||
end
|
||||
|
||||
defp retry_create_project(fun, _attempts_left), do: fun.()
|
||||
|
||||
@spec set_active_project(String.t()) :: {:ok, Project.t()} | {:error, :not_found | term()}
|
||||
def set_active_project(project_id) do
|
||||
case Repo.get(Project, project_id) do
|
||||
|
||||
Reference in New Issue
Block a user