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.
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
);
}
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