Site-Einstellungen sind in Sidekiq nach dem Neustart veraltet

Priorität: Mittel, kein Datenverlust, aber es werden stillschweigend nutzerorientierte PMs vom falschen
Konto gesendet, und jede Site-Einstellung, die nach dem Start geändert wird, kann in Hintergrundjobs falsch sein.

Plattform: Selbst gehostet, Standard-discourse_docker-Zwei-Container-Setup (data +
web_only). Core 2026.7.0-latest (30d8364f0ab). Nicht browserabhängig — serverseitig.


Beschreibung

Tatsächliches Ergebnis: Nachdem der Sidekiq-Dämon neu geforkt wurde (z. B. nach dem RSS-Speicher-Neustart in
Demon::Sidekiq.rss_memory_check), dient der neue Sidekiq-Prozess Site-Einstellungen aus dem
Startzeitpunkt-Snapshot des Unicorn-Masters, nicht aus der Datenbank. Jede Einstellung, die seit dem
Start des Masters geändert wurde, ist stillschweigend falsch in Hintergrundjobs, bis entweder (a) die Einstellung
wieder geändert wird, während dieser Sidekiq-Prozess lebt, oder (b) der Container neu gestartet wird.

Das sichtbare Symptom auf meiner Site: Automatisierte System-PMs (post_hidden, flags_agreed_and_post_deleted, Auto-Entsuspension) wurden von einem früheren Wert von
site_contact_username gesendet. Die Datenbank hatte den korrigierten Wert seit fünf Tagen gespeichert, und das
Mitarbeiter-Aktionsprotokoll zeigte keine Schreibvorgänge in diesem Zeitraum. Währenddessen wurden die gleichen Nachrichtentypen, die von einer Webanforderung gesendet wurden, vom korrekten Benutzer verwendet, da Web-Worker die MessageBus-Aktualisierung verarbeitet hatten und Sidekiq nicht.

Erwartetes Ergebnis: Ein frisch geforkter Dämon sollte aktuelle Site-Einstellungen lesen. SiteSetting
Werte in Sidekiq sollten mit der Datenbank übereinstimmen, unabhängig davon, wie oft Sidekiq seit dem Start des Masters neu gestartet wurde.


Reproduzierbare Schritte

  1. Starten Sie eine selbst gehostete Instanz. Notieren Sie den aktuellen site_contact_username (nennen wir ihn userA).
  2. Ändern Sie in Admin → Einstellungen site_contact_username in userB. Alle laufenden Prozesse
    nehmen dies korrekt auf (MessageBus /site_settingsSiteSetting.refresh!).
  3. Beenden Sie den Sidekiq-Prozess im Container, damit der Unicorn-Master ihn neu forkt
    (kill <sidekiq_pid>; oder warten Sie einfach, bis Demon::Sidekiq.rss_memory_check ihn neu startet, sobald er die 1000 MB RSS-Schwelle überschreitet — auf einer beschäftigten Site geschieht dies von selbst).
  4. Lösen Sie jede Systemnachricht aus, die über Jobs::SendSystemMessage geliefert wird — z. B. lassen Sie einen Beitrag durch Community-Flags verstecken, was :send_system_message aus Post#hide! in die Warteschlange stellt.
  5. Öffnen Sie die resultierende PM.

Beobachtet: Die PM wird von userA verfasst — dem Wert, der zum Zeitpunkt des Master-Starts aktuell war, nicht userB.
Erwartet: verfasst von userB.


Analyse

Discourse.after_fork ruft SiteSetting.after_fork auf (lib/site_setting_extension.rb:736):

def after_fork
  @process_id = nil
  ensure_listen_for_changes
end

Es ruft niemals refresh! auf, sodass das Kind das current-Einstellungs-Hash des Elternteils über
copy-on-write erbt und es für seine gesamte Lebensdauer behält, es sei denn, eine nachfolgende Änderung wird verbreitet.

ensure_listen_for_changes (lib/site_setting_extension.rb:713) ist im Kind auch effektiv ein No-op, da @subscribed als true geerbt wird:

def ensure_listen_for_changes
  return if @listen_for_changes == false

  unless @subscribed
    MessageBus.subscribe(SITE_SETTINGS_CHANNEL) { |message| ... }
    @subscribed = true
  end
end

Es funktioniert überhaupt nur, weil MessageBus.after_fork zuerst läuft (lib/discourse.rb:1064) und den Bus-Thread gegen das geerbte Callback-Register wiederbelebt.

Der Dämon ist ein einfacher fork vom Master (lib/demon/base.rb:181), und Sidekiq forkt routinemäßig in der Produktion neu: Demon::Sidekiq.rss_memory_check startet ihn neu, wenn er über
DEFAULT_MAX_ALLOWED_SIDEKIQ_RSS_MEGABYTES = 1000 liegt. Jeder Fork belebt einen Wert, der vor zwei Wochen zuletzt wahr war.

Dies ist langjährig und keine Regression: after_fork hat diese Form seit
8fc2549 (2014) und ist auf der aktuellen main unverändert.

Warum es leicht zu übersehen ist: Beim ersten Start teilen Master und Sidekiq denselben (korrekten)
Snapshot, und während Sidekiq lebt, empfängt es Änderungsbenachrichtigungen. Die Divergenz erscheint nur für Einstellungen, die nach dem Master-Start geändert wurden, in einem Sidekiq-Prozess, der nach dieser
Änderung geforkt wurde — d. h. sie beginnt stillschweigend, einige Zeit später, ohne Fehler irgendwo.


Auswirkungen über den Kontaktbenutzer hinaus

site_contact_username ist nur der sichtbare Fall, da er einen Benutzernamen auf eine PM stempelt, die Benutzer lesen. Die gleiche Veraltung gilt für jede Site-Einstellung, die innerhalb eines Jobs konsultiert wird — Rate-Limits, E-Mail-/Benachrichtigungseinstellungen, Feature-Toggles, Plugin-Einstellungen — ohne Fehler und ohne
Protokollzeile. Betreiber schließen zu Recht, “die Einstellung hält nicht” und suchen nach etwas, das die Datenbank überschreibt.

Vorgeschlagene Korrektur

In SiteSetting.after_fork den geerbten Zustand fallen lassen und neu lesen:

def after_fork
  @process_id = nil
  @subscribed = false
  ensure_listen_for_changes
  refresh!
end

@subscribed = false macht auch die eigene Abonnement des Kindes explizit, anstatt sich auf MessageBus.after_fork zu verlassen, das das geerbte wiederbelebt.

Workaround für Betreiber

./launcher restart web_only (ein vollständiger Container-Neustart, sodass der Master die Einstellung beim Start neu liest). Das erneute Speichern der Einstellung im Admin repariert nur den derzeit lebenden Sidekiq-Prozess — der
nächste Re-Fork kehrt ihn stillschweigend zurück.

1 „Gefällt mir“

Vielen Dank für den Bericht! Ich habe einen Fix dafür in

eingereicht.

1 „Gefällt mir“