tommasoamici

tommasoamici

Ecto.Adapters.SQL.Sandbox and GenServer restarting with new PID

I’ve got an annoying problem on my hands. I’m open to a different approach as well.

I’m writing a GenServer to store a live auction, when bids are submitted they’re stored in the database with Ash actions and the GenServer picks them up from PubSub.

I want to introduce a recovery mechanism so that if a GenServer crashes it can reload from the database and continue working. My current implementation does this in the GenServer.init/1 callback.

I’m writing tests for this and something that I can’t figure out is: when a GenServer is killed, the DynamicSupervisor will restart it and it will run the init/1 again. So far so good.

However, the new process has a different PID and Ecto.Adapters.SQL.Sandbox doesn’t allow connecting from it. I want to avoid :shared mode so tests can be async.

At this point there’s a race condition: when starting up the GenServer wants to hit the database and it can’t so it crashes.
I can’t allow the new PID because I don’t know what it is and by the time I look it up in the registry, it’s already crashed :confused:

Has anyone managed to instrument tests for this scenario? Ideally without polluting the application code with these implementation details.

Marked As Solved

martosaur

martosaur

Make your genserver set :$callers key in process dictionary and Ecto sandbox should be able to figure ownership. More info on caller tracking: Task — Elixir v1.18.4

basically, you’ll have something like this:

defmodule MyGenserver do
  use GenServer

  def start_link(arg) do
    callers = Process.get(:"$callers", [])
    GenServer.start_link(__MODULE__, {arg, callers})
  end

  def init({arg, callers}) do
    Process.put(:"$callers", [self() | callers])
    ...
  end
end

Also Liked

al2o3cr

al2o3cr

An approach I’ve used is passing the test process’s PID along when starting the child in DynamicSupervisor.

The GenServer then calls Ecto.Adapters.SQL.Sandbox.allow(YourRepo, parent_pid, self())

I don’t know if there’s an “official” way to ask the repo “are you sandbox”; there wasn’t when I wrote the code using this feature, so I did it by checking Application.fetch_env!(:your_app_name, YourRepo)[:pool] - if it’s the atom Ecto.Adapters.SQL.Sandbox, then the sandbox is enabled.

While your GenServer is doing that, another useful thing to add is Process.monitor(parent_pid). That allows avoiding two race-for-the-flag scenarios that spam the logs and crash unexpectedly:

  • if the test process exits but the GenServer is still running, subsequent Repo interactions will raise an error. Once you’re monitoring parent_pid, the GenServer can take appropriate action when the :DOWN message arrives. Usually that action is to return :stop, but you may also have cleanup to do.
  • if the GenServer crashes at just the wrong time, the DynamicSupervisor will restart it after the test process has already exited. Again, this will cause any Repo interaction in the GenServer to fail.

One other note: make sure you’ve tuned the restart parameters of DynamicSupervisor correctly, especially when running fast tests in parallel. Nothing more frustrating than trying to figure out why your tests fail randomly, but only when all run together, and only on a fast machine…

jswanner

jswanner

This is my preferred approach as well, and it works for tests because :"$callers" is already set in the test process

Where Next?

Popular in Questions Top

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
Tee
can someone please explain to me how Enum.reduce works with maps
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
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
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

Other popular topics Top

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
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement