When does a LiveView process end?

This was literally a question that popped into my head when I was trying to fall asleep and I couldn’t find an answer. So here I am.

Thinking of scenarios like if a user navigates away from a LiveView page, or closes their browser, or etc. What if the LiveView is halfway through processing some event that might take a second or two? Would it be always guaranteed to finish before the process shuts down, even if the browser is closed?

I know there’s a heartbeat in the socket, so does Phoenix automatically end the process when the heartbeat stops responding?

(The exact situation I’m thinking of is a user clicking a button that fires an event that recalculates a bunch of stuff, and then might send an email at the end. Obviously this wouldn’t all be done in the LiveView, but it’d be initiated by the LiveView and I wouldn’t want it killed halfway through.)

2 Likes

On a ‘normal’ interaction which stops LiveView (such as closing the browser tab or going to a different non-LiveView page), a hook activates in the the LiveView JavaScript library which sends a final phx_leave message. This is handled by the LiveView process here, conveying to OTP to shut down the process normally.

On ‘abnormal’ exits such as violently killing the browser program through the OS’ task manager, the computer losing power, the internet connection being lost, etc., this message will not be sent. Depending on the situation, this will either result in a socket which is closed, or a socket which is (for the time being) hanging, i.e. seemingly open but not responding to any requests. Recognizing which processes are hanging vs. which are ‘truly’ alive is what the heartbeat mechanism does. In either case (‘dead’ or ‘hanging’), at the ‘Phoenix Channel’-layer the socket will end up being closed, which the LiveView-process will see and handle here as another type of incoming message.

In either way, these messages will be handled by the LiveView GenServer only after the handle_*-action for the current message has finished executing. So if that action takes multiple seconds to execute, the process will be alive for those couple of extra seconds.

This seems like everything might be all right. However, there is one situation in which the LiveView process will be killed before finishing executing their current action: when the parent process, the OTP application, or more commonly your full Elixir program is stopped or is restarted. Depending on how important the action is, you might want to handle this situation properly. A common approach is to use Oban or other persistent-job-processing libraries, which will shorten your LiveView action and ensure that the calculation+e-mail sending happens atomically and still goes through if the app was restarted in the meantime.

Another advantage of using that technique is that you don’t lock a user out of interacting with the LiveView UI while the action is being executed, which might be a problem if it takes longer than a couple of seconds.

10 Likes

Oh that’s really interesting! So in most common circumstances, everything will finish running before the LiveView terminates - the current message being processed will finish be processed, and then the process will exit normally. That’s really good to know!

And yes that’s also a very good point about processing in the background. I’d been thinking along the lines of “in a handle_event callback, send a message to self that will perform the time-consuming task, set a loading flag to true so that the UI can update, and return” but of course the LiveView can only process one message at a time, can’t it? So any other events that get triggered from the UI will be queued behind the time-consuming task anyway.

I’ve got some thinking to do, but thank you so much for explaining it to me!

Correct! So the only way to have it happen properly in the background is by using another process, such as a Task. If you want to keep track in the UI whether the task has completed already or not, you could either poll a spawned task using Task.yield every so often (but that is not very nice), or instead as part of the task completing having it send back a message to the LiveView process (either directly or through for instance Phoenix.PubSub) as final step before completing.