# 无限的请求到 /presence/update

**URL:** https://meta.discourse.org/t/infinite-requests-to-presence-update/268063
**Category:** Bug
**Created:** [2023年六月12日 15:48 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063 "2023-06-12T15:48:22Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![AhmedLoud](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ahmedloud/32/576547_2.png) [@AhmedLoud](https://meta.discourse.org/u/AhmedLoud)
#### Post date: [2023年六月12日 15:48 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/1 "2023-06-12T15:48:22Z")

</div>

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

到目前为止，我的理解是 JS 客户端：

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

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

### 如何复现？

在 `presence_controller.rb` 的 `update` 方法中模拟一个错误。（在我的例子中，我只是为了演示而渲染了状态 405）

```ruby
  def update

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

```

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

 ![image](https://global.discourse-cdn.com/meta/original/4X/7/9/e/79ebb115810417059fe0441775d350f8aa0a6847.png)

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

---

<div class="post-metadata">

### Author: ![AhmedLoud](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ahmedloud/32/576547_2.png) [@AhmedLoud](https://meta.discourse.org/u/AhmedLoud)
#### Post date: [2023年六月12日 22:30 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/2 "2023-06-12T22:30:22Z")

</div>

> [@AhmedLoud](#):
>
> 然后每 30 秒请求一次，当没有更改时

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

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

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [2023年六月13日 06:24 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/4 "2023-06-13T06:24:06Z")

</div>

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

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2023年六月13日 08:42 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/5 "2023-06-13T08:42:16Z")

</div>

@AhmedLoud 这是您通过 [FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub](https://github.com/discourse/discourse/commit/589add7bb5d01912281c2c38f91325f34567b6bd) 修复的问题，还是一个单独的问题？

---

<div class="post-metadata">

### Author: ![AhmedLoud](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ahmedloud/32/576547_2.png) [@AhmedLoud](https://meta.discourse.org/u/AhmedLoud)
#### Post date: [2023年六月13日 10:44 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/7 "2023-06-13T10:44:23Z")

</div>

@david 抱歉造成误解。

我最初的主题帖子与此无关：[FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub](https://github.com/discourse/discourse/commit/589add7bb5d01912281c2c38f91325f34567b6bd)

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

在 `_scheduleNextUpdate` 中

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

```

事实上，在 `_updateServer` 方法中 → 事件会在出错时放回队列：

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

```

> [@AhmedLoud](#):
>
> 另外，每 30 秒重新计算一次在线状态是否真的有必要？

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

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

```

---

<div class="post-metadata">

### Author: ![AhmedLoud](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ahmedloud/32/576547_2.png) [@AhmedLoud](https://meta.discourse.org/u/AhmedLoud)
#### Post date: [2023年六月13日 10:46 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/8 "2023-06-13T10:46:37Z")

</div>

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

---

<div class="post-metadata">

### Author: ![AhmedLoud](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ahmedloud/32/576547_2.png) [@AhmedLoud](https://meta.discourse.org/u/AhmedLoud)
#### Post date: [2023年六月13日 13:56 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/9 "2023-06-13T13:56:55Z")

</div>

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

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

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

```ruby
    # 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:)

```

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

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2023年七月28日 21:36 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/10 "2023-07-28T21:36:41Z")

</div>

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

> <https://github.com/discourse/discourse/pull/22864>
>
> We already handled 429 rate limit errors correctly. This commit adds backoff log…ic to other types of error to avoid requests being retried every second.
> 
> https://meta.discourse.org/t/268063

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2023年八月7日 07:00 UTC](https://meta.discourse.org/t/infinite-requests-to-presence-update/268063/11 "2023-08-07T07:00:35Z")

</div>

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