You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Phoenix LiveView登出跳转非LiveView页面时重复触发mount问题

问题成因

这确实是竞态条件导致的,触发逻辑如下:

  1. 浏览器发起DELETE /users/log_out请求,服务端开始执行登出逻辑
  2. 服务端先删除了数据库里的用户session token,接着通过PubSub向当前用户的LiveSocket广播disconnect消息
  3. 这个PubSub消息是进程内/本地广播,几乎瞬时到达浏览器端的LiveSocket客户端
  4. LiveSocket默认断开后会自动重连,此时浏览器还没收到服务端返回的302跳转响应,旧的/dash PageLive页面还没被销毁,所以重连请求会携带旧的session信息尝试重新mount PageLive
  5. 此时数据库里的token已经被删除,UserLiveAuth的mount逻辑认证失败,触发重定向到登录页
  6. 等到浏览器收到302跳转响应的时候,页面已经被LiveView的客户端重定向到登录页了,就出现了你看到的错误提示。
    如果302响应的网络传输比PubSub消息处理更快,浏览器先跳转页面销毁了socket,就不会触发重连,所以这个问题是偶现的。

解决方案

推荐两个稳妥的修复方式,选其中一种即可:

方案1:调整登出逻辑的执行顺序,优先返回重定向

把销毁token、广播断开的逻辑放到异步任务中执行,让302响应先返回给浏览器,避免重连:
修改user_auth.ex的log_out_user/1方法:

def log_out_user(conn) do
  user_token = get_session(conn, :user_token)
  live_socket_id = get_session(conn, :live_socket_id)

  # 异步执行销毁操作,不阻塞重定向响应返回
  Task.start(fn ->
    user_token && Accounts.delete_session_token(user_token)
    if live_socket_id, do: MyappWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
  end)

  conn
  |> renew_session()
  |> delete_resp_cookie(@remember_me_cookie)
  |> redirect(to: "/")
end

方案2:禁用LiveSocket主动断开后的自动重连

修改前端LiveSocket配置(assets/js/app.js),收到服务端主动断开的消息时不重试,直接跳转首页:

let liveSocket = new LiveSocket("/live", Socket, {
  params: {_csrf_token: csrfToken},
  onDisconnect: (code, reason) => {
    // 服务端主动广播的disconnect消息,reason为"disconnected"
    if (reason === "disconnected") {
      // 直接跳转首页,不触发重连
      window.location.href = "/"
    }
  }
})

两种方案都可以彻底解决这个竞态问题,方案1改后端逻辑更简单,不需要动前端代码,更推荐使用。

内容的提问来源于stack exchange,提问作者vaer-k

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 20:06:02