-
Notifications
You must be signed in to change notification settings - Fork 8
/
give_away_test.exs
154 lines (127 loc) · 4.52 KB
/
give_away_test.exs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
defmodule Mutex.GiveAwayTest do
alias Mutex.Lock
alias Mutex.ReleaseError
import Mutex.Test.Utils
require Logger
use ExUnit.Case, async: true
@moduletag :capture_log
@mut rand_mod()
setup do
{:ok, _pid} = start_supervised({Mutex, name: @mut})
:ok
end
test "can give away a lock" do
assert {:ok, lock} = Mutex.lock(@mut, :gift)
parent = self()
assert parent == Mutex.whereis_name({@mut, :gift})
pid =
xspawn(fn ->
assert_receive {:"MUTEX-TRANSFER", ^parent, ^lock, :some_data}
hang()
end)
assert :ok = Mutex.give_away(@mut, lock, pid, :some_data)
assert pid == Mutex.whereis_name({@mut, :gift})
kill(pid)
# Once the pid is cleaned we can lock again. We need to await because the
# mutex will not always receive the :DOWN message from the previous owner
# immediately.
assert %_{} = Mutex.await(@mut, :gift)
end
test "default give away data is nil" do
assert {:ok, lock} = Mutex.lock(@mut, :gift2)
parent = self()
pid =
xspawn(fn ->
assert_receive {:"MUTEX-TRANSFER", ^parent, ^lock, nil}
hang()
end)
assert :ok = Mutex.give_away(@mut, lock, pid)
assert pid == Mutex.whereis_name({@mut, :gift2})
end
test "cannot give away if now owning" do
{ack, wack} = vawack()
xspawn(fn ->
assert {:ok, lock} = Mutex.lock(@mut, :gift3)
ack.(lock)
hang()
end)
lock = wack.()
pid =
xspawn(fn ->
receive do
:stop_from_test -> :ok
# Should not receive any transfer message
msg -> flunk("received message: #{inspect(msg)}")
end
end)
ensure_message(assert_raise ReleaseError, fn -> Mutex.give_away(@mut, lock, pid) end)
# The transfer message is send from the giver process, that ensures that
# here we can test that the givee will only receive our :stop_from_test
# message.
send(pid, :stop_from_test)
await_down(pid)
end
test "cannot give away an unknown lock" do
lock = %Mutex.Lock{type: :single, key: :made_up, keys: nil}
pid = xspawn(&hang/0)
ensure_message(assert_raise ReleaseError, fn -> Mutex.give_away(@mut, lock, pid) end)
end
test "can give away to non existing pid" do
{dead_pid, mref} = spawn_monitor(fn -> :ok end)
assert_receive {:DOWN, ^mref, :process, ^dead_pid, :normal}
refute Process.alive?(dead_pid)
assert {:ok, lock} = Mutex.lock(@mut, :dead)
assert :ok = Mutex.give_away(@mut, lock, dead_pid)
# We can lock immediately. Theoretically there could be a race condition
# where the mutex would receive this lock attempt before it receives the
# {:DOWN,_,_,_,:noproc} message when monitoring the dead pid. In practice
# the :DOWN message is immediate by the BEAM implementation so this is not a
# problem.
assert {:ok, _lock} = Mutex.lock(@mut, :dead)
end
test "give away to self is forbidden" do
this = self()
assert {:ok, lock} = Mutex.lock(@mut, :to_self)
ensure_message(assert_raise ArgumentError, fn -> Mutex.give_away(@mut, lock, this, :some_data) end)
end
test "give away with multilocks" do
# For now multilocks are considered independent locks and can be given away
# unitarily.
{ack, wack} = vawack()
parent = self()
pid =
xspawn(fn ->
receive do
{:"MUTEX-TRANSFER", ^parent, lock, _} ->
ack.(lock)
hang()
end
end)
assert %{keys: [:k1, :k2]} = Mutex.await_all(@mut, [:k1, :k2])
assert :ok = Mutex.give_away(@mut, %Lock{type: :single, key: :k2}, pid)
assert %Mutex.Lock{type: :single, key: :k2, keys: nil} = wack.()
# ownership of :k2 was given but we ketp :k1
assert self() == Mutex.whereis_name({@mut, :k1})
assert pid == Mutex.whereis_name({@mut, :k2})
end
test "gift loop" do
key = :counter
assert {:ok, lock} = Mutex.lock(@mut, key)
# start X processes, each one will be give the key in its turn and increment
# the transfer_data
n_procs = 1000
# Note we start the loop from the "back", the first item will receive self()
# as the "give_to" and will receive the lock last and give it to the test
# process.
first_pid =
Enum.reduce(1..n_procs, self(), fn _, give_to ->
xspawn(fn ->
receive do
{:"MUTEX-TRANSFER", _, lock, counter} -> Mutex.give_away(@mut, lock, give_to, counter + 1)
end
end)
end)
assert :ok = Mutex.give_away(@mut, lock, first_pid, 0)
assert_receive({:"MUTEX-TRANSFER", _, ^lock, ^n_procs})
end
end