From cd7bca7fc060ade8d50c7881d8edf4bf3ec611b2 Mon Sep 17 00:00:00 2001 From: Christian Alexander Date: Sat, 28 Feb 2026 17:35:33 -0700 Subject: [PATCH] fix: check Horde.Registry instead of Horde for compile-time detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit horde 0.10 has no root `Horde` module, so `Code.ensure_loaded?(Horde)` always returned false. This prevented the Horde backend, Singleton, and the horde-mode branch of `impl/0` from ever being compiled — even when horde was installed as a dependency. Check `Horde.Registry` instead, which is the actual module provided by horde 0.10. --- CHANGELOG.md | 4 ++ lib/durable_object/cluster.ex | 2 +- lib/durable_object/cluster/horde.ex | 2 +- lib/durable_object/singleton.ex | 2 +- test/durable_object/cluster_test.exs | 75 ++++++++++++++-------------- 5 files changed, 44 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7780e4..afd7440 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] +### Fixed + +- Horde compile-time detection now checks `Horde.Registry` instead of `Horde` — horde 0.10 has no root `Horde` module, so `Code.ensure_loaded?(Horde)` always returned `false`, preventing the Horde backend from being compiled even when the dependency was installed + ## [0.3.1] - 2026-02-26 ### Fixed diff --git a/lib/durable_object/cluster.ex b/lib/durable_object/cluster.ex index ec3c9ef..32cdcde 100644 --- a/lib/durable_object/cluster.ex +++ b/lib/durable_object/cluster.ex @@ -42,7 +42,7 @@ defmodule DurableObject.Cluster do Returns the backend module for the current mode. """ @spec impl() :: module() - if Code.ensure_loaded?(Horde) do + if Code.ensure_loaded?(Horde.Registry) do def impl do case mode() do :local -> DurableObject.Cluster.Local diff --git a/lib/durable_object/cluster/horde.ex b/lib/durable_object/cluster/horde.ex index f0fb450..d4dd614 100644 --- a/lib/durable_object/cluster/horde.ex +++ b/lib/durable_object/cluster/horde.ex @@ -1,4 +1,4 @@ -if Code.ensure_loaded?(Horde) do +if Code.ensure_loaded?(Horde.Registry) do defmodule DurableObject.Cluster.Horde do @moduledoc """ Horde-mode cluster backend using Horde.Registry and Horde.DynamicSupervisor. diff --git a/lib/durable_object/singleton.ex b/lib/durable_object/singleton.ex index 2e70719..03cb586 100644 --- a/lib/durable_object/singleton.ex +++ b/lib/durable_object/singleton.ex @@ -1,4 +1,4 @@ -if Code.ensure_loaded?(Horde) do +if Code.ensure_loaded?(Horde.Registry) do defmodule DurableObject.Singleton do @moduledoc """ Cluster singleton utility for ensuring only one instance of a process diff --git a/test/durable_object/cluster_test.exs b/test/durable_object/cluster_test.exs index 17037ae..3775502 100644 --- a/test/durable_object/cluster_test.exs +++ b/test/durable_object/cluster_test.exs @@ -70,39 +70,17 @@ defmodule DurableObject.ClusterTest do end end - if Code.ensure_loaded?(Horde) do - test "returns Horde for horde mode" do - original = Application.get_env(:durable_object, :registry_mode) - - try do - Application.put_env(:durable_object, :registry_mode, :horde) - assert Cluster.impl() == DurableObject.Cluster.Horde - after - if original do - Application.put_env(:durable_object, :registry_mode, original) - else - Application.delete_env(:durable_object, :registry_mode) - end - end - end - end - - test "raises helpful error for horde mode without Horde installed" do - unless Code.ensure_loaded?(Horde) do - original = Application.get_env(:durable_object, :registry_mode) - - try do - Application.put_env(:durable_object, :registry_mode, :horde) + test "returns Horde for horde mode" do + original = Application.get_env(:durable_object, :registry_mode) - assert_raise RuntimeError, ~r/Horde mode requires the :horde dependency/, fn -> - Cluster.impl() - end - after - if original do - Application.put_env(:durable_object, :registry_mode, original) - else - Application.delete_env(:durable_object, :registry_mode) - end + try do + Application.put_env(:durable_object, :registry_mode, :horde) + assert Cluster.impl() == DurableObject.Cluster.Horde + after + if original do + Application.put_env(:durable_object, :registry_mode, original) + else + Application.delete_env(:durable_object, :registry_mode) end end end @@ -147,15 +125,36 @@ defmodule DurableObject.ClusterTest do end end - if Code.ensure_loaded?(Horde) do - describe "Horde backend" do - test "generates correct via_tuple format" do - via = DurableObject.Cluster.Horde.via_tuple(MyModule, "object-123") + describe "Horde backend" do + test "module is defined when horde dependency is available" do + assert Code.ensure_loaded?(Horde.Registry), + "Horde.Registry should be available (horde is in deps)" - assert {:via, Horde.Registry, {DurableObject.HordeRegistry, {MyModule, "object-123"}}} = - via + assert {:module, DurableObject.Cluster.Horde} = + Code.ensure_loaded(DurableObject.Cluster.Horde) + end + + test "impl/0 returns Horde backend in horde mode" do + original = Application.get_env(:durable_object, :registry_mode) + + try do + Application.put_env(:durable_object, :registry_mode, :horde) + assert Cluster.impl() == DurableObject.Cluster.Horde + after + if original do + Application.put_env(:durable_object, :registry_mode, original) + else + Application.delete_env(:durable_object, :registry_mode) + end end end + + test "generates correct via_tuple format" do + via = DurableObject.Cluster.Horde.via_tuple(MyModule, "object-123") + + assert {:via, Horde.Registry, {DurableObject.HordeRegistry, {MyModule, "object-123"}}} = + via + end end describe "integration with ObjectSupervisor" do