Repository: general-CbIC/poolex
Pull request #49 (2023-06-19 15:34:22)

Allow workers_count: 0
Previously if you sent `workers_count: 0` it generated two workers. With negative numbers, it had similar behaviors. This PR fixes it without additional code.

I modified a little bit the way of testing using messages instead of "sleeping". It is considered a better practice in general.


Patch:
diff --git a/lib/poolex.ex b/lib/poolex.ex
index 4367a3d..536c244 100644
--- a/lib/poolex.ex
+++ b/lib/poolex.ex
@@ -60,7 +60,7 @@ defmodule Poolex do
   @type poolex_option() ::
           {:pool_id, pool_id()}
           | {:worker_module, module()}
-          | {:workers_count, pos_integer()}
+          | {:workers_count, non_neg_integer()}
           | {:max_overflow, non_neg_integer()}
           | {:worker_args, list(any())}
           | {:worker_start_fun, atom()}
@@ -294,15 +294,27 @@ defmodule Poolex do
       worker_start_fun: worker_start_fun
     }
 
-    worker_pids =
-      Enum.map(1..workers_count, fn _ ->
-        {:ok, worker_pid} = start_worker(state)
-        Monitoring.add(monitor_id, worker_pid, :worker)
+    worker_pids = start_workers(workers_count, state, monitor_id)
+    {:ok, %State{state | idle_workers_state: IdleWorkers.init(idle_workers_impl, worker_pids)}}
+  end
 
-        worker_pid
-      end)
+  @spec start_workers(non_neg_integer(), State.t(), Monitoring.monitor_id()) :: [pid]
+  defp start_workers(0, _state, _monitor_id) do
+    []
+  end
 
-    {:ok, %State{state | idle_workers_state: IdleWorkers.init(idle_workers_impl, worker_pids)}}
+  defp start_workers(workers_count, _state, _monitor_id) when workers_count < 0 do
+    msg = "workers_count must be non negative number, received: #{inspect(workers_count)}"
+    raise ArgumentError, msg
+  end
+
+  defp start_workers(workers_count, state, monitor_id) do
+    Enum.map(1..workers_count, fn _ ->
+      {:ok, worker_pid} = start_worker(state)
+      Monitoring.add(monitor_id, worker_pid, :worker)
+
+      worker_pid
+    end)
   end
 
   @spec start_worker(State.t()) :: {:ok, pid()}


Test patch:
diff --git a/test/poolex_test.exs b/test/poolex_test.exs
index 7b6c8ff..23b5175 100644
--- a/test/poolex_test.exs
+++ b/test/poolex_test.exs
@@ -315,6 +315,45 @@ defmodule PoolexTest do
       assert debug_info.idle_workers_count == 0
       assert debug_info.overflow == 0
     end
+
+    test "allows workers_count: 0" do
+      pool_name = start_pool(worker_module: SomeWorker, workers_count: 0, max_overflow: 2)
+      debug_info = Poolex.get_debug_info(pool_name)
+
+      assert debug_info.idle_workers_count == 0
+      assert debug_info.idle_workers_pids == []
+      assert debug_info.busy_workers_count == 0
+      assert debug_info.busy_workers_pids == []
+      assert debug_info.overflow == 0
+
+      pid = self()
+
+      spawn(fn ->
+        Poolex.run!(pool_name, fn server ->
+          SomeWorker.traceable_call(server, pid, :foo, 50)
+        end)
+      end)
+
+      assert_receive {:traceable_start, :foo, worker_pid}
+      refute_received {:traceable_end, _, _}
+
+      debug_info = Poolex.get_debug_info(pool_name)
+
+      assert debug_info.idle_workers_count == 0
+      assert debug_info.idle_workers_pids == []
+      assert debug_info.busy_workers_count == 1
+      assert debug_info.busy_workers_pids == [worker_pid]
+      assert debug_info.overflow == 1
+
+      assert_receive {:traceable_end, :foo, ^worker_pid}
+      debug_info = Poolex.get_debug_info(pool_name)
+
+      assert debug_info.idle_workers_count == 0
+      assert debug_info.idle_workers_pids == []
+      assert debug_info.busy_workers_count == 0
+      assert debug_info.busy_workers_pids == []
+      assert debug_info.overflow == 0
+    end
   end
 
   describe "child_spec" do
diff --git a/test/support/some_worker.ex b/test/support/some_worker.ex
index f7df327..244ed7e 100644
--- a/test/support/some_worker.ex
+++ b/test/support/some_worker.ex
@@ -2,14 +2,27 @@ defmodule SomeWorker do
   @moduledoc false
   use GenServer
 
+  def traceable_call(server, pid, msg, delay) do
+    GenServer.call(server, {:traceable, pid, msg, delay})
+  end
+
   def start_link do
     GenServer.start_link(__MODULE__, [])
   end
 
+  @impl true
   def init(_options) do
     {:ok, :ok}
   end
 
+  @impl true
+  def handle_call({:traceable, pid, msg, delay}, _pid, state) do
+    send(pid, {:traceable_start, msg, self()})
+    :timer.sleep(delay)
+    Process.send_after(pid, {:traceable_end, msg, self()}, 10)
+    {:reply, :ok, state}
+  end
+
   def handle_call(:do_some_work, _from, state) do
     {:reply, :some_result, state}
   end