Requêtes infinies vers `/presence/update`

Ok ce n’est peut-être pas un bug à proprement parler et c’est peut-être conçu ainsi, mais j’ai quelques interrogations sur la façon dont la présence est mise à jour côté JS. Le fichier app/assets/javascripts/discourse/app/services/presence.js

Ce que j’ai compris jusqu’à présent, c’est que le client JS :

  • Limite la mise à jour à 1 par seconde lorsque la file d’attente des mises à jour n’est pas vide
  • Ensuite, une requête toutes les 30 secondes lorsqu’il n’y a pas de changements

Ce que j’ai remarqué, c’est que si le serveur génère une erreur pour une raison quelconque, le JS continuera à faire des requêtes chaque seconde. Cela crée également des erreurs dans la console Uncaught (in promise) ligne 565.
Ne devrait-il pas s’arrêter à un moment donné après N erreurs pour ne pas surcharger le serveur ?

Comment reproduire ?

Simuler une erreur dans la méthode update de presence_controller.rb. (Dans mon cas, j’ai rendu le statut 405 juste pour la démo)

  def update

    # Le client JS est conçu pour limiter à une requête par seconde
    # Lorsqu'aucun changement n'est effectué, il fait une requête toutes les 30 secondes
    RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed!
    render json: { error: "Not authorized" }, status: 405
    return
    ...

Ensuite, côté client, allez sur n’importe quel écran avec une gestion de présence. J’ai choisi l’écran de réponse au sujet et j’ai commencé à répondre.

Le résultat est que cela continuera indéfiniment et chaque seconde.

1 « J'aime »

Est-il vraiment nécessaire de recalculer la présence toutes les 30 secondes ? Parce qu’il y a :

  • un calcul lorsqu’une action enter est effectuée
  • un calcul lorsqu’une action leave est effectuée
  • et un calcul lorsque la présence de l’utilisateur change

La demande de fonctionnalité consiste-t-elle à “ralentir” en cas d’erreur, de sorte que nous ralentissions jusqu’à environ 30 secondes en cas d’erreurs répétées…

1 « J'aime »

@AhmedLoud est-ce le problème que vous avez résolu via FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub, ou est-ce un problème distinct?

1 « J'aime »

@david désolé pour le malentendu.

Mon premier sujet n’était pas lié à ceci : FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub

Même sans la mise à jour programmée toutes les 30 secondes, s’il y a une erreur du backend, l’algorithme essaiera à nouveau chaque seconde car la file d’attente des événements ne sera pas vide :

Dans _scheduleNextUpdate

   } else if (this._queuedEvents.length > 0) {
      this._cancelTimer();
      cancel(this._debounceTimer);
      this._debounceTimer = debounce(
        this,
        this._throttledUpdateServer,
        this._presenceDebounceMs // Je crois que c'est 1 seconde
      );

En fait, dans la méthode _updateServer → les événements sont remis dans la file d’attente en cas d’erreur :

  } catch (e) {
      // Remettre les événements échoués dans la file d'attente pour la prochaine fois
      this._queuedEvents.unshift(...queue);

C’était juste quelque chose que j’avais remarqué par la suite… Quand tout se passe bien (c’est-à-dire sans erreurs), est-il vraiment nécessaire de continuer à mettre à jour le serveur toutes les 30 secondes ?

} else if (
      !this._nextUpdateTimer &&
      this._presentChannels.size > 0 &&
      !isTesting()
    ) {
      this._nextUpdateTimer = discourseLater(
        this,
        this._throttledUpdateServer,
        PRESENCE_INTERVAL_S * 1000 // C'est 30 secondes
      );
    }
1 « J'aime »

Je ne pense pas qu’il y ait de délai d’attente. Il continue de réessayer toutes les 1 seconde sans s’arrêter en cas d’erreur. Mais peut-être que j’ai manqué quelque chose.

1 « J'aime »

Ok, je pense avoir compris pourquoi il doit se « enregistrer » à nouveau toutes les 30 secondes.

Il y a un job d’arrière-plan qui est déclenché toutes les minutes PresenceChannelAutoLeave qui fera quitter le canal aux membres expirés.

C’est aussi commenté dans presence_channel.rb pour la méthode present(user:, client_id:) :

    # Marque le client d'un utilisateur comme présent dans ce canal. Le client_id doit être unique par
    # onglet de navigateur. Cette méthode doit être appelée de manière répétée (au moins une fois toutes les DEFAULT_TIMEOUT)
    # pendant que l'utilisateur est présent dans le canal.
    def present(user:, client_id:)

Je me demande toujours quelle est la partie de l’erreur qui est déclenchée chaque seconde.

Nous gérions déjà correctement les erreurs 429 « rate limited ». Mais pour d’autres erreurs inattendues, j’ai ajouté une logique de « backoff exponentiel » (limitée à 30 secondes). Merci d’avoir attiré notre attention sur ce point @AhmedLoud

3 « J'aime »

Ce sujet a été automatiquement fermé après 9 jours. De nouvelles réponses ne sont plus autorisées.