Prioridad: Media, sin pérdida de datos, pero envía silenciosamente mensajes privados (PM) visibles para el usuario desde la cuenta incorrecta, y cualquier configuración del sitio cambiada después del inicio puede ser incorrecta dentro de los trabajos en segundo plano.
Plataforma: Autoalojada, configuración estándar de discourse_docker con dos contenedores (data + web_only). Núcleo 2026.7.0-latest (30d8364f0ab). No específico del navegador — del lado del servidor.
Descripción
Resultado real: Después de que el demonio Sidekiq se bifurque nuevamente (por ejemplo, después del reinicio de memoria RSS en Demon::Sidekiq.rss_memory_check), el nuevo proceso de Sidekiq sirve las configuraciones del sitio desde la captura en el momento del inicio del maestro unicorn, y no desde la base de datos. Cualquier configuración cambiada desde que el maestro inició es silenciosamente incorrecta dentro de los trabajos en segundo plano, hasta que (a) la configuración se cambia nuevamente mientras ese proceso de Sidekiq está activo, o (b) se reinicia el contenedor.
El síntoma visible en mi sitio: los mensajes privados del sistema automatizados (post_hidden, flags_agreed_and_post_deleted, auto-unsuspend) se enviaron desde un valor anterior de site_contact_username. La base de datos había almacenado el valor corregido durante cinco días, y el registro de acciones del personal no mostraba escrituras en ese período. Mientras tanto, los mismos tipos de mensajes enviados desde una solicitud web usaban el usuario correcto, porque los trabajadores web habían procesado la actualización de MessageBus y Sidekiq no.
Resultado esperado: Un demonio recién bifurcado debería leer las configuraciones del sitio actuales. Los valores de SiteSetting en Sidekiq deberían coincidir con la base de datos, independientemente de cuántas veces Sidekiq se haya reiniciado desde que el maestro inició.
Pasos reproducibles
- Inicia una instancia autoalojada. Anota el
site_contact_usernameactual (llámalouserA). - En Admin → Configuraciones, cambia
site_contact_usernameauserB. Todos los procesos en ejecución recogen esto correctamente (MessageBus/site_settings→SiteSetting.refresh!). - Mata el proceso Sidekiq dentro del contenedor para que el maestro unicorn lo bifurque nuevamente (
kill <sidekiq_pid>; o simplemente espera a queDemon::Sidekiq.rss_memory_checklo reinicie una vez que supere el umbral de 1000 MB de RSS — en un sitio ocupado esto ocurre por sí solo). - Dispara cualquier mensaje del sistema que se entregue mediante
Jobs::SendSystemMessage— por ejemplo, deja que una publicación sea ocultada por banderas comunitarias, lo que encola:send_system_messagedesdePost#hide!. - Abre el PM resultante.
Observado: El PM está autoría por userA — el valor que era actual cuando el maestro inició, no userB.
Esperado: autoría por userB.
Análisis
Discourse.after_fork llama a SiteSetting.after_fork (lib/site_setting_extension.rb:736):
def after_fork
@process_id = nil
ensure_listen_for_changes
end
Nunca llama a refresh!, por lo que el hijo hereda el hash de configuraciones current del padre mediante copia al escribir y lo mantiene durante toda su vida útil a menos que un cambio subsiguiente sea transmitido.
ensure_listen_for_changes (lib/site_setting_extension.rb:713) también es efectivamente un no-op en el hijo, porque @subscribed se hereda como true:
def ensure_listen_for_changes
return if @listen_for_changes == false
unless @subscribed
MessageBus.subscribe(SITE_SETTINGS_CHANNEL) { |message| ... }
@subscribed = true
end
end
Funciona en absoluto solo porque MessageBus.after_fork se ejecuta primero (lib/discourse.rb:1064) y revive el hilo del bus contra el registro de callbacks heredado.
El demonio es una fork simple del maestro (lib/demon/base.rb:181), y Sidekiq se bifurca rutinariamente en producción: Demon::Sidekiq.rss_memory_check lo reinicia por encima de DEFAULT_MAX_ALLOWED_SIDEKIQ_RSS_MEGABYTES = 1000. Cada bifurcación resucita un valor que había sido verdadero por última vez hace dos semanas.
Esto es de larga data y no una regresión: after_fork ha tenido esta forma desde 8fc2549 (2014), y no ha cambiado en main actual.
Por qué es fácil de pasar por alto: en el primer inicio, el maestro y Sidekiq comparten la misma captura (correcta), y mientras Sidekiq permanece activo, recibe transmisiones de cambios. La divergencia solo aparece para configuraciones cambiadas después del inicio del maestro, en un proceso Sidekiq bifurcado después de ese cambio — es decir, comienza silenciosamente, algún tiempo después, sin error en ninguna parte.
Impacto más allá del usuario de contacto
site_contact_username es solo el caso visible, porque marca un nombre de usuario en un PM que los usuarios leen. La misma antigüedad aplica a cualquier configuración del sitio consultada dentro de un trabajo — límites de tasa, configuraciones de correo electrónico/notificación, interruptores de función, configuraciones de plugins — sin error y sin línea de registro. Los operadores concluyen razonablemente “la configuración no se queda” y buscan algo que esté sobrescribiendo la base de datos.
Corrección sugerida
En SiteSetting.after_fork, elimina el estado heredado y vuelve a leer:
def after_fork
@process_id = nil
@subscribed = false
ensure_listen_for_changes
refresh!
end
@subscribed = false también hace que la suscripción propia del hijo sea explícita en lugar de depender de MessageBus.after_fork para revivir la heredada.
Solución alternativa para operadores
./launcher restart web_only (un reinicio completo del contenedor, para que el maestro vuelva a leer la configuración en el inicio). Guardar nuevamente la configuración en admin solo corrige el proceso Sidekiq actualmente activo — la siguiente bifurcación silenciosamente la revierte.