fireproofsocks

fireproofsocks

Cachex fallbacks vs. Ecto Sandbox process ownership

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?

First Post!

ruslandoga

ruslandoga

I think :shared mode actually exists for your exact use case: when you can’t explicitly allow every process to use the checked out repo. The error probably is coming from someplace else. Consider this test case:

defmodule Share.Test do
  use Share.DataCase

  test "repo can be called from async function" do
    test = self()

    spawn(fn ->
      assert Share.Repo.query!("select 1 + 1").rows == [[2]]
      send(test, :done)
    end)

    assert_receive :done
  end
end

It passes even though the spawned process is not explicitely allowed to use the repo.

Where Next?

Popular in Questions Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New

We're in Beta

About us Mission Statement