How do mix (umbrella) apps receive messages, non-blockingly?

I’m very much a beginner at Elixir, so I apologise if this is a basic question.

TL;DR I’m struggling to get the repo portion of my LiveView umbrella app to wait for & respond to potential incoming messages from other processes, and in a non-blocking way, in the manner GenServers do.

A little more detail: I’m building a simple umbrella app with a LiveView interface as one ‘sub-app’ (as I call them - i.e. the mix apps in the umbrella’s /apps/ folder), and a ‘plain elixir’ (i.e. made with mix new …) sub-app acting as a database repo. The repo, however, is also supposed to 1) spawn and interact with several GenServer processes, whilst at the same time 2) executing instructions from the phoenix interface (or from me via IEX, at the moment), in between waiting for these spawned processes’ messages (i.e., the repo shouldn’t be blocked, just because I’ve told it to expect a particular message from a different process, that may arrive some point in the future).

With a GenServer process, this is of course defacto behaviour - the process can execute code (when instructed to, by an incoming message) whilst its mailbox fills up, until it’s finished its current task. And then with handle_call/handle_cast/handle_info, it deals with the next message in the queue. Rinse and repeat.

Problem: But, I’m struggling to figure out simply where/how to put code to make the process my LiveView/repo umbrella app runs in operate similarly, without either manually coding some kind of GenServer-imitation looping process (to avoid the blocking of receive do), or using Phoenix PubSub - both of which seem hacky?

More Info: As I understand it, when a client reaches out to the server, a single process is created to run all the LiveView/repo code, and so deal with the client’s interaction. In the code generated by the mix CLI, I can’t seem to find any functions (in either the LiveView or repo sub-apps) that define default behaviour for incoming messages, that I can override? If this process were a GenServer, I could (I think) define handle_* callback functions to accommodate messages coming in from other processes, and still interact with the phoenix interface (or again, IEX for now), and thus the client.

But ‘converting the server process to a GenServer’ myself isn’t something I’ve read anywhere, and would seem very strange? Plus, I wouldn’t know where to put the “use Genserver” etc code, if I had to? I could maybe use Phoenix PubSub in this main server process, and also use it in the GenServer processes spawned, to message back and forth between them, but that not only seems inappropriate, doesn’t it defeat the elixir m.o. of point of direct messaging between two processes (or, at least using an overkill of a system to achieve it)?

I apologise if this was too bulky, and too basic, a question - or if it was unclear; if so, please let me know, and I’ll reply more clearly. The problem is probably that I’ve misunderstood some of the basic elixir paradigms at play here, and am trying to square a circle. So, thanks so much for your time, and for any ideas as to what’s the correct thing to do. I really appreciate it.

Thanks!

If you want to customize how your requests are processed, I think you should have a look at Plug. This is what handles your requests in a Phoenix app.

Moreover, have a look at your MyWebApp.Enpoint module, usually located at lib/my_app_web/endpoint.ex file, in your Phoenix app. This modules itself is a plug (actually, a Builder), that combines multiple other plugs (like your router, which is a plug itself). You can add your own plug there, at any point to intercept your request. Add it before router, to have your plug run before routes are even evaluated, or after it so you have access to what was generated by controllers. All of plugs are run in the process created for the request, so you should be good to go.

About LiveView, after your request finished and sent a response, another process, separate from the request process, is spawned to handle LiveView (which is a GenServer itself, and acts exactly like one).

If you want to go further, I don’t think you can customize your request pipeline with anything other than plugs “cleanly”.

Have a look at cowboy which is the web server used by Phoenix. Also, I remember Phoenix (or cowboy? I don’t remember exactly) using poolboy for its acceptor pool, which might not be related to your problem, but is worth knowing.

A sub-app is more than a repo… it can have multiple repo too.
A sub app has a supervision tree, where You can start Repo. It contains Contexts defining Core logic. Eventually, it will access db at the some point.

And a Repo module use poolboy to manage database access.

There is no problem managing Processes in the sub-app if it has a supervision tree. And Repo is started from sub-app supervision tree.

Well, not really. Each LiveView get its own process, and Repo code is not managed here.

Repo is not managed inside the process, it is a process that manage a pool of workers with poolboy.

Remember we don’t have to use one process, we can use many, and each can talk to other processes.

You might try to run the observer to see all the started processes.

You might also see Phoenix hides this complexity, so You don’t have to know OTP, but under the hood, everything is a GenServer.

1 Like