Requisições infinitas para `/presence/update`

Ok, pode não ser um bug em si e talvez tenha sido projetado assim, mas tenho algumas dúvidas sobre como a presença está sendo atualizada no lado do JS. O arquivo app/assets/javascripts/discourse/app/services/presence.js

O que entendi até agora é que o cliente JS:

  • Limitará a atualização para 1 por segundo quando a fila de atualizações não estiver vazia
  • Em seguida, uma solicitação a cada 30 segundos quando não houver alterações

O que notei é que, se o servidor der erro por qualquer motivo, o JS continuará fazendo solicitações a cada segundo. Além disso, cria erros no console Uncaught (in promise) na linha 565.
Não deveria parar em algum ponto após N erros para não sobrecarregar o servidor?

Como reproduzir?

Simule um erro no método update do presence_controller.rb. (No meu caso, retornei o status 405 apenas para fins de demonstração)

  def update

    # O cliente JS é projetado para limitar a uma solicitação por segundo
    # Quando nenhuma alteração está sendo feita, ele faz uma solicitação a cada 30 segundos
    RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed!
    render json: { error: "Not authorized" }, status: 405
    return
    ...

Em seguida, no cliente, vá para qualquer tela com gerenciamento de presença. Eu escolhi a tela de resposta do tópico e comecei a responder.

O resultado é que ele continuará para sempre e a cada segundo.

1 curtida

Além disso, é realmente necessário recalcular a presença a cada 30 segundos? Porque há:

  • um cálculo quando uma ação de enter é realizada
  • um cálculo quando uma ação de leave é realizada
  • e um cálculo quando a userPresence muda

O pedido de funcionalidade é para “recuar” em caso de erro, de modo que recuaríamos até cerca de 30 segundos em caso de erros repetidos…

1 curtida

@AhmedLoud este é o problema que você corrigiu via FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub, ou é um problema separado?

1 curtida

@david desculpe pelo mal-entendido.

Meu primeiro post no tópico não estava relacionado a isto: FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub

Mesmo sem a atualização agendada a cada 30 segundos, se houver algum erro do backend, o algoritmo tentará novamente a cada segundo porque a fila de eventos não estará vazia:

Em _scheduleNextUpdate

   } else if (this._queuedEvents.length > 0) {
      this._cancelTimer();
      cancel(this._debounceTimer);
      this._debounceTimer = debounce(
        this,
        this._throttledUpdateServer,
        this._presenceDebounceMs // Acredito que isto seja 1 segundo
      );

Na verdade, no método _updateServer → os eventos são recolocados na fila em caso de erro:

  } catch (e) {
      // Recoloca os eventos falhados na fila para a próxima vez
      this._queuedEvents.unshift(...queue);

Isto foi apenas algo que notei depois… Quando tudo corre bem (ou seja, sem erros), é realmente necessário continuar a atualizar o servidor a cada 30 segundos?

} else if (
      !this._nextUpdateTimer &&
      this._presentChannels.size > 0 &&
      !isTesting()
    ) {
      this._nextUpdateTimer = discourseLater(
        this,
        this._throttledUpdateServer,
        PRESENCE_INTERVAL_S * 1000 // Isto são 30 segundos
      );
    }
1 curtida

Não acredito que haja um recuo. Ele continua tentando a cada 1 segundo sem parar quando há algum erro. Mas talvez eu tenha perdido alguma coisa.

1 curtida

Ok, acho que entendi por que ele precisa fazer o “check-in” novamente a cada 30 segundos.

Existe um job em segundo plano que é acionado a cada minuto PresenceChannelAutoLeave que fará com que membros expirados saiam do canal.

Também está comentado em presence_channel.rb para o método present(user:, client_id:):

    # Marca o cliente de um usuário como presente neste canal. O client_id deve ser único por
    # aba do navegador. Este método deve ser chamado repetidamente (pelo menos uma vez a cada DEFAULT_TIMEOUT)
    # enquanto o usuário estiver presente no canal.
    def present(user:, client_id:)

Ainda estou pensando sobre a parte do erro que é acionada a cada segundo.

Já estávamos lidando corretamente com erros 429 ‘rate limited’. Mas para outros erros inesperados, adicionei uma lógica de backoff exponencial (limitada a 30s). Obrigado por trazer isso à nossa atenção @AhmedLoud

3 curtidas

Este tópico foi fechado automaticamente após 9 dias. Novas respostas não são mais permitidas.