Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loader - global lock using Registry #647

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Introduce global process lock for Loader Workers, preventing multiple workers from
attempting to compile the same module simultaneously

## 0.2.1 (2024-11-14)

### Fixes
Expand Down
18 changes: 9 additions & 9 deletions lib/beacon/boot.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ defmodule Beacon.Boot do
def init(%{site: site, mode: :testing}) when is_atom(site) do
Logger.debug("Beacon.Boot is disabled for site #{site} on testing mode")

# reload modules that are expected to be available, even empty
Beacon.Loader.reload_routes_module(site)
Beacon.Loader.reload_components_module(site)
Beacon.Loader.reload_live_data_module(site)
# load modules that are expected to be available, even empty
Beacon.Loader.load_routes_module(site)
Beacon.Loader.load_components_module(site)
Beacon.Loader.load_live_data_module(site)

:ignore
end
Expand All @@ -35,7 +35,7 @@ defmodule Beacon.Boot do
Logger.info("Beacon.Boot booting site #{site}")
task_supervisor = Beacon.Registry.via({site, TaskSupervisor})

# temporary disable module reloading so we can populate data more efficiently
# temporary disable module loading so we can populate data more efficiently
%{mode: :manual} = Beacon.Config.update_value(site, :mode, :manual)
Beacon.Loader.populate_default_media(site)
Beacon.Loader.populate_default_components(site)
Expand All @@ -46,12 +46,12 @@ defmodule Beacon.Boot do
%{mode: :live} = Beacon.Config.update_value(site, :mode, :live)

# still needed to test Beacon itself
Beacon.Loader.reload_routes_module(site)
Beacon.Loader.reload_components_module(site)
Beacon.Loader.load_routes_module(site)
Beacon.Loader.load_components_module(site)

assets = [
Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_runtime_js(site) end),
Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_runtime_css(site) end)
Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.load_runtime_js(site) end),
Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.load_runtime_css(site) end)
]

# TODO: revisit this timeout after we upgrade to Tailwind v4
Expand Down
38 changes: 19 additions & 19 deletions lib/beacon/error_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ defmodule Beacon.ErrorHandler do
alias Beacon.Loader

def undefined_function(module, fun, args) do
ensure_loaded(module) or reload_resource(module)
ensure_loaded(module) or load_resource(module)
:error_handler.undefined_function(module, fun, args)
end

def undefined_lambda(module, fun, args) do
ensure_loaded(module) or reload_resource(module)
ensure_loaded(module) or load_resource(module)
:error_handler.undefined_lambda(module, fun, args)
end

Expand All @@ -33,35 +33,35 @@ defmodule Beacon.ErrorHandler do
end
end

defp reload_resource(module) when is_atom(module) do
defp load_resource(module) when is_atom(module) do
module
|> Module.split()
|> reload_resource()
|> load_resource()
rescue
# ignore erlang modules
_ -> false
end

defp reload_resource(["Beacon", "Web", "LiveRenderer", _site_id, resource]) do
reload_beacon_resource(Process.get(:__beacon_site__), resource)
defp load_resource(["Beacon", "Web", "LiveRenderer", _site_id, resource]) do
load_beacon_resource(Process.get(:__beacon_site__), resource)
end

defp reload_resource(_module), do: false
defp load_resource(_module), do: false

defp reload_beacon_resource(nil = _site, _resource), do: false
defp load_beacon_resource(nil = _site, _resource), do: false

defp reload_beacon_resource(site, resource) do
defp load_beacon_resource(site, resource) do
case resource do
"Page" <> page_id -> Loader.reload_page_module(site, page_id)
"Layout" <> layout_id -> Loader.reload_layout_module(site, layout_id)
"Routes" -> Loader.reload_routes_module(site)
"Components" -> Loader.reload_components_module(site)
"LiveData" -> Loader.reload_live_data_module(site)
"Stylesheet" -> Loader.reload_stylesheet_module(site)
"Snippets" -> Loader.reload_snippets_module(site)
"ErrorPage" -> Loader.reload_error_page_module(site)
"EventHandlers" -> Loader.reload_event_handlers_module(site)
"InfoHandlers" -> Loader.reload_info_handlers_module(site)
"Page" <> page_id -> Loader.load_page_module(site, page_id)
"Layout" <> layout_id -> Loader.load_layout_module(site, layout_id)
"Routes" -> Loader.load_routes_module(site)
"Components" -> Loader.load_components_module(site)
"LiveData" -> Loader.load_live_data_module(site)
"Stylesheet" -> Loader.load_stylesheet_module(site)
"Snippets" -> Loader.load_snippets_module(site)
"ErrorPage" -> Loader.load_error_page_module(site)
"EventHandlers" -> Loader.load_event_handlers_module(site)
"InfoHandlers" -> Loader.load_info_handlers_module(site)
_ -> false
end
end
Expand Down
91 changes: 42 additions & 49 deletions lib/beacon/loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ defmodule Beacon.Loader do
GenServer.call(worker(site), :populate_default_home_page, @timeout)
end

def reload_runtime_js(site) do
GenServer.call(worker(site), :reload_runtime_js, :timer.minutes(2))
def load_runtime_js(site) do
GenServer.call(worker(site), :load_runtime_js, :timer.minutes(2))
end

def reload_runtime_css(site) do
GenServer.call(worker(site), :reload_runtime_css, :timer.minutes(2))
def load_runtime_css(site) do
GenServer.call(worker(site), :load_runtime_css, :timer.minutes(2))
end

def fetch_snippets_module(site) do
Expand Down Expand Up @@ -153,61 +153,62 @@ defmodule Beacon.Loader do
Loader.InfoHandlers.module_name(site)
end

def maybe_reload_page_module(site, page_id) do
maybe_reload(Loader.Page.module_name(site, page_id), fn -> reload_page_module(site, page_id) end)
def load_snippets_module(site) do
call_worker(site, :load_snippets_module, {:load_snippets_module, [site]})
end

def reload_snippets_module(site) do
call_worker(site, :reload_snippets_module, {:reload_snippets_module, [site]})
def load_routes_module(site) do
call_worker(site, :load_routes_module, {:load_routes_module, [site]})
end

def reload_routes_module(site) do
call_worker(site, :reload_routes_module, {:reload_routes_module, [site]})
def load_components_module(site) do
call_worker(site, :load_components_module, {:load_components_module, [site]})
end

def reload_components_module(site) do
call_worker(site, :reload_components_module, {:reload_components_module, [site]})
def load_live_data_module(site) do
call_worker(site, :load_live_data_module, {:load_live_data_module, [site]})
end

def reload_live_data_module(site) do
call_worker(site, :reload_live_data_module, {:reload_live_data_module, [site]})
def load_error_page_module(site) do
call_worker(site, :load_error_page_module, {:load_error_page_module, [site]})
end

def reload_error_page_module(site) do
call_worker(site, :reload_error_page_module, {:reload_error_page_module, [site]})
def load_stylesheet_module(site) do
call_worker(site, :load_stylesheet_module, {:load_stylesheet_module, [site]})
end

def reload_stylesheet_module(site) do
call_worker(site, :reload_stylesheet_module, {:reload_stylesheet_module, [site]})
def load_event_handlers_module(site) do
call_worker(site, :load_event_handlers_module, {:load_event_handlers_module, [site]})
end

def reload_event_handlers_module(site) do
call_worker(site, :reload_event_handlers_module, {:reload_event_handlers_module, [site]})
def load_info_handlers_module(site) do
call_worker(site, :load_info_handlers_module, {:load_info_handlers_module, [site]})
end

def reload_info_handlers_module(site) do
call_worker(site, :reload_info_handlers_module, {:reload_info_handlers_module, [site]})
end

def reload_layouts_modules(site) do
Enum.map(Content.list_published_layouts(site), &reload_layout_module(&1.site, &1.id))
def load_layouts_modules(site) do
site
|> Content.list_published_layouts()
|> Enum.map(&load_layout_module(&1.site, &1.id))
end

def reload_layout_module(site, layout_id) do
call_worker(site, {:reload_layout_module, layout_id}, {:reload_layout_module, [site, layout_id]})
def load_layout_module(site, layout_id) do
call_worker(site, {:load_layout_module, layout_id}, {:load_layout_module, [site, layout_id]})
end

def reload_pages_modules(site, opts \\ []) do
def load_pages_modules(site, opts \\ []) do
per_page = Keyword.get(opts, :per_page, :infinity)
Enum.map(Content.list_published_pages(site, per_page: per_page), &reload_page_module(&1.site, &1.id))

site
|> Content.list_published_pages(per_page: per_page)
|> Enum.map(&load_page_module(&1.site, &1.id))
end

def reload_page_module(site, page_id) do
call_worker(site, {:reload_page_module, page_id}, {:reload_page_module, [site, page_id]})
def load_page_module(site, page_id) do
call_worker(site, {:load_page_module, page_id}, {:load_page_module, [site, page_id]})
end

# call worker asyncly or syncly depending on the current site mode
# or skip if mode is manual so we don't reload modules
# or skip if mode is manual so we don't load modules
defp call_worker(site, async_request, sync_request) do
mode = Beacon.Config.fetch!(site).mode

Expand All @@ -228,14 +229,6 @@ defmodule Beacon.Loader do
GenServer.call(worker(site), {:unload_page_module, page_id}, @timeout)
end

defp maybe_reload(module, reload_fun) do
if :erlang.module_loaded(module) do
{:ok, module}
else
reload_fun.()
end
end

# Server

def handle_continue(:async_init, config) do
Expand All @@ -258,7 +251,7 @@ defmodule Beacon.Loader do
|> Loader.Layout.module_name(id)
|> unload()

reload_runtime_css(site)
load_runtime_css(site)

{:noreply, config}
end
Expand All @@ -270,7 +263,7 @@ defmodule Beacon.Loader do
|> Loader.Page.module_name(id)
|> unload()

reload_runtime_css(site)
load_runtime_css(site)

{:noreply, config}
end
Expand All @@ -284,7 +277,7 @@ defmodule Beacon.Loader do
|> unload()
end

reload_runtime_css(site)
load_runtime_css(site)

{:noreply, config}
end
Expand All @@ -300,7 +293,7 @@ defmodule Beacon.Loader do
|> Loader.Stylesheet.module_name()
|> unload()

reload_runtime_css(site)
load_runtime_css(site)

{:noreply, config}
end
Expand All @@ -310,7 +303,7 @@ defmodule Beacon.Loader do
|> Loader.Snippets.module_name()
|> unload()

reload_runtime_css(site)
load_runtime_css(site)

{:noreply, config}
end
Expand All @@ -320,7 +313,7 @@ defmodule Beacon.Loader do
|> Loader.ErrorPage.module_name()
|> unload()

reload_runtime_css(site)
load_runtime_css(site)

{:noreply, config}
end
Expand All @@ -330,7 +323,7 @@ defmodule Beacon.Loader do
|> Loader.Components.module_name()
|> unload()

reload_runtime_css(site)
load_runtime_css(site)

{:noreply, config}
end
Expand All @@ -340,7 +333,7 @@ defmodule Beacon.Loader do
|> Loader.LiveData.module_name()
|> unload()

reload_runtime_css(site)
load_runtime_css(site)

{:noreply, config}
end
Expand Down
Loading
Loading