Repository: commanded/commanded (elixir)

Issue:
Erlang error :timeout_value in Commanded.Aggregates.Aggregate
While sending commands in batches as part of a data migration, we saw the following error start to pop up hundreds at a time
```
Process #PID<0.12900.125> terminating ** (ErlangError) Erlang error: :timeout_value     (stdlib) gen_server.erl:411: :gen_server.loop/7     (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3 Initial Call: Commanded.Aggregates.Aggregate.init/1 Ancestors: [Commanded.Aggregates.Supervisor, #PID<0.2770.0>, #PID<0.2769.0>] Message Queue Length: 0 Messages: [] Links: [#PID<0.2782.0>, #PID<0.26612.120>, #PID<0.2772.0>] Dictionary: [] Trapping Exits: false Status: :running Heap Size: 6772 Stack Size: 27 Reductions: 57045 
```

We have the aggregate lifespan and the command execution timeouts set to 60_000. 

Pull request description:
Set default aggregate lifespan timeout to `:infinity`
To ensure the `lifespan_timeout` is always a valid timeout value that can be used for `GenServer` handle callbacks.

Fixes #199.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56f06c3..d63bd5d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 ### Bug fixes
 
 - Fix snapshot recording ([#196](https://github.com/commanded/commanded/pull/196)).
+- Set default aggregate lifespan timeout to `:infinity` ([#200](https://github.com/commanded/commanded/pull/200)).
 
 ## v0.17.0
 
diff --git a/lib/commanded/aggregates/aggregate.ex b/lib/commanded/aggregates/aggregate.ex
index 81dab13..52ef316 100644
--- a/lib/commanded/aggregates/aggregate.ex
+++ b/lib/commanded/aggregates/aggregate.ex
@@ -53,7 +53,7 @@ defmodule Commanded.Aggregates.Aggregate do
             aggregate_uuid: nil,
             aggregate_state: nil,
             aggregate_version: 0,
-            lifespan_timeout: nil,
+            lifespan_timeout: :infinity,
             snapshot_every: nil,
             snapshot_module_version: 1,
             snapshot_version: 0


Test patch:
diff --git a/test/aggregates/aggregate_lifespan_test.exs b/test/aggregates/aggregate_lifespan_test.exs
index c755acf..9879fcb 100644
--- a/test/aggregates/aggregate_lifespan_test.exs
+++ b/test/aggregates/aggregate_lifespan_test.exs
@@ -11,8 +11,9 @@ defmodule Commanded.Aggregates.AggregateLifespanTest do
     WithdrawMoney
   }
 
+  alias Commanded.ExampleDomain.BankAccount.Events.BankAccountOpened
   alias Commanded.EventStore
-
+  alias Commanded.EventStore.RecordedEvent
   alias Commanded.Registration
 
   describe "aggregate started" do
@@ -65,11 +66,12 @@ defmodule Commanded.Aggregates.AggregateLifespanTest do
       assert_receive {:DOWN, ^ref, :process, _, :normal}
     end
 
-    test "should adhere to aggregate lifespan when taking snapshot after receiving published event", %{
-      aggregate_uuid: aggregate_uuid,
-      pid: pid,
-      ref: ref
-    } do
+    test "should adhere to aggregate lifespan when taking snapshot after receiving published event",
+         %{
+           aggregate_uuid: aggregate_uuid,
+           pid: pid,
+           ref: ref
+         } do
       :ok = BankRouter.dispatch(%OpenAccount{account_number: aggregate_uuid, initial_balance: 10})
 
       events = aggregate_uuid |> EventStore.stream_forward() |> Enum.to_list()
@@ -85,11 +87,12 @@ defmodule Commanded.Aggregates.AggregateLifespanTest do
       assert {:ok, _snapshot} = EventStore.read_snapshot(aggregate_uuid)
     end
 
-    test "should adhere to aggregate lifespan when receiving published events after taking snapshot", %{
-      aggregate_uuid: aggregate_uuid,
-      pid: pid,
-      ref: ref
-    } do
+    test "should adhere to aggregate lifespan when receiving published events after taking snapshot",
+         %{
+           aggregate_uuid: aggregate_uuid,
+           pid: pid,
+           ref: ref
+         } do
       :ok = BankRouter.dispatch(%OpenAccount{account_number: aggregate_uuid, initial_balance: 10})
 
       events = aggregate_uuid |> EventStore.stream_forward() |> Enum.to_list()
@@ -104,31 +107,52 @@ defmodule Commanded.Aggregates.AggregateLifespanTest do
       assert_receive {:DOWN, ^ref, :process, _, :normal}
       assert {:ok, _snapshot} = EventStore.read_snapshot(aggregate_uuid)
     end
+
+    test "should use `:infinity` lifespan by default", %{
+      aggregate_uuid: aggregate_uuid,
+      pid: pid,
+      ref: ref
+    } do
+      events = [
+        %RecordedEvent{
+          event_id: UUID.uuid4(),
+          stream_version: 1,
+          data: %BankAccountOpened{account_number: aggregate_uuid, initial_balance: 1_000}
+        }
+      ]
+
+      # Simulate sending an event directly to the aggregate process
+      send(pid, {:events, events})
+
+      refute_receive {:DOWN, ^ref, :process, ^pid, _}
+    end
   end
 
   describe "deprecated `after_command/1` callback" do
     test "should fail to compile when missing `after_event/1` function" do
-      assert_raise ArgumentError, "Aggregate lifespan `BankAccountLifespan` does not define a callback function: `after_event/1`", fn ->
-        Code.eval_string """
-          alias Commanded.ExampleDomain.BankAccount
-          alias Commanded.ExampleDomain.BankAccount.Commands.{OpenAccount, DepositMoney}
-
-          defmodule BankAccountLifespan do
-            def after_command(%OpenAccount{}), do: 5
-            def after_command(%DepositMoney{}), do: 20
-            def after_command(_), do: :infinity
-          end
-
-          defmodule BankRouter do
-            use Commanded.Commands.Router
-
-            dispatch [OpenAccount],
-              to: BankAccount,
-              lifespan: BankAccountLifespan,
-              identity: :account_number
-          end
-        """
-      end
+      assert_raise ArgumentError,
+                   "Aggregate lifespan `BankAccountLifespan` does not define a callback function: `after_event/1`",
+                   fn ->
+                     Code.eval_string("""
+                       alias Commanded.ExampleDomain.BankAccount
+                       alias Commanded.ExampleDomain.BankAccount.Commands.{OpenAccount, DepositMoney}
+
+                       defmodule BankAccountLifespan do
+                         def after_command(%OpenAccount{}), do: 5
+                         def after_command(%DepositMoney{}), do: 20
+                         def after_command(_), do: :infinity
+                       end
+
+                       defmodule BankRouter do
+                         use Commanded.Commands.Router
+
+                         dispatch [OpenAccount],
+                           to: BankAccount,
+                           lifespan: BankAccountLifespan,
+                           identity: :account_number
+                       end
+                     """)
+                   end
     end
   end
 end