无限的请求到 /presence/update

好的,这可能不算是严格意义上的“bug”,也许是设计如此,但我对 JS 端如何更新在线状态有些疑问。文件是 app/assets/javascripts/discourse/app/services/presence.js

到目前为止,我的理解是 JS 客户端:

  • 当更新队列不为空时,会将更新频率限制为每秒一次
  • 然后在没有更改时每 30 秒发送一次请求

我注意到的是,如果服务器因任何原因出错,JS 将继续每秒发送请求。同时会在控制台产生错误 Uncaught (in promise),第 565 行。
它不应该在 N 次错误后停止,以免使服务器过载吗?

如何复现?

presence_controller.rbupdate 方法中模拟一个错误。(在我的例子中,我只是为了演示而渲染了状态 405)

  def update

    # JS 客户端设计为每秒节流一次
    # 当没有进行更改时,它每 30 秒发送一次请求
    RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed!
    render json: { error: "Not authorized" }, status: 405
    return
    ...

然后在客户端进入任何带有在线状态管理的屏幕。我选择了主题回复屏幕并开始回复。

结果是它会永远每秒钟持续发送请求。

1 个赞

另外,每 30 秒重新计算一次用户在线状态真的有必要吗?因为有:

  • 执行 enter 操作时进行一次计算
  • 执行 leave 操作时进行一次计算
  • 用户在线状态更改时进行一次计算

这里的需求是出错时“退避”,这样的话,在重复出错时,我们会退避到大约 30 秒……

1 个赞

@AhmedLoud 这是您通过 FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub 修复的问题,还是一个单独的问题?

1 个赞

@david 抱歉造成误解。

我最初的主题帖子与此无关:FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub

即使没有每 30 秒的计划更新,如果后端出现任何错误,算法也会每秒重试,因为事件队列将不为空:

_scheduleNextUpdate

   } else if (this._queuedEvents.length > 0) {
      this._cancelTimer();
      cancel(this._debounceTimer);
      this._debounceTimer = debounce(
        this,
        this._throttledUpdateServer,
        this._presenceDebounceMs // 我认为这是 1 秒
      );

事实上,在 _updateServer 方法中 → 事件会在出错时放回队列:

  } catch (e) {
      // 将失败的事件放回队列以供下次使用
      this._queuedEvents.unshift(...queue);

这只是我后来注意到的……当一切顺利(意味着没有错误)时,真的有必要每 30 秒更新一次服务器吗?

} else if (
      !this._nextUpdateTimer &&
      this._presentChannels.size > 0 &&
      !isTesting()
    ) {
      this._nextUpdateTimer = discourseLater(
        this,
        this._throttledUpdateServer,
        PRESENCE_INTERVAL_S * 1000 // 这是 30 秒
      );
    }
1 个赞

我不认为有退避机制。当出现任何错误时,它会每秒重试一次,而且不会停止。但也许我错过了什么。

1 个赞

好的,我想我明白了为什么它需要每 30 秒“签到”一次。

有一个后台作业每分钟触发一次 PresenceChannelAutoLeave,它将使过期的成员离开频道。

它也在 presence_channel.rb 中为 present(user:, client_id:) 方法进行了注释:

    # Mark a user's client as present in this channel. The client_id should be unique per
    # browser tab. This method should be called repeatedly (at least once every DEFAULT_TIMEOUT)
    # while the user is present in the channel.
    def present(user:, client_id:)

仍然想知道每秒触发的错误部分。

我们已经正确处理了 429 “速率限制”错误。但对于其他意外错误,我添加了一些指数退避逻辑(上限为 30 秒)。感谢您引起我们的注意 @AhmedLoud

3 个赞

此主题已在 9 天后自动关闭。不允许回复。