Skip to content

Cliente morre após stream:error <ack class="status" type="media"/> e nunca reconecta; connect vira no-op e forcereconnect dá 500 até reiniciar o processo (0.7.2) #185

Open

Description

@Clalber

Ambiente

  • Imagem: evoapicloud/evolution-go:latest (v0.7.2), Docker Swarm, 1 réplica
  • Store: Postgres (schema whatsmeow v14), DATABASE_SAVE_MESSAGES=false
  • ~15 instâncias ativas, webhook com subscribe MESSAGE,SEND_MESSAGE,CONNECTION,QRCODE
  • Instâncias criadas com ignoreGroups=true, ignoreStatus=true
  • Produção real (CRM de atendimento) — cada hora de queda é lead perdido

Resumo

Tivemos 3 ocorrências em 5 dias (27, 30 e 31/08/2026) do mesmo quadro, em instâncias diferentes: o cliente whatsmeow recebe um stream:error desconhecido com <ack class="status" type="media"/>, o socket cai (EOF) e o cliente nunca mais reconecta. A instância fica com disconnect_reason = "Reconnecting" para sempre, e as rotas de recuperação ficam todas inertes:

Rota Resposta durante o travamento
POST /instance/connect (token da instância) 200, mas no-op silencioso — o cliente morto não é recriado; nenhuma linha de log
POST /instance/forcereconnect/{id} (chave global) 500 {"error":"failed to connect"} em ~2,0s, sem nenhuma linha de log
POST /instance/disconnect 500 {"error":"no active session found"}
GET /instance/status {Connected:false, LoggedIn:false}

O único destrave é reiniciar o processo (docker service update --force). Depois do restart, um único POST /instance/connect sobe a MESMA sessão em ~5s, sem QR — ou seja, credencial e device store estavam íntegros o tempo todo. O defeito é estado interno do processo.

Na ocorrência de 30–31/08 a instância ficou 27 horas fora do ar; um watchdog externo disparou 693 POST /instance/connect (todos 200) sem nenhum efeito. Uma segunda instância travou junto no mesmo período, com o mesmo quadro.

Postgres não estava esgotado: 0 ocorrências de too many clients nos logs de 96h e 9/100 conexões logo após o restart — então este caso é diferente (mas pode se compor com) os leaks de pool já reportados em #106, #109, #112, #118, #165 e #175.

Logs da ocorrência de 27/08 (BRT)

11:11:03.029 [Client/Socket ERROR] Error reading from websocket: failed to get reader: failed to read frame header: EOF
11:22:38-41  [Client ERROR] Failed to sync app state after notification: ... mismatching LTHash   (x7)
11:25:24.778 [Client ERROR] Event handler panicked while handling a *events.Archive: interface conversion: interface {} is *events.Archive, not map[string]interface {}   (x2)
12:05:11.213 [Client ERROR] Unknown stream error: <stream:error><ack class="status" id="3EB0CEF3CE75AA87C361A8" type="media"/></stream:error>
12:05:11.395 [Client/Socket ERROR] Error reading from websocket: failed to get reader: failed to read frame header: EOF
--- (a partir daqui o cliente nunca mais volta; connect no-op, forcereconnect 500) ---

Na mesma tarde, outra instância caiu com a MESMA assinatura e outro id:

13:13:29.933 [Client ERROR] Unknown stream error: <stream:error><ack class="status" id="3EB049A3E64AAE7174E188" type="media"/></stream:error>
13:13:30.110 [Client/Socket ERROR] Error reading from websocket: ... EOF

Dois ids diferentes em quedas diferentes → não é uma stanza única "envenenada" da conta; qualquer recebimento/ack de status com mídia parece poder disparar. Nota: as instâncias são criadas com ignoreStatus=true — pode haver interação entre o descarte do status e o ack enviado.

O que precisa ser feito na próxima atualização (passo a passo)

  1. Auto-reconexão após stream:error desconhecido. Hoje o cliente entra em estado terminal mas continua registrado como "Reconnecting". Um stream:error desconhecido deveria ser tratado como desconexão recuperável: teardown completo do cliente + reconexão com backoff exponencial (a credencial continua válida — provado pelo relogin instantâneo pós-restart).

  2. /instance/connect e /instance/forcereconnect precisam destruir e recriar o cliente quando ele existe mas está morto. Hoje o connect cai no caminho "Instance already running, settings updated without restarting client" mesmo com o cliente morto, e o forcereconnect falha sem executar um teardown real. Sugestão: se Connected=false, sempre descartar o objeto atual e criar cliente novo do store.

  3. Logar o erro real do Connect() no forcereconnect. O 500 failed to connect em ~2s não emite NENHUMA linha de log — impossível diagnosticar em produção. O erro retornado pelo whatsmeow (ou o estado interno que impede a criação) precisa ir para o log.

  4. Investigar o ack de status/mídia rejeitado pelo servidor. O <ack class="status" type="media"/> dentro do stream:error indica que o servidor considerou inválido um ack nosso referente a mensagem de status com mídia. Revisar o caminho de recebimento de status@broadcast com mídia quando ignoreStatus=true (ack duplicado? tipo errado? ack de algo que não deveria ser acked?).

  5. Corrigir o panic do handler de *events.Archive (já reportado em panic: "interface conversion: interface {} is *events.Archive, not map[string]interface {}" can crash the process (v0.7.1) #95 e Bug: API /chat/archive - interface conversion: interface {} is *events.Archive, not map[string]interface {} (v0.7.2) #101) — no nosso ambiente ele aparece em rajadas nos mesmos períodos das quedas. O emissor de webhook faz type assertion para map[string]interface{} e recebe o struct tipado.

  6. Observabilidade por instância: expor o último erro de desconexão/tentativa (last_disconnect_error, timestamp da última tentativa de reconnect) no /instance/status ou /instance/info, para monitoramento externo detectar esse estado sem depender de log scraping.

Workaround que estamos usando

Timer systemd a cada 2 min que: (a) religa via POST /instance/connect qualquer instância connected=false com device válido no store; (b) se a mesma instância seguir caída por 3 ciclos (connect inerte = este bug), reinicia o serviço inteiro com cooldown de 30 min. Funciona, mas reiniciar o processo derruba momentaneamente as demais ~14 instâncias — por isso o fix no servidor é importante.

Obrigado pelo projeto! Posso fornecer mais logs, testar builds com o fix, ou detalhar o ambiente.

Activity

  1. LineckerN commented on Sep 8, 2026

    @LineckerN

    Reproduzi a mesma assinatura em 0.7.2 e abri #190 com o que encontrei.

    Causa: o go.mod fixa whatsmeow v0.0.0-20260630180629-b572e5bcb92b, de 30/06.
    Três correções entraram depois desse pin e batem com os sintomas:

    commit data assunto
    28bfe53 04/09 client: ensure stream error is handled before reconnecting
    0fadda7 28/08 client,socket: use correct context for noise socket frames
    4fa3462 21/08 client: don't reuse handler queue between connections

    Um detalhe que pode explicar o forcereconnect devolvendo 500 no seu caso: aqui
    o loop de reconexão vazava uma conexão PostgreSQL por ciclo. Depois de ~99
    ciclos o max_connections (100) esgotou com conexões ociosas de containers que
    já não existiam, e o cliente passou a não conseguir mais nem iniciar:

    Failed to create container: failed to upgrade database:
    failed to check if version table is up to date: pq: sorry, too many clients already
    

    Nesse ponto qualquer tentativa de reconectar falha, porque não há conexão
    disponível para abrir o store. Se o seu banco for compartilhado com outra coisa,
    vale checar select count(*), client_addr from pg_stat_activity group by 2 antes
    de reiniciar o processo — o restart mascara o sintoma, mas as conexões órfãs de
    containers antigos podem continuar lá.

    Depois de limpar as órfãs e subir o build do #190: 0 stream errors, 0 disconnects,
    e uma fila offline de 3301 mensagens acumulada em 1,5 dia drenou por completo em
    59 minutos. São ~1 h de produção, então é evidência forte, não prova.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions