totorigolo

totorigolo

Ecto Sandbox ownership issue when persisting state in terminate callback

Hello,

Context
I am developing a toy website to get myself familiar with Elixir and Phoenix. The website is like multiple shared canvases where everyone with a token can change pixels (a bit like multiple tiny https://pixelcanvas.io/). On the home page, you can see auto-refreshing thumbnails of each canvas, and when you click on one, you see the full-size version with live updates using a Phoenix Channel. The canvases can be created or deleted on the fly in the admin section, which will create the new canvas (UUID, name) in the DB and spin-up a (unique) GenServer (per canvas). Without persistence, I successfully deployed it in a cluster, using global processes (via Horde), so far so good.

Now, I want to persist the images. To do that, whenever a canvas GenServer terminates, it saves the PNG blob in the DB. When it starts, it tries to load it from the DB. This works well, I can add/remove nodes from the cluster and the image survives.

Here is how canvas GenServers are handled:

  • when I create a canvas using Canvases.create_canvas/1, the function creates the row in DB, starts the GenServer under a (Horde) DynamicSupervisor using CanvasManager.start_canvas_server/1, and publishes a :canvas_created PubSub event to update the LiveViews.
  • when I delete a canvas, using Canvas.delete_canvas/1, it deletes the canvas and persisted images from the DB and publishes a :canvas_deleted PubSub event for both the LiveView and the CanvasManager, which will in turn terminate the GenServer.
  • when the application starts, CanvasManager is started under the supervision tree and will start all canvases.

The GenServer is needed to interact with the canvas: get the version, get the image as PNG, get a pixel color, set a pixel color. The DB is only used to list the existing canvases and persist the image.

Problem
I have a hard time figuring out how to test the GenServer now that there is persistence. I understand the issue, but I don’t know what’s the best way to address it: when I create a new canvas, I call Canvases.create_canvas/1 via a fixture; this will also start the GenServer under my application’s supervisor (not ExUnit’s). To stop the GenServer a the end of the test, the fixture calls ExUnit.Callbacks.on_exit/1. The GenServer cannot work without the row in DB (will fail on termination), and I want to keep the supervision tree clean after each test. The issue is that on_exit is executed in a different process than the test one, which is not the owner of the sandbox DB connection.

I read in some issue on GitHub that Jose does not recommend updating the DB from inside on_exit, so I think that there is something wrong with my design. What would you suggest then?

  • Manually waiting in every test that creates a canvas for the GenServer to terminate?
  • Not starting GenServer in tests that don’t need it? How? And that does not solve the issue for the tests that do need it.
  • Start the processes using ExUnit.Callbacks.start_supervised/2? How can I do that? Would that solve the ownership issue?
  • Something else? I don’t have much ideas.

Please tell me if you need any more details. The full source code is on GitHub (“canvas” => “lobby”/“image”, the GenServer is called ImageServer, I renamed here for clarity).

Thanks for the help!
Thomas

* For the curious: I am using a straightforward Rust NIF via Rustler to be able to mutate images.

Where Next?

Popular in Questions Top

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
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
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
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
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
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
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
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
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
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
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
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 43622 214
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement