I’m working on an app that uses both Cachex
and Ecto
. The common pattern is to use Cachex.fetch
to wrap database queries, something like
Cachex.fetch(MyCache, "key", fn key -> database_query_by(key) end)
where the value is returned from cache when available, otherwise it calls the fallback function.
This is working fine when running the app, but it’s proving difficult to test because of the subtleties around Ecto Sandbox processes. Tests fail with a long error: cannot find ownership process for #PID<0.1708.0>
. The error message is thankfully very detailed, but I can’t seem to find a way to run these tests async because the only way I can get them to work is by running them in shared mode (i.e. with async: false
)
The test setup that works is:
setup tags do
repo_pid = Sandbox.start_owner!(MyRepo, shared: not tags[:async])
on_exit(fn ->
Sandbox.stop_owner(repo_pid)
end)
:ok
end
This ensures that the repo process is shared when the test module includes async: false
. With a little snooping around, I can see that Cachex
relies on GenServer.call/3
to execute the fallback function, so it’s happening in its own process. I was hoping to allow this process explicitly, e.g.
allow = Process.whereis(MyCache)
Ecto.Adapters.SQL.Sandbox.allow(MyRepo, self(), allow)
But that doesn’t work because the process identifying the cache is not the process executing these callback functions, so the above still gets the cannot find ownership process for #PID<0.1708.0>
errors. I think the crux of the matter is in Cachex.Services.Courier.handle_call/3
where the fallback function is executed inside an ad-hoc spawn/1
block. It’s not a named process, so you can’t “allow” it via Ecto.Adapters.SQL.Sandbox.allow/3
. You can put an IO.inspect(self())
inside a Cachex.fetch
fallback function and see that the pid changes each time you run it.
It seems like Cachex
+ Ecto
tests need to run async: false
, but I noticed this post:
@benwilson512 mentions the use of the :caller
option being passed to Ecto.Repo
functions. However, that post is from 2019, and I don’t see mention of a :caller
option anywhere in the Ecto
docs.
Can anyone shed light on this?