fix(map): harden :preload_forecast against slow NFS + LiveStash crash
Two user-visible crashes in MapLive seen on prod pods today:
1. `:preload_forecast` handle_info timed out a Task.async_stream subtask
after 10 s and the `Enum.map(fn {:ok, h} -> h end)` match cascaded
the `:exit, :timeout` into the LiveView process, terminating the
session. Adds `on_timeout: :kill_task` and flat-maps only :ok
elements — a slow NFS forecast hour now drops from the preload
payload instead of killing the LV.
2. `LiveStash.stash/1` crashed with `** (ArgumentError) errors were
found at the given arguments: 2nd argument: not a valid match
specification` inside `:ets.select_replace/2` on OTP 28. The dep
(live_stash 0.2.0) has a latent bug against the tightened OTP 28
match-spec validator. Both call sites (MapLive.select_band,
SubmitLive.switch_tab) now route through a new
`MicrowavepropWeb.LiveStashGuard.stash/1` wrapper that rescues
ArgumentError, logs a warning, and returns the socket unchanged.
Stash is a convenience for reconnect state — failing it does not
need to kill the user's session.
Drop the guard module once the upstream match-spec fix lands.
This commit is contained in:
parent
c514626a62
commit
d88bdd70a8
3 changed files with 46 additions and 4 deletions
34
lib/microwaveprop_web/live/live_stash_guard.ex
Normal file
34
lib/microwaveprop_web/live/live_stash_guard.ex
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
defmodule MicrowavepropWeb.LiveStashGuard do
|
||||
@moduledoc """
|
||||
Tiny wrapper around `LiveStash.stash/1` that swallows the
|
||||
`ArgumentError: not a valid match specification` crash LiveStash 0.2.0
|
||||
raises under OTP 28.
|
||||
|
||||
The stash is a convenience for state persistence across reconnects;
|
||||
a failure is not fatal to the user's session. We log and return the
|
||||
socket unchanged instead of letting the LiveView process terminate
|
||||
mid-`handle_event`.
|
||||
|
||||
Remove this module and inline `LiveStash.stash/1` calls once the
|
||||
upstream match-spec fix lands in the `live_stash` hex package.
|
||||
"""
|
||||
|
||||
alias Phoenix.LiveView.Socket
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Best-effort wrapper around `LiveStash.stash/1`.
|
||||
|
||||
Returns the (possibly unchanged) socket no matter what the adapter
|
||||
raises.
|
||||
"""
|
||||
@spec stash(Socket.t()) :: Socket.t()
|
||||
def stash(socket) do
|
||||
LiveStash.stash(socket)
|
||||
rescue
|
||||
e in ArgumentError ->
|
||||
Logger.warning("LiveStashGuard: stash skipped (#{Exception.message(e)})")
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
|
@ -232,7 +232,7 @@ defmodule MicrowavepropWeb.MapLive do
|
|||
socket
|
||||
|> assign(selected_band: band, valid_times: valid_times, selected_time: selected_time)
|
||||
|> assign(:tracking_now?, tracking_now?(selected_time, valid_times, DateTime.utc_now()))
|
||||
|> LiveStash.stash()
|
||||
|> MicrowavepropWeb.LiveStashGuard.stash()
|
||||
|> push_event("update_scores", %{scores: Propagation.pack_scores(scores)})
|
||||
|> push_event("update_band_info", %{band_info: band_info(band)})
|
||||
|> push_timeline()
|
||||
|
|
@ -412,6 +412,10 @@ defmodule MicrowavepropWeb.MapLive do
|
|||
# since the JS hook expects `hours` in valid_time order.
|
||||
forecast_times = Enum.reject(socket.assigns.valid_times, &(selected && DateTime.compare(&1, selected) == :eq))
|
||||
|
||||
# `on_timeout: :kill_task` + explicit filter on :ok keeps a slow NFS
|
||||
# read from cascading the whole stream's :timeout exit up into the
|
||||
# LiveView process. A timed-out forecast hour just drops from the
|
||||
# preload payload; the client re-requests it on demand.
|
||||
hours =
|
||||
forecast_times
|
||||
|> Task.async_stream(
|
||||
|
|
@ -423,9 +427,13 @@ defmodule MicrowavepropWeb.MapLive do
|
|||
end,
|
||||
max_concurrency: 4,
|
||||
ordered: true,
|
||||
timeout: 10_000
|
||||
timeout: 10_000,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.map(fn {:ok, h} -> h end)
|
||||
|> Enum.flat_map(fn
|
||||
{:ok, h} -> [h]
|
||||
{:exit, _} -> []
|
||||
end)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ defmodule MicrowavepropWeb.SubmitLive do
|
|||
@impl true
|
||||
def handle_event("switch_tab", %{"tab" => tab}, socket) do
|
||||
socket = assign(socket, active_tab: String.to_existing_atom(tab))
|
||||
{:noreply, LiveStash.stash(socket)}
|
||||
{:noreply, MicrowavepropWeb.LiveStashGuard.stash(socket)}
|
||||
end
|
||||
|
||||
def handle_event("validate", %{"contact" => contact_params}, socket) do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue