Бесконечные запросы к `/presence/update`

Хорошо, возможно, это не совсем баг, а так и задумано, но у меня есть вопросы о том, как обновляется статус присутствия на стороне JS. Файл app/assets/javascripts/discourse/app/services/presence.js

Насколько я понял, JS-клиент:

  • Ограничивает частоту обновлений до одного раза в секунду, пока очередь обновлений не пуста
  • Затем делает один запрос каждые 30 секунд, если изменений нет

Однако я заметил, что если сервер по какой-то причине возвращает ошибку, JS продолжает отправлять запросы каждую секунду. Кроме того, в консоли появляются ошибки: Uncaught (in promise) на строке 565.
Разве не должно быть остановки после N ошибок, чтобы не перегружать сервер?

Как воспроизвести?

Сымитируйте ошибку в методе update контроллера presence_controller.rb. (В моём случае я просто вернул статус 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
  • и вычисление при изменении userPresence

Предложение здесь заключается в том, чтобы «отступить» при ошибке, чтобы в случае повторных ошибок мы отступали полностью до 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 лайк

Я не думаю, что есть механизм задержки (back off). При возникновении любой ошибки он продолжает повторять попытку каждую секунду без остановки. Но, возможно, я что-то упустил.

1 лайк

Хм, я думаю, понял, почему нужно снова «регистрироваться» каждые 30 секунд.

Есть фоновая задача, которая запускается каждую минуту — PresenceChannelAutoLeave, — и она заставляет истёкших участников покинуть канал.

Об этом также есть комментарий в файле presence_channel.rb для метода present(user:, client_id:):

    # Пометить клиента пользователя как присутствующего в этом канале. client_id должен быть уникальным для каждой
    # вкладки браузера. Этот метод должен вызываться повторно (хотя бы один раз каждые DEFAULT_TIMEOUT)
    # пока пользователь находится в канале.
    def present(user:, client_id:)

Всё ещё неясно, что касается части с ошибкой, которая срабатывает каждую секунду.

Мы уже корректно обрабатывали ошибки 429 «rate limited». Однако для других, неожиданных ошибок я добавил логику экспоненциальной задержки (с ограничением до 30 секунд). Спасибо, что обратили на это наше внимание @AhmedLoud

3 лайка

Эта тема была автоматически закрыта через 9 дней. Новые ответы больше не принимаются.