Skip to content

Commit

Permalink
fix: race condition compiling migrations when concurrently creating n…
Browse files Browse the repository at this point in the history
…ew tenants (#406)
  • Loading branch information
skrioify authored Oct 18, 2024
1 parent c4cc329 commit a9f1a00
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 1 deletion.
38 changes: 38 additions & 0 deletions lib/migration_compile_cache.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule AshPostgres.MigrationCompileCache do
@moduledoc """
A cache for the compiled migrations.
This is used to avoid recompiling the migration files
every time a migration is run, as well as ensuring that
migrations are compiled sequentially.
This is important because otherwise there is a race condition
where two invocations could be compiling the same migration at
once, which would error out.
"""

def start_link(opts \\ %{}) do
Agent.start_link(fn -> opts end, name: __MODULE__)
end

@doc """
Compile a file, caching the result for future calls.
"""
def compile_file(file) do
Agent.get_and_update(__MODULE__, fn state ->

Check warning on line 22 in lib/migration_compile_cache.ex

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

There should be no trailing white-space at the end of a line.

Check warning on line 22 in lib/migration_compile_cache.ex

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

There should be no trailing white-space at the end of a line.

Check warning on line 22 in lib/migration_compile_cache.ex

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

There should be no trailing white-space at the end of a line.
new_state = ensure_compiled(state, file)
{Map.get(new_state, file), new_state}
end)
end

defp ensure_compiled(state, file) do
case Map.get(state, file) do
nil ->
compiled = Code.compile_file(file)
Map.put(state, file, compiled)
_ ->
state
end
end

end
7 changes: 6 additions & 1 deletion lib/multitenancy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ defmodule AshPostgres.MultiTenancy do
end

defp load_migration!({version, _, file}) when is_binary(file) do
loaded_modules = file |> Code.compile_file() |> Enum.map(&elem(&1, 0))
loaded_modules = file |> compile_file() |> Enum.map(&elem(&1, 0))

if mod = Enum.find(loaded_modules, &migration?/1) do
{version, mod}
Expand All @@ -70,6 +70,11 @@ defmodule AshPostgres.MultiTenancy do
"file #{Path.relative_to_cwd(file)} does not define an Ecto.Migration"
end
end

Check warning on line 73 in lib/multitenancy.ex

View workflow job for this annotation

GitHub Actions / ash-ci (15) / mix credo --strict

There should be no trailing white-space at the end of a line.

Check warning on line 73 in lib/multitenancy.ex

View workflow job for this annotation

GitHub Actions / ash-ci (14) / mix credo --strict

There should be no trailing white-space at the end of a line.

Check warning on line 73 in lib/multitenancy.ex

View workflow job for this annotation

GitHub Actions / ash-ci (16) / mix credo --strict

There should be no trailing white-space at the end of a line.
defp compile_file(file) do
AshPostgres.MigrationCompileCache.start_link()
AshPostgres.MigrationCompileCache.compile_file(file)
end

defp migration?(mod) do
function_exported?(mod, :__migration__, 0)
Expand Down

0 comments on commit a9f1a00

Please sign in to comment.