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

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
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
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
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
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
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
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

Other popular topics Top

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
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
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

We're in Beta

About us Mission Statement