diff --git a/lib/essig/application.ex b/lib/essig/application.ex index d5dbe31..aae19e5 100644 --- a/lib/essig/application.ex +++ b/lib/essig/application.ex @@ -7,7 +7,7 @@ defmodule Essig.Application do def start(_type, _args) do children = [ Essig.Repo, - Essig.SandRepo, + Essig.RepoSingleConn, {Phoenix.PubSub, name: Essig.PubSub}, {Registry, keys: :unique, name: Essig.Scopes.Registry}, Essig.Scopes.DynamicSupervisor diff --git a/lib/pg_lock.ex b/lib/pg_lock.ex index 859f5fc..c81df23 100644 --- a/lib/pg_lock.ex +++ b/lib/pg_lock.ex @@ -1,13 +1,20 @@ defmodule Essig.PGLock do @moduledoc """ A simple wrapper around pg_try_advisory_lock and pg_advisory_unlock. - To get consistent PG connection, it uses SandRepo (which is configured with a Sandbox as pool) + To get consistent PG connection, it uses second Repo with pool_size=1 This makes it possible to use the same connection for locking and releasing the lock! Because releasing the lock on a different connection than locking it will fail. This is the best workaround I could come up with. + + + - Check locks with: + + ```sql + SELECT locktype, transactionid, virtualtransaction, mode FROM pg_locks; + ``` """ - use Essig.SandRepo + use Essig.RepoSingleConn def with_lock(kind, fun) do lock_key = :erlang.phash2("#{kind}-#{Essig.Context.current_scope()}") @@ -26,10 +33,10 @@ defmodule Essig.PGLock do end def get_lock(key) do - Ecto.Adapters.SQL.query(SandRepo, "SELECT pg_try_advisory_lock($1)", [key], []) + Ecto.Adapters.SQL.query(RepoSingleConn, "SELECT pg_try_advisory_lock($1)", [key], []) end def release_lock(key) do - Ecto.Adapters.SQL.query(SandRepo, "SELECT pg_advisory_unlock($1)", [key], []) + Ecto.Adapters.SQL.query(RepoSingleConn, "SELECT pg_advisory_unlock($1)", [key], []) end end diff --git a/lib/sand_repo.ex b/lib/repo_single_conn.ex similarity index 81% rename from lib/sand_repo.ex rename to lib/repo_single_conn.ex index f6f6e39..5e8fd30 100644 --- a/lib/sand_repo.ex +++ b/lib/repo_single_conn.ex @@ -1,10 +1,10 @@ -defmodule Essig.SandRepo do +defmodule Essig.RepoSingleConn do @moduledoc """ - This is special SandRepo, that allows checking out a single connection. + This is special RepoSingleConn, with a single connection. We use this when getting a DB advisory lock and releasing it afterwards. This is not possible with the standard Ecto.Repo outside of a transaction. - To keep the configuration overhead low, we use dynamic config (init -callback) and + To keep the configuration overhead low, we use dynamic config (init-callback) and copy the main config for the Essig.Repo with a few tweaks. This means the DB config stays unchanged. """ @@ -19,7 +19,7 @@ defmodule Essig.SandRepo do def init(_type, _config) do special_config = [ telemetry_prefix: [:essig, :sand_repo], - pool: Ecto.Adapters.SQL.Sandbox + pool_size: 1 ] main_config = Application.get_env(:essig, Essig.Repo) @@ -30,7 +30,7 @@ defmodule Essig.SandRepo do defmacro __using__(_) do quote do - alias Essig.SandRepo + alias Essig.RepoSingleConn require Ecto.Query import Ecto.Query import Ecto.Changeset