mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-24 04:27:59 +01:00
143 lines
3.8 KiB
Elixir
143 lines
3.8 KiB
Elixir
defmodule PhilomenaJob.SemaphoreTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias PhilomenaJob.Semaphore.Server, as: SemaphoreServer
|
|
|
|
@max_concurrency 8
|
|
|
|
describe "Server functionality" do
|
|
setup do
|
|
{:ok, pid} = SemaphoreServer.start_link(max_concurrency: @max_concurrency)
|
|
on_exit(fn -> Process.exit(pid, :kill) end)
|
|
|
|
%{pid: pid}
|
|
end
|
|
|
|
test "allows max_concurrency processes to acquire", %{pid: pid} do
|
|
# Check acquire
|
|
results =
|
|
(1..@max_concurrency)
|
|
|> Task.async_stream(fn _ -> SemaphoreServer.acquire(pid) end)
|
|
|> Enum.map(fn {:ok, res} -> res end)
|
|
|
|
assert true == Enum.all?(results)
|
|
|
|
# Check linking to process exit
|
|
# If this hangs, linking to process exit does not release the semaphore
|
|
results =
|
|
(1..@max_concurrency)
|
|
|> Task.async_stream(fn _ -> SemaphoreServer.acquire(pid) end)
|
|
|> Enum.map(fn {:ok, res} -> res end)
|
|
|
|
assert true == Enum.all?(results)
|
|
end
|
|
|
|
test "does not allow max_concurrency + 1 processes to acquire (exit)", %{pid: pid} do
|
|
processes =
|
|
(1..@max_concurrency)
|
|
|> Enum.map(fn _ -> acquire_and_wait_for_release(pid) end)
|
|
|
|
# This task should not be able to acquire
|
|
task = Task.async(fn -> SemaphoreServer.acquire(pid) end)
|
|
assert nil == Task.yield(task, 10)
|
|
|
|
# Terminate processes holding the semaphore
|
|
Enum.each(processes, &Process.exit(&1, :kill))
|
|
|
|
# Now the task should be able to acquire
|
|
assert {:ok, :ok} == Task.yield(task, 10)
|
|
end
|
|
|
|
test "does not allow max_concurrency + 1 processes to acquire (release)", %{pid: pid} do
|
|
processes =
|
|
(1..@max_concurrency)
|
|
|> Enum.map(fn _ -> acquire_and_wait_for_release(pid) end)
|
|
|
|
# This task should not be able to acquire
|
|
task = Task.async(fn -> SemaphoreServer.acquire(pid) end)
|
|
assert nil == Task.yield(task, 10)
|
|
|
|
# Release processes holding the semaphore
|
|
Enum.each(processes, &send(&1, :release))
|
|
|
|
# Now the task should be able to acquire
|
|
assert {:ok, :ok} == Task.yield(task, 10)
|
|
end
|
|
|
|
test "does not allow max_concurrency + 1 processes to acquire (run)", %{pid: pid} do
|
|
processes =
|
|
(1..@max_concurrency)
|
|
|> Enum.map(fn _ -> run_and_wait_for_release(pid) end)
|
|
|
|
# This task should not be able to acquire
|
|
task = Task.async(fn -> SemaphoreServer.acquire(pid) end)
|
|
assert nil == Task.yield(task, 10)
|
|
|
|
# Release processes holding the semaphore
|
|
Enum.each(processes, &send(&1, :release))
|
|
|
|
# Now the task should be able to acquire
|
|
assert {:ok, :ok} == Task.yield(task, 10)
|
|
end
|
|
|
|
test "does not allow re-acquire from the same process", %{pid: pid} do
|
|
acquire = fn ->
|
|
try do
|
|
{:ok, SemaphoreServer.acquire(pid)}
|
|
rescue
|
|
err -> {:error, err}
|
|
end
|
|
end
|
|
|
|
task = Task.async(fn ->
|
|
acquire.()
|
|
acquire.()
|
|
end)
|
|
|
|
assert {:ok, {:error, %MatchError{}}} = Task.yield(task)
|
|
end
|
|
|
|
test "allows re-release from the same process", %{pid: pid} do
|
|
release = fn ->
|
|
try do
|
|
{:ok, SemaphoreServer.release(pid)}
|
|
rescue
|
|
err -> {:error, err}
|
|
end
|
|
end
|
|
|
|
task = Task.async(fn ->
|
|
release.()
|
|
release.()
|
|
end)
|
|
|
|
assert {:ok, {:ok, :ok}} = Task.yield(task)
|
|
end
|
|
end
|
|
|
|
defp run_and_wait_for_release(pid) do
|
|
spawn(fn ->
|
|
SemaphoreServer.run(pid, fn ->
|
|
wait_for_release()
|
|
end)
|
|
end)
|
|
end
|
|
|
|
defp acquire_and_wait_for_release(pid) do
|
|
spawn(fn ->
|
|
SemaphoreServer.acquire(pid)
|
|
wait_for_release()
|
|
SemaphoreServer.release(pid)
|
|
end)
|
|
end
|
|
|
|
defp wait_for_release do
|
|
receive do
|
|
:release ->
|
|
:ok
|
|
|
|
_ ->
|
|
wait_for_release()
|
|
end
|
|
end
|
|
end
|