Repository: general-CbIC/poolex
Pull request #41 (2023-04-13 13:44:18)

Feature/workers fifo impl
### Added

- Added `FIFO` worker's implementation. [About implemetations](https://hexdocs.pm/poolex/custom-implementations.html)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75c08e7..62c957d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Added `FIFO` worker's implementation. [About implemetations](https://hexdocs.pm/poolex/custom-implementations.html)
+
 ## [0.6.1] - 2023-03-25
 
 ### Documentation updates
diff --git a/README.md b/README.md
index 610ccab..1106cc7 100644
--- a/README.md
+++ b/README.md
@@ -43,10 +43,10 @@ With `poolex` you can:
   - [Defining the worker](https://hexdocs.pm/poolex/example-of-use.html#defining-the-worker)
   - [Configuring Poolex](https://hexdocs.pm/poolex/example-of-use.html#configuring-poolex)
   - [Using Poolex](https://hexdocs.pm/poolex/example-of-use.html#using-poolex)
-- [Custom implementations](https://hexdocs.pm/poolex/custom-implementations.html)
-  - [Callers](https://hexdocs.pm/poolex/custom-implementations.html#callers)
-  - [Workers](https://hexdocs.pm/poolex/custom-implementations.html#workers)
-  - [Writing custom implementations](https://hexdocs.pm/poolex/custom-implementations.html#writing-custom-implementations)
+- [Workers and callers implementations](https://hexdocs.pm/poolex/workers-and-callers-implementations.html)
+  - [Callers](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#callers)
+  - [Workers](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#workers)
+  - [Writing custom implementations](https://hexdocs.pm/poolex/workers-and-callers-implementations.html#writing-custom-implementations)
 - [Contributions](#contributions)
 
 ## Installation
diff --git a/docs/guides/custom-implementations.md b/docs/guides/workers-and-callers-implementations.md
similarity index 74%
rename from docs/guides/custom-implementations.md
rename to docs/guides/workers-and-callers-implementations.md
index 1fc3a82..0662d76 100644
--- a/docs/guides/custom-implementations.md
+++ b/docs/guides/workers-and-callers-implementations.md
@@ -1,4 +1,4 @@
-# Custom Implementations
+# Workers and callers implementations
 
 `Poolex` operates with two concepts: `callers` and `workers`. In both cases, we are talking about processes.
 
@@ -12,9 +12,7 @@
 
 The implementation of the caller storage structure should be conceptually similar to a queue since by default we want to give workers in the order they are requested. But this logic can be easily changed by writing your implementation.
 
-Behaviour of callers collection described [here](../../lib/poolex/callers/behaviour.ex).
-
-Default implementation based on erlang `:queue` you can see [here](../../lib/poolex/callers/impl/erlang_queue.ex).
+Behaviour of callers collection described [here](https://hexdocs.pm/poolex/Poolex.Callers.Behaviour.html).
 
 ### Behaviour callbacks
 
@@ -27,6 +25,12 @@ Default implementation based on erlang `:queue` you can see [here](../../lib/poo
 | `remove_by_pid/2` | Removes given caller by caller's pid from `state` and returns a new state.                                           |
 | `to_list/1`       | Returns list of callers.                                                                                             |
 
+### Callers implementations out of the box
+
+| Module                            | Description                                    | Source                                                                                           | Default? |
+|-----------------------------------|------------------------------------------------|--------------------------------------------------------------------------------------------------|----------|
+| `Poolex.Callers.Impl.ErlangQueue` | `FIFO` implementation based on `:erlang.queue` | [link](https://github.com/general-CbIC/poolex/blob/main/lib/poolex/callers/impl/erlang_queue.ex) | ✅       |
+
 ## Workers
 
 **Workers** are processes launched in a pool. `Poolex` works with two collections of workers:
@@ -36,9 +40,7 @@ Default implementation based on erlang `:queue` you can see [here](../../lib/poo
 
 For both cases, the default implementation is based on lists. But it is possible to set different implementations for them.
 
-Behaviour of workers collection described [here](../../lib/poolex/workers/behaviour.ex).
-
-The default implementation for `idle` and `busy` workers is [here](../../lib/poolex/workers/impl/list.ex).
+Behaviour of workers collection described [here](https://hexdocs.pm/poolex/Poolex.Workers.Behaviour.html).
 
 ### Behaviour callbacks
 
@@ -54,6 +56,13 @@ The default implementation for `idle` and `busy` workers is [here](../../lib/poo
 | `empty?/1`  | Returns `true` if the `state` is empty, `false` otherwise.                                                       |
 | `pop/1`     | Removes one of workers from `state` and returns it as `{caller, state}`. Returns `:empty` if the state is empty. |
 
+### Workers implementations out of the box
+
+| Module                            | Description                                    | Source                                                                                           | Default? |
+|-----------------------------------|------------------------------------------------|--------------------------------------------------------------------------------------------------|----------|
+| `Poolex.Workers.Impl.List`        | `LIFO` implementation based on Elixir's `List` | [link](https://github.com/general-CbIC/poolex/blob/main/lib/poolex/workers/impl/list.ex)         | ✅       |
+| `Poolex.Workers.Impl.ErlangQueue` | `FIFO` implementation based on `:erlang.queue` | [link](https://github.com/general-CbIC/poolex/blob/main/lib/poolex/workers/impl/erlang_queue.ex) | ❌       |
+
 ## Writing custom implementations
 
 It's quite simple when using the [Behaviours](https://elixir-lang.org/getting-started/typespecs-and-behaviours.html#behaviours) mechanism in Elixir.
diff --git a/lib/poolex/workers/impl/erlang_queue.ex b/lib/poolex/workers/impl/erlang_queue.ex
new file mode 100644
index 0000000..dd9b663
--- /dev/null
+++ b/lib/poolex/workers/impl/erlang_queue.ex
@@ -0,0 +1,54 @@
+defmodule Poolex.Workers.Impl.ErlangQueue do
+  @moduledoc """
+  Simple workers queue (FIFO) implementation based on Erlang `:queue`
+  """
+  @behaviour Poolex.Workers.Behaviour
+
+  @impl true
+  def init() do
+    :queue.new()
+  end
+
+  @impl true
+  def init(workers) do
+    :queue.from_list(workers)
+  end
+
+  @impl true
+  def add(state, worker) do
+    :queue.in(worker, state)
+  end
+
+  @impl true
+  def member?(state, worker) do
+    :queue.member(worker, state)
+  end
+
+  @impl true
+  def remove(state, worker) do
+    :queue.filter(fn element -> element != worker end, state)
+  end
+
+  @impl true
+  def count(state) do
+    :queue.len(state)
+  end
+
+  @impl true
+  def to_list(state) do
+    :queue.to_list(state)
+  end
+
+  @impl true
+  def empty?(state) do
+    :queue.is_empty(state)
+  end
+
+  @impl true
+  def pop(state) do
+    case :queue.out(state) do
+      {{:value, worker}, new_state} -> {worker, new_state}
+      {:empty, _state} -> :empty
+    end
+  end
+end
diff --git a/mix.exs b/mix.exs
index 486831b..928cd19 100644
--- a/mix.exs
+++ b/mix.exs
@@ -55,7 +55,7 @@ defmodule Poolex.MixProject do
       main: "readme",
       extras: [
         "docs/CONTRIBUTING.md",
-        "docs/guides/custom-implementations.md",
+        "docs/guides/workers-and-callers-implementations.md",
         "docs/guides/migration-from-poolboy.cheatmd",
         "docs/guides/example-of-use.cheatmd",
         "docs/guides/getting-started.cheatmd",


Test patch:
diff --git a/test/poolex/callers/impl/erlang_queue_test.exs b/test/poolex/callers/impl/erlang_queue_test.exs
new file mode 100644
index 0000000..834908e
--- /dev/null
+++ b/test/poolex/callers/impl/erlang_queue_test.exs
@@ -0,0 +1,45 @@
+defmodule Poolex.Callers.Impl.ErlangQueueTest do
+  use ExUnit.Case
+
+  alias Poolex.Callers.Impl.ErlangQueue, as: Impl
+
+  setup do
+    [state: Impl.init()]
+  end
+
+  test "usage cases", %{state: state} do
+    # Must be empty after init/0
+    assert Impl.empty?(state) == true
+    assert Impl.pop(state) == :empty
+    assert Impl.to_list(state) == []
+
+    # Add 2 callers
+    caller_1_pid = :c.pid(0, 250, 1)
+    caller_1 = gen_caller(caller_1_pid)
+
+    caller_2_pid = :c.pid(0, 250, 2)
+    caller_2 = gen_caller(caller_2_pid)
+
+    state =
+      state
+      |> Impl.add(caller_1)
+      |> Impl.add(caller_2)
+
+    assert Impl.empty?(state) == false
+    assert Impl.to_list(state) == [caller_1, caller_2]
+    assert state |> Impl.pop() |> elem(0) == caller_1
+
+    # Remove by pid
+    state =
+      state
+      |> Impl.add(caller_1)
+      |> Impl.add(caller_2)
+      |> Impl.remove_by_pid(caller_1_pid)
+
+    assert Impl.to_list(state) == [caller_2, caller_2]
+  end
+
+  defp gen_caller(pid, tag \\ make_ref()) do
+    {pid, tag}
+  end
+end
diff --git a/test/poolex/workers/impl/erlang_queue_test.exs b/test/poolex/workers/impl/erlang_queue_test.exs
new file mode 100644
index 0000000..8f37274
--- /dev/null
+++ b/test/poolex/workers/impl/erlang_queue_test.exs
@@ -0,0 +1,74 @@
+defmodule Poolex.Workers.Impl.ErlangQueueTest do
+  use ExUnit.Case
+
+  alias Poolex.Workers.Impl.ErlangQueue, as: Impl
+
+  test "empty workers queue" do
+    state = Impl.init()
+
+    assert Impl.empty?(state) == true
+    assert Impl.count(state) == 0
+    assert Impl.to_list(state) == []
+  end
+
+  test "queue with some workers" do
+    worker_1 = :c.pid(0, 250, 1)
+    worker_2 = :c.pid(0, 250, 2)
+
+    state = Impl.init([worker_1, worker_2])
+
+    assert Impl.empty?(state) == false
+    assert Impl.count(state) == 2
+    assert Impl.to_list(state) == [worker_1, worker_2]
+  end
+
+  test "add and remove workers" do
+    worker_1 = :c.pid(0, 250, 1)
+    worker_2 = :c.pid(0, 250, 2)
+
+    state = Impl.init()
+
+    state =
+      state
+      |> Impl.add(worker_1)
+      |> Impl.add(worker_2)
+
+    assert Impl.member?(state, worker_1) == true
+    assert Impl.member?(state, worker_2) == true
+    assert Impl.empty?(state) == false
+    assert Impl.count(state) == 2
+    assert Impl.to_list(state) == [worker_1, worker_2]
+
+    state = Impl.remove(state, worker_1)
+
+    assert Impl.member?(state, worker_1) == false
+    assert Impl.member?(state, worker_2) == true
+    assert Impl.empty?(state) == false
+    assert Impl.count(state) == 1
+    assert Impl.to_list(state) == [worker_2]
+  end
+
+  test "pop/1" do
+    worker_1 = :c.pid(0, 250, 1)
+    worker_2 = :c.pid(0, 250, 2)
+    worker_3 = :c.pid(0, 250, 3)
+
+    state =
+      Impl.init()
+      |> Impl.add(worker_1)
+      |> Impl.add(worker_2)
+      |> Impl.add(worker_3)
+
+    {worker, state} = Impl.pop(state)
+    assert worker == worker_1
+
+    {worker, state} = Impl.pop(state)
+    assert worker == worker_2
+
+    {worker, state} = Impl.pop(state)
+    assert worker == worker_3
+
+    assert Impl.pop(state) == :empty
+    assert Impl.empty?(state) == true
+  end
+end
diff --git a/test/poolex/workers/impl/list_test.exs b/test/poolex/workers/impl/list_test.exs
new file mode 100644
index 0000000..159055e
--- /dev/null
+++ b/test/poolex/workers/impl/list_test.exs
@@ -0,0 +1,74 @@
+defmodule Poolex.Workers.Impl.ListTest do
+  use ExUnit.Case
+
+  alias Poolex.Workers.Impl.List, as: Impl
+
+  test "empty workers queue" do
+    state = Impl.init()
+
+    assert Impl.empty?(state) == true
+    assert Impl.count(state) == 0
+    assert Impl.to_list(state) == []
+  end
+
+  test "queue with some workers" do
+    worker_1 = :c.pid(0, 250, 1)
+    worker_2 = :c.pid(0, 250, 2)
+
+    state = Impl.init([worker_1, worker_2])
+
+    assert Impl.empty?(state) == false
+    assert Impl.count(state) == 2
+    assert Impl.to_list(state) == [worker_1, worker_2]
+  end
+
+  test "add and remove workers" do
+    worker_1 = :c.pid(0, 250, 1)
+    worker_2 = :c.pid(0, 250, 2)
+
+    state = Impl.init()
+
+    state =
+      state
+      |> Impl.add(worker_1)
+      |> Impl.add(worker_2)
+
+    assert Impl.member?(state, worker_1) == true
+    assert Impl.member?(state, worker_2) == true
+    assert Impl.empty?(state) == false
+    assert Impl.count(state) == 2
+    assert Impl.to_list(state) == [worker_2, worker_1]
+
+    state = Impl.remove(state, worker_1)
+
+    assert Impl.member?(state, worker_1) == false
+    assert Impl.member?(state, worker_2) == true
+    assert Impl.empty?(state) == false
+    assert Impl.count(state) == 1
+    assert Impl.to_list(state) == [worker_2]
+  end
+
+  test "pop/1" do
+    worker_1 = :c.pid(0, 250, 1)
+    worker_2 = :c.pid(0, 250, 2)
+    worker_3 = :c.pid(0, 250, 3)
+
+    state =
+      Impl.init()
+      |> Impl.add(worker_1)
+      |> Impl.add(worker_2)
+      |> Impl.add(worker_3)
+
+    {worker, state} = Impl.pop(state)
+    assert worker == worker_3
+
+    {worker, state} = Impl.pop(state)
+    assert worker == worker_2
+
+    {worker, state} = Impl.pop(state)
+    assert worker == worker_1
+
+    assert Impl.pop(state) == :empty
+    assert Impl.empty?(state) == true
+  end
+end