Erro 104 no NGINX com .NET - Visão 2 | (104: Connection reset by peer) while reading response header from upstream | Ambiente .NET
- Erro 104 no NGINX com .NET - Visão 2 | (104: Connection reset by peer) while reading response header from upstream | Ambiente .NET
Erro 104 (ECONNRESET) em Nginx com SignalR, Redis e MongoDB
Visão geral
O erro 104 (ECONNRESET – Connection reset by peer) não é um erro do SignalR, do MongoDB ou do Redis. Ele é um erro de baixo nível do sistema operacional (TCP/IP) que aparece quando uma conexão é encerrada abruptamente enquanto ainda está sendo utilizada.
No contexto de aplicações .NET + SignalR atrás do Nginx, esse erro normalmente surge quando operações demoradas ocorrem durante uma requisição ativa, como no OnConnectedAsync ou em métodos do Hub.
Onde o erro 104 é definido
O código 104 corresponde a:
ECONNRESET
Esse valor é definido no kernel Linux (POSIX / errno.h) e significa:
A conexão TCP foi resetada pelo outro lado.
O Nginx apenas registra esse erro, ele não o cria.
Papel de cada camada
Cliente
↓
Nginx (proxy reverso)
↓
Kestrel (.NET)
↓
SignalR Hub
↓
Redis
↓
MongoDB
- SignalR: executa o método do Hub
- Redis: cache rápido
- MongoDB: fallback com latência variável
- Nginx: aguarda resposta do backend
- Kernel TCP/IP: decide o estado final da conexão
Fluxo causal completo do erro 104
Cliente chama método do SignalR
↓
Nginx encaminha requisição e inicia contagem de timeout
↓
Método do Hub executa
↓
Consulta Redis (cache miss)
↓
Fallback para MongoDB (latência imprevisível)
↓
Método do Hub fica bloqueado
↓
Timeout do Nginx é atingido
↓
Nginx encerra o socket TCP
↓
TCP envia pacote RST
↓
Kernel marca conexão como resetada
↓
Tentativa de leitura/escrita no socket
↓
Kernel retorna errno = ECONNRESET (104)
↓
Nginx loga: recv() failed (104: Connection reset by peer)
Relação entre timeout e erro 104
Timeout não lança erro 104 diretamente.
Encadeamento real:
Timeout lógico (Nginx)
↓
Decisão de encerrar conexão
↓
TCP envia RST
↓
Aplicação tenta usar o socket
↓
Kernel retorna ECONNRESET (104)
FIN vs RST (conceito essencial)
FIN (fechamento gracioso)
- Encerramento ordenado
- Dados pendentes podem ser transmitidos
- Não gera erro 104
FIN → ACK → FIN → ACK
RST (reset abrupto)
- Encerramento imediato
- Buffers descartados
- Próxima leitura/escrita gera ECONNRESET (104)
RST → conexão inválida imediatamente
Timeouts quase sempre terminam em RST.
A chamada recv() e o nascimento do erro 104
No nível mais baixo, o NGINX e o Kestrel utilizam system calls do Linux para ler dados do socket TCP.
A chamada central é:
ssize_t recv(int socket_fd, void *buffer, size_t size, int flags);
O comportamento do erro 104 acontece exatamente aqui.
Exemplo conceitual em C (nível kernel/user space)
ssize_t n = recv(socket_fd, buffer, sizeof(buffer), 0);
if (n == -1) {
if (errno == ECONNRESET) {
// errno = 104
printf("Connection reset by peer\n");
}
}
Nesse ponto:
- O socket já recebeu um RST
- O kernel marca a conexão como inválida
- Qualquer
recv()ousend()retorna-1 errnoé preenchido com ECONNRESET (104)
Como o Nginx traduz isso para log
Fluxo interno simplificado do Nginx:
n = recv(fd, buf, size, 0);
if (n == -1) {
ngx_log_error(
NGX_LOG_ERR,
log,
errno,
"recv() failed"
);
}
Se o kernel retornou:
errno = 104
O log final será:
recv() failed (104: Connection reset by peer)
O Nginx não interpreta a causa, apenas registra o erro retornado pelo kernel.
Exemplo comparativo: FIN vs RST no recv()
Caso FIN (fechamento normal)
n = recv(socket_fd, buffer, size, 0);
// n == 0
// significa EOF (conexão fechada normalmente)
- Nenhum erro
- Nenhum errno
- Conexão encerrada de forma limpa
Caso RST (erro 104)
n = recv(socket_fd, buffer, size, 0);
// n == -1
// errno == ECONNRESET (104)
- Encerramento abrupto
- Socket inválido imediatamente
Por que Redis + Mongo agrava o problema
Redis é rápido e previsível. MongoDB é variável.
Quando o Redis não contém o dado:
Redis (ms)
Mongo (10ms → vários segundos)
Isso cria um caminho de latência não determinístico dentro de um método de Hub, que mantém a conexão aberta e bloqueada.
SignalR e o problema de acoplamento
Métodos de Hub são executados dentro de uma conexão persistente.
Quando o método depende de:
- fallback de cache
- consultas de banco
- operações externas
- latência variável
o tempo de resposta da conexão passa a depender do pior cenário.
OnConnectedAsync e sua posição correta
O método base.OnConnectedAsync() deve ser chamado no final do método sobrescrito.
public override async Task OnConnectedAsync()
{
var connectionId = Context.ConnectionId;
await base.OnConnectedAsync();
}
O que não deve existir no OnConnectedAsync
- Consulta ao Redis
- Fallback para MongoDB
- Lógica de negócio
- Operações bloqueantes
- Chamadas HTTP
- Qualquer I/O lento
Exemplo problemático
public override async Task OnConnectedAsync()
{
var data = await redis.GetAsync(key);
if (data == null)
data = await mongo.FindAsync(...);
await base.OnConnectedAsync();
}
Exemplo arquitetural recomendado
public override async Task OnConnectedAsync()
{
var connectionId = Context.ConnectionId;
await base.OnConnectedAsync();
_ = Task.Run(() => InicializacaoPesadaAsync(connectionId));
}
Ou:
public async Task InitAsync()
{
var data = await redis.GetAsync(key);
if (data == null)
data = await mongo.FindAsync(...);
await Clients.Caller.SendAsync("InitCompleted", data);
}
Diferença entre 104, 502, 504 e 499
| Código | Origem | Significado |
|---|---|---|
| 104 | Kernel TCP | Conexão resetada (RST) |
| 502 | Nginx | Backend fechou inválido |
| 504 | Nginx | Timeout aguardando resposta |
| 499 | Nginx | Cliente fechou a conexão |
Onde o erro 104 não é definido
- Não é definido no SignalR
- Não é definido no ASP.NET
- Não é definido no MongoDB
- Não é definido no Redis
- Não é definido na configuração do Nginx
Ele é definido exclusivamente no TCP/IP do kernel.
Insight arquitetural central
Métodos de Hub não devem ser usados como:
- camada síncrona de acesso a dados
- mecanismo de fallback de cache
- ponto de espera para operações lentas
SignalR deve responder rápido e delegar processamento pesado para fluxos desacoplados.