Prioridade: Média, sem perda de dados, mas envia silenciosamente mensagens privadas (PMs) voltadas ao usuário a partir da conta errada, e qualquer configuração do site alterada após a inicialização pode estar incorreta dentro dos jobs em segundo plano.
Plataforma: Auto-hospedado, configuração padrão de dois contêineres discourse_docker (data + web_only). Núcleo 2026.7.0-latest (30d8364f0ab). Não é específico do navegador — é do lado do servidor.
Descrição
Resultado atual: Após o demônio Sidekiq re-forkar (por exemplo, após a reinicialização de memória RSS em Demon::Sidekiq.rss_memory_check), o novo processo Sidekiq serve as configurações do site a partir da captura no momento da inicialização do mestre unicorn, e não do banco de dados. Qualquer configuração alterada desde que o mestre foi iniciado está silenciosamente incorreta dentro dos jobs em segundo plano, até que (a) a configuração seja alterada novamente enquanto esse processo Sidekiq estiver ativo, ou (b) o contêiner seja reiniciado.
O sintoma visível no meu site: mensagens privadas do sistema automatizadas (post_hidden, flags_agreed_and_post_deleted, auto-unsuspend) foram enviadas a partir de um valor anterior de site_contact_username. O banco de dados continha o valor corrigido há cinco dias, e o log de ações da equipe não mostrava gravações nesse intervalo. Enquanto isso, os mesmos tipos de mensagem enviados a partir de uma solicitação web usavam o usuário correto, porque os trabalhadores web haviam processado a atualização do MessageBus e o Sidekiq não.
Resultado esperado: Um demônio recém-forkado deve ler as configurações atuais do site. Os valores de SiteSetting no Sidekiq devem corresponder ao banco de dados, independentemente de quantas vezes o Sidekiq tenha sido reiniciado desde que o mestre foi iniciado.
Passos reproduzíveis
- Inicialize uma instância auto-hospedada. Anote o
site_contact_usernameatual (chame-o deuserA). - Em Admin → Configurações, altere
site_contact_usernameparauserB. Todos os processos em execução capturam isso corretamente (MessageBus/site_settings→SiteSetting.refresh!). - Mate o processo Sidekiq dentro do contêiner para que o mestre unicorn o re-forque (
kill <sidekiq_pid>; ou simplesmente espere queDemon::Sidekiq.rss_memory_checko reinicie quando ultrapassar o limite de 1000 MB de RSS — em um site movimentado, isso acontece sozinho). - Dispare qualquer mensagem do sistema que seja entregue via
Jobs::SendSystemMessage— por exemplo, deixe que uma postagem seja ocultada por sinalizações da comunidade, o que enfileira:send_system_messagedePost#hide!. - Abra a PM resultante.
Observado: A PM é autorada por userA — o valor que estava vigente quando o mestre foi iniciado, e não userB.
Esperado: autorada por userB.
Análise
Discourse.after_fork chama SiteSetting.after_fork (lib/site_setting_extension.rb:736):
def after_fork
@process_id = nil
ensure_listen_for_changes
end
Ele nunca chama refresh!, então o filho herda o hash de configurações current do pai via copy-on-write e o mantém por toda a sua vida útil, a menos que uma alteração subsequente seja transmitida.
ensure_listen_for_changes (lib/site_setting_extension.rb:713) também é efetivamente um no-op no filho, porque @subscribed é herdado 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
Ele funciona apenas porque MessageBus.after_fork é executado primeiro (lib/discourse.rb:1064) e revive a thread do barramento contra o registro de callbacks herdado.
O demônio é um fork simples do mestre (lib/demon/base.rb:181), e o Sidekiq re-forka rotineiramente em produção: Demon::Sidekiq.rss_memory_check o reinicia acima de DEFAULT_MAX_ALLOWED_SIDEKIQ_RSS_MEGABYTES = 1000. Cada fork ressuscita um valor que tinha sido verdadeiro pela última vez há duas semanas.
Isso é de longa data, não uma regressão: after_fork tem essa forma desde 8fc2549 (2014) e está inalterado no main atual.
Por que é fácil passar despercebido: na inicialização inicial, o mestre e o Sidekiq compartilham a mesma captura (correta), e enquanto o Sidekiq permanece ativo, ele recebe transmissões de alterações. A divergência só aparece para configurações alteradas após a inicialização do mestre, em um processo Sidekiq forkado após essa alteração — ou seja, começa silenciosamente, algum tempo depois, sem erro em nenhum lugar.
Impacto além do usuário de contato
site_contact_username é apenas o caso visível, porque ele marca um nome de usuário em uma PM que os usuários leem. A mesma desatualização se aplica a qualquer configuração do site consultada dentro de um job — limites de taxa, configurações de e-mail/notificação, alternadores de recurso, configurações de plugin — sem erro e sem linha de log. Operadores concluem razoavelmente que “a configuração não gruda” e vão procurar algo que esteja sobrescrevendo o banco de dados.
Correção sugerida
Em SiteSetting.after_fork, descarte o estado herdado e leia novamente:
def after_fork
@process_id = nil
@subscribed = false
ensure_listen_for_changes
refresh!
end
@subscribed = false também torna a assinatura do filho explícita, em vez de depender do MessageBus.after_fork para reviver a herdada.
Solução alternativa para operadores
./launcher restart web_only (uma reinicialização completa do contêiner, para que o mestre relia a configuração na inicialização). Re-salvar a configuração no admin apenas corrige o processo Sidekiq atualmente ativo — o próximo re-fork o reverte silenciosamente.