60 lines
1.7 KiB
Elixir
60 lines
1.7 KiB
Elixir
defmodule Ecto.Integration.LockTest do
|
|
# We can keep this test async as long as it
|
|
# is the only one accessing the lock_test table.
|
|
use ExUnit.Case, async: true
|
|
|
|
import Ecto.Query
|
|
alias Ecto.Integration.PoolRepo
|
|
|
|
defmodule LockCounter do
|
|
use Ecto.Schema
|
|
|
|
schema "lock_counters" do
|
|
field :count, :integer
|
|
end
|
|
end
|
|
|
|
setup do
|
|
PoolRepo.delete_all(LockCounter)
|
|
:ok
|
|
end
|
|
|
|
test "lock for update" do
|
|
%{id: id} = PoolRepo.insert!(%LockCounter{count: 1})
|
|
pid = self()
|
|
|
|
lock_for_update =
|
|
Application.get_env(:ecto_sql, :lock_for_update) ||
|
|
raise ":lock_for_update not set in :ecto application"
|
|
|
|
# Here we are manually inserting the lock in the query
|
|
# to test multiple adapters. Never do this in actual
|
|
# application code: it is not safe and not public.
|
|
query = from(lc in LockCounter, where: lc.id == ^id)
|
|
query = %{query | lock: lock_for_update}
|
|
|
|
{:ok, new_pid} =
|
|
Task.start_link fn ->
|
|
assert_receive :select_for_update, 5000
|
|
|
|
PoolRepo.transaction(fn ->
|
|
[post] = PoolRepo.all(query) # this should block until the other trans. commit
|
|
post |> Ecto.Changeset.change(count: post.count + 1) |> PoolRepo.update!
|
|
end)
|
|
|
|
send pid, :updated
|
|
end
|
|
|
|
PoolRepo.transaction(fn ->
|
|
[post] = PoolRepo.all(query) # select and lock the row
|
|
send new_pid, :select_for_update # signal second process to begin a transaction
|
|
post |> Ecto.Changeset.change(count: post.count + 1) |> PoolRepo.update!
|
|
end)
|
|
|
|
assert_receive :updated, 5000
|
|
|
|
# Final count will be 3 if SELECT ... FOR UPDATE worked and 2 otherwise
|
|
assert [%LockCounter{count: 3}] = PoolRepo.all(LockCounter)
|
|
end
|
|
end
|