Repository: general-CbIC/poolex
Pull request #18 (2023-01-28 14:34:08)

Documentation
- Use `ex_check` for static analysis.
- Add `.tool-versions` file with elixir/OTP versions.
- Add contributing guide.
- Add documentation for `Poolex` functions.


Patch:
diff --git a/.check.exs b/.check.exs
new file mode 100644
index 0000000..468673d
--- /dev/null
+++ b/.check.exs
@@ -0,0 +1,33 @@
+[
+  ## don't run tools concurrently
+  # parallel: false,
+
+  ## don't print info about skipped tools
+  # skipped: false,
+
+  ## always run tools in fix mode (put it in ~/.check.exs locally, not in project config)
+  # fix: true,
+
+  ## don't retry automatically even if last run resulted in failures
+  # retry: false,
+
+  ## list of tools (see `mix check` docs for a list of default curated tools)
+  tools: [
+    ## curated tools may be disabled (e.g. the check for compilation warnings)
+    {:sobelow, false},
+    {:mix_audit, false}
+
+    ## ...or have command & args adjusted (e.g. enable skip comments for sobelow)
+    # {:sobelow, "mix sobelow --exit --skip"},
+
+    ## ...or reordered (e.g. to see output from dialyzer before others)
+    # {:dialyzer, order: -1},
+
+    ## ...or reconfigured (e.g. disable parallel execution of ex_unit in umbrella)
+    # {:ex_unit, umbrella: [parallel: false]},
+
+    ## custom new tools may be added (Mix tasks or arbitrary commands)
+    # {:my_task, "mix my_task", env: %{"MIX_ENV" => "prod"}},
+    # {:my_tool, ["my_tool", "arg with spaces"]}
+  ]
+]
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
index 0000000..4d82a50
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+erlang 25.2.1
+elixir 1.14.3-otp-25
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8dde9d1..dd2f8a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+## Added
+
+- Docs for functions with examples of use.
+
+## Changed
+
+- Use [ex_check](https://github.com/karolsluszniak/ex_check) for static analysis.
+
 ## [0.2.1] - 2023-01-27
 
 ### Changed
diff --git a/README.md b/README.md
index 95c6363..3a5fd9b 100644
--- a/README.md
+++ b/README.md
@@ -139,14 +139,9 @@ defmodule PoolexExample.Test do
 end
 ```
 
-Run the test function and see the result.
+Run the test function `PoolexExample.Test.start()` and see the result:
 
-```shell
-iex -S mix
-```
-
-```iex
-iex> PoolexExample.Test.start
+```text
 process #PID<0.227.0> calculating square root of 5
 process #PID<0.223.0> calculating square root of 1
 process #PID<0.225.0> calculating square root of 3
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
new file mode 100644
index 0000000..0a354e4
--- /dev/null
+++ b/docs/CONTRIBUTING.md
@@ -0,0 +1,123 @@
+# Contributing to Poolex
+
+Poolex is written in [Elixir](https://elixir-lang.org/).
+
+For branching management, this project uses [git-flow](https://github.com/petervanderdoes/gitflow-avh). The `main` branch is reserved for releases: the development process occurs on `develop` and `feature` branches. Please never commit to main.
+
+You can use [asdf](https://asdf-vm.com/) to set up required Elixir and OTP. Current versions are listed in the file `.tool-versions`.
+
+## Setup
+
+### Local repository
+
+1. Fork the repository.
+2. Clone your fork to a local repository:
+
+    ```shell
+    git clone https://github.com/your-login/poolex.git
+    cd poolex
+    ```
+
+3. Checkout `develop`:
+
+    ```shell
+    git checkout develop
+    ```
+
+### Development environment (using asdf)
+
+1. Install asdf by [Getting Started guideline](https://asdf-vm.com/guide/getting-started.html)
+2. Add plugins for elixir and OTP
+
+    ```shell
+    asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git
+    asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
+    ```
+
+3. Install tools:
+
+    ```shell
+    cd poolex
+    asdf install
+    ```
+
+### Development environment (without asdf)
+
+Please see [installation instructions](https://elixir-lang.org/install.html).
+
+### Git-flow
+
+If you want to use `git-flow` CLI, please check [installation instructions](https://github.com/petervanderdoes/gitflow-avh/wiki/Installation).
+
+### Building the project
+
+1. Fetch the project dependecies:
+
+    ```shell
+    cd poolex
+    mix deps.get
+    ```
+
+2. Run the static analyzers:
+
+    ```shell
+    mix check
+    ```
+
+## Workflow
+
+To make a change, please use this workflow:
+
+1. Checkout `develop` and apply the last upstream changes (use rebase, not merge!):
+
+    ```shell
+    git checkout develop
+    git fetch --all --prune
+    git rebase upstream/develop
+    ```
+
+2. For a tiny patch, create a new branch with an explicit name:
+
+    ```shell
+    git checkout -b <my_branch>
+    ```
+
+    Alternatively, if you are working on a feature which would need more work, you can create a feature branch with `git-flow`:
+
+    ```shell
+    git flow feature start <my_feature>
+    ```
+
+    *Note: always open an issue and ask before starting a big feature, to avoid it not beeing merged and your time lost.*
+
+3. When your feature is ready, feel free to use [interactive rebase](https://help.github.com/articles/about-git-rebase/) so your history looks clean and is easy to follow. Then, apply the last upstream changes on `develop` to prepare integration:
+
+    ```shell
+    git checkout develop
+    git fetch --all --prune
+    git rebase upstream/develop
+    ```
+
+4. If there were commits on `develop` since the beginning of your feature branch, integrate them by **rebasing** if your branch has few commits, or merging if you had a long-lived branch:
+
+    ```shell
+    git checkout <my_feature_branch>
+    git rebase develop
+    ```
+
+    *Note: the only case you should merge is when you are working on a big feature. If it is the case, we should have discussed this before as stated above.*
+
+5. Run the tests and static analyzers to ensure there is no regression and all works as expected:
+
+    ```shell
+    mix check
+    ```
+
+6. If it’s all good, open a pull request to merge your branch into the `develop` branch on the main repository.
+
+## Coding style
+
+Please format your code with `mix format` or your editor and follow
+[this style guide](https://github.com/christopheradams/elixir_style_guide).
+
+All contributed code must be documented and functions must have typespecs. In general, take your inspiration from the existing code.
diff --git a/lib/poolex.ex b/lib/poolex.ex
index 79d65e9..52a1a8e 100644
--- a/lib/poolex.ex
+++ b/lib/poolex.ex
@@ -16,26 +16,92 @@ defmodule Poolex do
           | {:worker_args, list(any())}
           | {:workers_count, pos_integer()}
 
+  @doc """
+  Starts a Poolex process without links (outside of a supervision tree).
+
+  See start_link/2 for more information.
+
+  ## Examples
+
+      iex> Poolex.start(:my_pool, worker_module: Agent, worker_args: [fn -> 0 end], workers_count: 5)
+      iex> %Poolex.State{idle_workers_count: idle_workers_count} = Poolex.get_state(:my_pool)
+      iex> idle_workers_count
+      5
+  """
   @spec start(pool_id(), list(poolex_option())) :: GenServer.on_start()
   def start(pool_id, opts) do
     GenServer.start(__MODULE__, {pool_id, opts}, name: pool_id)
   end
 
+  @doc """
+  Starts a Poolex process linked to the current process.
+
+  This is often used to start the Poolex as part of a supervision tree.
+
+  After the process is started, you can access it using the previously specified `pool_id`.
+
+  ## Options
+
+  | Option             | Description                                    | Example        | Default value          |
+  |--------------------|------------------------------------------------|----------------|------------------------|
+  | `worker_module`    | Name of module that implements our worker      | `MyApp.Worker` | **option is required** |
+  | `worker_start_fun` | Name of the function that starts the worker    | `:run`         | `:start`               |
+  | `worker_args`      | List of arguments passed to the start function | `[:gg, "wp"]`  | `[]`                   |
+  | `workers_count`    | How many workers should be running in the pool | `5`            | **option is required** |
+
+  ## Examples
+
+      iex> Poolex.start_link(:my_pool, worker_module: Agent, worker_args: [fn -> 0 end], workers_count: 5)
+      iex> %Poolex.State{idle_workers_count: idle_workers_count} = Poolex.get_state(:my_pool)
+      iex> idle_workers_count
+      5
+  """
   @spec start_link(pool_id(), list(poolex_option())) :: GenServer.on_start()
   def start_link(pool_id, opts) do
     GenServer.start_link(__MODULE__, {pool_id, opts}, name: pool_id)
   end
 
+  @doc """
+  Same as `run!/3` but handles runtime_errors.
+
+  Returns:
+    * `{:runtime_error, reason}` on errors.
+    * `:all_workers_are_busy` if no free worker was found before the timeout.
+
+  See `run!/3` for more information.
+
+  ## Examples
+
+      iex> Poolex.start_link(:some_pool, worker_module: Agent, worker_args: [fn -> 5 end], workers_count: 1)
+      iex> Poolex.run(:some_pool, fn _pid -> raise RuntimeError end)
+      {:runtime_error, %RuntimeError{message: "runtime error"}}
+      iex> Poolex.run(:some_pool, fn pid -> Agent.get(pid, &(&1)) end)
+      {:ok, 5}
+  """
   @type run_option() :: {:timeout, timeout()}
   @spec run(pool_id(), (worker :: pid() -> any()), list(run_option())) ::
           {:ok, any()} | :all_workers_are_busy | {:runtime_error, any()}
   def run(pool_id, fun, options \\ []) do
     {:ok, run!(pool_id, fun, options)}
+  rescue
+    runtime_error -> {:runtime_error, runtime_error}
   catch
     :exit, {:timeout, _meta} -> :all_workers_are_busy
     :exit, reason -> {:runtime_error, reason}
   end
 
+  @doc """
+  The main function for working with the pool.
+
+  When executed, an attempt is made to obtain a worker with the specified timeout (5 seconds by default).
+  In case of successful execution of the passed function, the result will be returned, otherwise an error will be raised.
+
+  ## Examples
+
+      iex> Poolex.start_link(:some_pool, worker_module: Agent, worker_args: [fn -> 5 end], workers_count: 1)
+      iex> Poolex.run!(:some_pool, fn pid -> Agent.get(pid, &(&1)) end)
+      5
+  """
   @spec run!(pool_id(), (worker :: pid() -> any()), list(run_option())) :: any()
   def run!(pool_id, fun, options \\ []) do
     timeout = Keyword.get(options, :timeout, @default_wait_timeout)
@@ -49,11 +115,28 @@ defmodule Poolex do
     end
   end
 
+  @doc """
+  Returns current state of started pool.
+
+  Primarily needed to help with debugging.
+
+  ## Examples
+
+      iex> Poolex.start(:my_pool, worker_module: Agent, worker_args: [fn -> 0 end], workers_count: 5)
+      iex> state = %Poolex.State{} = Poolex.get_state(:my_pool)
+      iex> state.busy_workers_count
+      0
+      iex> state.idle_workers_count
+      5
+      iex> state.worker_module
+      Agent
+  """
   @spec get_state(pool_id()) :: State.t()
   def get_state(pool_id) do
     GenServer.call(pool_id, :get_state)
   end
 
+  @impl GenServer
   def init({pool_id, opts}) do
     worker_module = Keyword.fetch!(opts, :worker_module)
     workers_count = Keyword.fetch!(opts, :workers_count)
@@ -93,6 +176,7 @@ defmodule Poolex do
     })
   end
 
+  @impl GenServer
   def handle_call(
         :get_idle_worker,
         {from_pid, _} = caller,
@@ -131,6 +215,7 @@ defmodule Poolex do
     {:reply, state, state}
   end
 
+  @impl GenServer
   def handle_cast(
         {:release_busy_worker, worker_pid},
         %State{
@@ -166,6 +251,7 @@ defmodule Poolex do
     {:noreply, %{state | waiting_callers: left_waiting_callers}}
   end
 
+  @impl GenServer
   def handle_info(
         {:DOWN, monitoring_reference, _process, dead_process_pid, _reason},
         %State{
diff --git a/lib/poolex/monitoring.ex b/lib/poolex/monitoring.ex
index e6ab889..c921bf4 100644
--- a/lib/poolex/monitoring.ex
+++ b/lib/poolex/monitoring.ex
@@ -1,11 +1,10 @@
 defmodule Poolex.Monitoring do
-  @moduledoc """
-  Interfaces to hide monitoring implementation.
-  """
+  @moduledoc false
   @type monitor_id() :: atom() | reference()
   @type kind_of_process() :: :worker | :caller
 
   @spec init(Poolex.pool_id()) :: {:ok, monitor_id()}
+  @doc false
   def init(pool_id) do
     monitor_id = :ets.new(:"#{pool_id}_references", [:set, :named_table, :private])
 
@@ -13,6 +12,7 @@ defmodule Poolex.Monitoring do
   end
 
   @spec add(monitor_id(), pid(), kind_of_process()) :: :ok
+  @doc false
   def add(monitor_id, process_pid, kind_of_process) do
     reference = Process.monitor(process_pid)
     :ets.insert_new(monitor_id, {reference, process_pid, kind_of_process})
@@ -21,6 +21,7 @@ defmodule Poolex.Monitoring do
   end
 
   @spec remove(monitor_id(), reference()) :: kind_of_process()
+  @doc false
   def remove(monitor_id, monitoring_reference) do
     true = Process.demonitor(monitoring_reference)
     [{_reference, _pid, kind_of_process}] = :ets.lookup(monitor_id, monitoring_reference)
diff --git a/lib/poolex/supervisor.ex b/lib/poolex/supervisor.ex
index 0db0a90..a238281 100644
--- a/lib/poolex/supervisor.ex
+++ b/lib/poolex/supervisor.ex
@@ -2,10 +2,13 @@ defmodule Poolex.Supervisor do
   @moduledoc false
   use DynamicSupervisor
 
+  @doc false
+  @spec start_link() :: Supervisor.on_start()
   def start_link do
     DynamicSupervisor.start_link(__MODULE__, nil)
   end
 
+  @doc false
   def init(_arg) do
     DynamicSupervisor.init(strategy: :one_for_one)
   end
diff --git a/mix.exs b/mix.exs
index 930d6be..76cc212 100644
--- a/mix.exs
+++ b/mix.exs
@@ -27,9 +27,11 @@ defmodule Poolex.MixProject do
   # Run "mix help deps" to learn about dependencies.
   defp deps do
     [
-      {:credo, "~> 1.6.7", runtime: false, optional: true, only: [:dev, :test]},
-      {:ex_doc, "~> 0.29.0", only: :dev, runtime: false},
-      {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}
+      {:ex_check, "~> 0.15.0", only: [:dev], runtime: false},
+      {:credo, ">= 0.0.0", only: [:dev], runtime: false},
+      {:dialyxir, ">= 0.0.0", only: [:dev], runtime: false},
+      {:doctor, ">= 0.0.0", only: [:dev], runtime: false},
+      {:ex_doc, ">= 0.0.0", only: [:dev], runtime: false}
     ]
   end
 
@@ -43,7 +45,7 @@ defmodule Poolex.MixProject do
   defp docs do
     [
       main: "Poolex",
-      extras: ["README.md"]
+      extras: ["README.md", "docs/CONTRIBUTING.md"]
     ]
   end
 end
diff --git a/mix.lock b/mix.lock
index c2f58fa..6fcc234 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,9 +1,12 @@
 %{
   "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
   "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"},
+  "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
   "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
+  "doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"},
   "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
   "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
+  "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"},
   "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
   "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
   "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},


Test patch:
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index fe0ab4b..b4ac97c 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -9,9 +9,6 @@ on:
   pull_request:
     branches: [ develop ]
 
-env:
-  MIX_ENV: test
-
 jobs:
   test:
     runs-on: ubuntu-20.04
@@ -46,20 +43,9 @@ jobs:
         run: mix do deps.get, compile
       - name: Launch tests
         run: mix test
-  format-check:
+  static-analysis:
     runs-on: ubuntu-20.04
-    name: Check code format
-    steps:
-      - uses: actions/checkout@v3
-      - uses: erlef/setup-beam@v1
-        with:
-          otp-version: '25.2'
-          elixir-version: '1.14.3'
-      - name: Check code format
-        run: mix format --check-formatted
-  credo:
-    runs-on: ubuntu-20.04
-    name: Check code format
+    name: Static analysis
     steps:
       - uses: actions/checkout@v3
       - uses: erlef/setup-beam@v1
@@ -67,6 +53,6 @@ jobs:
           otp-version: '25.2'
           elixir-version: '1.14.3'
       - name: Fetch dependencies
-        run: mix do deps.get
-      - name: Credo check
-        run: mix credo
+        run: mix do deps.get, compile
+      - name: Run
+        run: mix check
diff --git a/test/poolex_test.exs b/test/poolex_test.exs
index c0e6b14..081bc74 100644
--- a/test/poolex_test.exs
+++ b/test/poolex_test.exs
@@ -1,5 +1,6 @@
 defmodule PoolexTest do
   use ExUnit.Case
+  doctest Poolex
 
   setup do
     [pool_name: pool_name()]