Repository: commanded/commanded
Pull request #399 (2020-08-13 22:49:29)

Add `hibernate_after` option to application config
The `hibernate_after` option is used by the Commanded application's top-level supervisor when starting its children. This determines whether the internal `GenServer` processes used by Commanded, such as the aggregates dynamic supervisor, hibernate after a period of inactivity. See [`GenServer.start_link/3`](https://hexdocs.pm/elixir/GenServer.html#start_link/3) for details.

The default value is `:infinity` which disables hibernation. You can configure the value when defining or starting your own Commanded application.

#### Examples

Configured at compile-time:

```elixir
defmodule MyApp do
  use Commanded.Application, 
    otp_app: :my_app, 
    hibernate_after: :timer.minutes(1)
end
```

Configured at runtime:

```elixir
{:ok, _pid} = MyApp.start_link(hibernate_after: :timer.minutes(1))
```

Or supervised:

```elixir
Supervisor.start_link(
  [
    {MyApp, hibernate_after: :timer.seconds(15)}
  ],
  strategy: :one_for_one
)
```




Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 353e895..bb9aa23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
 - Add `init/1` callback function to event handlers and process managers ([#393](https://github.com/commanded/commanded/pull/393)).
 - Include `application` and `handler_name` as additional event handler metadata ([#396](https://github.com/commanded/commanded/pull/396)).
 - Allow `GenServer` start options to be provided when starting event handlers and process managers ([#398](https://github.com/commanded/commanded/pull/398)).
+- Add `hibernate_after` option to application config ([#399](https://github.com/commanded/commanded/pull/399)).
 
 ## v1.1.1
 
diff --git a/lib/commanded/aggregates/aggregate.ex b/lib/commanded/aggregates/aggregate.ex
index bd54d8b..0120097 100644
--- a/lib/commanded/aggregates/aggregate.ex
+++ b/lib/commanded/aggregates/aggregate.ex
@@ -71,7 +71,7 @@ defmodule Commanded.Aggregates.Aggregate do
 
   def start_link(config, opts) do
     {start_opts, aggregate_opts} =
-      Keyword.split(opts, [:name, :timeout, :debug, :spawn_opt, :hibernate_after])
+      Keyword.split(opts, [:debug, :name, :timeout, :spawn_opt, :hibernate_after])
 
     aggregate_module = Keyword.fetch!(aggregate_opts, :aggregate_module)
     aggregate_uuid = Keyword.fetch!(aggregate_opts, :aggregate_uuid)
diff --git a/lib/commanded/aggregates/supervisor.ex b/lib/commanded/aggregates/supervisor.ex
index 8fc9e85..6d5aa1e 100644
--- a/lib/commanded/aggregates/supervisor.ex
+++ b/lib/commanded/aggregates/supervisor.ex
@@ -10,11 +10,11 @@ defmodule Commanded.Aggregates.Supervisor do
   alias Commanded.Aggregates.Aggregate
   alias Commanded.Registration
 
-  def start_link(args) do
-    application = Keyword.fetch!(args, :application)
-    name = Module.concat([application, __MODULE__])
+  def start_link(opts) do
+    {start_opts, supervisor_opts} =
+      Keyword.split(opts, [:debug, :name, :timeout, :spawn_opt, :hibernate_after])
 
-    DynamicSupervisor.start_link(__MODULE__, args, name: name)
+    DynamicSupervisor.start_link(__MODULE__, supervisor_opts, start_opts)
   end
 
   @doc """
diff --git a/lib/commanded/application/supervisor.ex b/lib/commanded/application/supervisor.ex
index 43d4f5d..71632e5 100644
--- a/lib/commanded/application/supervisor.ex
+++ b/lib/commanded/application/supervisor.ex
@@ -68,15 +68,23 @@ defmodule Commanded.Application.Supervisor do
 
   defp app_child_spec(name, config) do
     task_dispatcher_name = Module.concat([name, Commanded.Commands.TaskDispatcher])
+    aggregates_supervisor_name = Module.concat([name, Commanded.Aggregates.Supervisor])
     subscriptions_name = Module.concat([name, Commanded.Subscriptions])
     registry_name = Module.concat([name, Commanded.Subscriptions.Registry])
     snapshotting = Keyword.get(config, :snapshotting, %{})
+    hibernate_after = Keyword.get(config, :hibernate_after, :infinity)
 
     [
-      {Task.Supervisor, name: task_dispatcher_name},
-      {Commanded.Aggregates.Supervisor, application: name, snapshotting: snapshotting},
-      {Commanded.Subscriptions.Registry, application: name, name: registry_name},
-      {Commanded.Subscriptions, application: name, name: subscriptions_name}
+      {Task.Supervisor, name: task_dispatcher_name, hibernate_after: hibernate_after},
+      {Commanded.Aggregates.Supervisor,
+       name: aggregates_supervisor_name,
+       application: name,
+       snapshotting: snapshotting,
+       hibernate_after: hibernate_after},
+      {Commanded.Subscriptions.Registry,
+       application: name, name: registry_name, hibernate_after: hibernate_after},
+      {Commanded.Subscriptions,
+       application: name, name: subscriptions_name, hibernate_after: hibernate_after}
     ]
   end
 
diff --git a/lib/commanded/event/handler.ex b/lib/commanded/event/handler.ex
index 0e9318e..337c274 100644
--- a/lib/commanded/event/handler.ex
+++ b/lib/commanded/event/handler.ex
@@ -437,7 +437,7 @@ defmodule Commanded.Event.Handler do
   end
 
   # GenServer start options
-  @start_opts [:name, :timeout, :debug, :spawn_opt, :hibernate_after]
+  @start_opts [:debug, :name, :timeout, :spawn_opt, :hibernate_after]
 
   # Event handler configuration options
   @handler_opts [:application, :name, :consistency, :start_from, :subscribe_to]
diff --git a/lib/commanded/event_store/adapters/in_memory.ex b/lib/commanded/event_store/adapters/in_memory.ex
index 68d2108..07d4789 100644
--- a/lib/commanded/event_store/adapters/in_memory.ex
+++ b/lib/commanded/event_store/adapters/in_memory.ex
@@ -26,7 +26,8 @@ defmodule Commanded.EventStore.Adapters.InMemory do
   alias Commanded.EventStore.{EventData, RecordedEvent, SnapshotData}
 
   def start_link(opts \\ []) do
-    {start_opts, in_memory_opts} = Keyword.split(opts, [:name, :timeout, :debug, :spawn_opt])
+    {start_opts, in_memory_opts} =
+      Keyword.split(opts, [:debug, :name, :timeout, :spawn_opt, :hibernate_after])
 
     state = %State{
       name: Keyword.fetch!(opts, :name),
diff --git a/lib/commanded/process_managers/process_manager.ex b/lib/commanded/process_managers/process_manager.ex
index 1c4ea87..5b6c0cb 100644
--- a/lib/commanded/process_managers/process_manager.ex
+++ b/lib/commanded/process_managers/process_manager.ex
@@ -462,7 +462,7 @@ defmodule Commanded.ProcessManagers.ProcessManager do
   defdelegate identity(), to: Commanded.ProcessManagers.ProcessManagerInstance
 
   # GenServer start options
-  @start_opts [:name, :timeout, :debug, :spawn_opt, :hibernate_after]
+  @start_opts [:debug, :name, :timeout, :spawn_opt, :hibernate_after]
 
   # Process manager configuration options
   @handler_opts [
diff --git a/lib/commanded/process_managers/process_router.ex b/lib/commanded/process_managers/process_router.ex
index b9397b1..7b2a052 100644
--- a/lib/commanded/process_managers/process_router.ex
+++ b/lib/commanded/process_managers/process_router.ex
@@ -37,7 +37,7 @@ defmodule Commanded.ProcessManagers.ProcessRouter do
 
   def start_link(application, process_name, process_module, opts \\ []) do
     {start_opts, router_opts} =
-      Keyword.split(opts, [:name, :timeout, :debug, :spawn_opt, :hibernate_after])
+      Keyword.split(opts, [:debug, :name, :timeout, :spawn_opt, :hibernate_after])
 
     name = name(application, process_name)
     consistency = Keyword.get(router_opts, :consistency, :eventual)
diff --git a/lib/commanded/subscriptions.ex b/lib/commanded/subscriptions.ex
index 638cf5a..983ca81 100644
--- a/lib/commanded/subscriptions.ex
+++ b/lib/commanded/subscriptions.ex
@@ -17,7 +17,8 @@ defmodule Commanded.Subscriptions do
   ]
 
   def start_link(opts) do
-    {start_opts, subscriptions_opts} = Keyword.split(opts, [:name, :timeout, :debug, :spawn_opt])
+    {start_opts, subscriptions_opts} =
+      Keyword.split(opts, [:debug, :name, :timeout, :spawn_opt, :hibernate_after])
 
     GenServer.start_link(__MODULE__, subscriptions_opts, start_opts)
   end
diff --git a/lib/commanded/subscriptions/registry.ex b/lib/commanded/subscriptions/registry.ex
index 0414a91..40ff34f 100644
--- a/lib/commanded/subscriptions/registry.ex
+++ b/lib/commanded/subscriptions/registry.ex
@@ -8,7 +8,8 @@ defmodule Commanded.Subscriptions.Registry do
   use GenServer
 
   def start_link(opts) do
-    {start_opts, registry_opts} = Keyword.split(opts, [:name, :timeout, :debug, :spawn_opt])
+    {start_opts, registry_opts} =
+      Keyword.split(opts, [:debug, :name, :timeout, :spawn_opt, :hibernate_after])
 
     GenServer.start_link(__MODULE__, registry_opts, start_opts)
   end


Test patch:
diff --git a/test/application/application_supervisor_test.exs b/test/application/application_supervisor_test.exs
new file mode 100644
index 0000000..ef1d1a0
--- /dev/null
+++ b/test/application/application_supervisor_test.exs
@@ -0,0 +1,33 @@
+defmodule Commanded.Aggregates.ApplicationSupervisorTest do
+  use ExUnit.Case
+
+  alias Commanded.DefaultApp
+  alias Commanded.Helpers.Wait
+
+  describe "application supervisor configured with `hibernate_after`" do
+    setup do
+      start_supervised!({DefaultApp, hibernate_after: 1})
+      :ok
+    end
+
+    test "children are hibernated after inactivity" do
+      for module <- [
+            Commanded.Commands.TaskDispatcher,
+            Commanded.Aggregates.Supervisor,
+            Commanded.Subscriptions,
+            Commanded.Subscriptions.Registry
+          ] do
+        name = Module.concat([DefaultApp, module])
+        pid = Process.whereis(name)
+
+        assert is_pid(pid)
+
+        Wait.until(fn -> assert_hibernated(pid) end)
+      end
+    end
+  end
+
+  defp assert_hibernated(pid) do
+    assert Process.info(pid, :current_function) == {:current_function, {:erlang, :hibernate, 3}}
+  end
+end