Skip to content

Commit

Permalink
Feat: just use a Repo with pool=1 for PG locks
Browse files Browse the repository at this point in the history
Problem: 
- the sandbox config has some test-specific params, like timing out, cleaning up the conn, etc. 
- this prevents proper usage

Solution: 
- we just stick to a normal repo with a pool of size=1, that way we always get the same connection
- this is only use for locking, so there should be no performance issues
  • Loading branch information
mindreframer committed Sep 3, 2024
1 parent 1182bc7 commit 214ffda
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 10 deletions.
2 changes: 1 addition & 1 deletion lib/essig/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions lib/pg_lock.ex
Original file line number Diff line number Diff line change
@@ -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()}")
Expand All @@ -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
10 changes: 5 additions & 5 deletions lib/sand_repo.ex → lib/repo_single_conn.ex
Original file line number Diff line number Diff line change
@@ -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.
"""
Expand All @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit 214ffda

Please sign in to comment.