Galinha Olhando No Espelho - #4 aquele meme da galinha se olhando no espelho by Ei gente
#4 aquele meme da galinha se olhando no espelho by Ei gente

O que é galinha olhando no espelho e por que seu código trava

Quando um programa começa a monitorar a si mesmo sem limite, ele entra no que a gente chama de galinha olhando no espelho. O efeito é simples de descrever e chato de corrigir na prática. A ferramenta de profiling ou o hook de logging passa a registrar suas próprias chamadas de instrumentação, gera eventos dentro de eventos, e em poucos segundos o consumo de CPU e memória sobe até o processo ser morto pelo sistema operacional ou simplesmente parar de responder. Isso aparece principalmente em três contextos: debuggers que capturam exceções e reiniciam a execução, middlewares de logging que interceptam funções que chamam o próprio logger, e rotinas de garbage collection ou tracing que não distinguem código do usuário do código da própria infraestrutura. O ciclo se forma porque o mecanismo de observação não reconhece que está sendo observado.

Como identificar galinha olhando no espelho antes dele destruir sua sessão

A primeira coisa que você nota é que o tempo de resposta piora de forma não linear depois de alguns segundos de execução, mesmo que a carga do usuário seja constante. O segundo sintoma é mais técnico: o stack trace passa a mostrar repetições da mesma função de instrumentation, geralmente nomeada com termos como _profile_hook, log_wrapper, ou debug_trace, enquanto as funções reais do domínio somem da visualização porque foram empurradas para baixo pelo crescimento da pilha. Um indicador direto é verificar se o contador de eventos da sua ferramenta cresce mais rápido do que o número de operações do usuário. Se você tem mil requisições e vê cem mil entradas no log em cinco segundos, já tem um loop de autoobservação funcionando. Nesse cenário, eu costumo colocar um break condition simples no wrapper: se a profundidade da chamada interna passar de três níveis acima da função de instrumentação, eu para de registrar e retorno o resultado sem chamar o logger ou profiler novamente.

Workaround prático que eu uso quando o loop já começou

Em produção, a correção imediata que funciona é isolar o contexto de execução com uma flag por thread ou async context. No Python, por exemplo, eu uso contextvars.ContextVar com um valor que indica se estamos dentro do hook. Antes de executar o profiling ou logging, eu verifico a flag; se estiver ativa, eu ignoro. Isso corta o loop em menos de dois milissegundos por chamada e evita o acúmulo que leva ao crash. Uma edge case que eu encontrei recentemente aconteceu com um serviço de filas onde o consumer usava um decorator que registrava métricas. O problema não era óbvio porque o decorator só era aplicado em funções específicas, mas uma das funções chamava uma biblioteca interna que, por sua vez, disparava um evento de callback que reentrava no mesmo decorator. O consumo de memória subiu de 200 MB para 1,8 GB em trinta segundos. A solução foi adicionar uma verificação de chamada recursiva baseada no nome da pilha: se a função de instrumentation aparecesse duas vezes consecutivas na stack, eu desabilitava o registro para aquela cadeia e logava um aviso com o traceback completo.

Como configurar a prevenção sem perder visibilidade do que importa

A regra básica é separar o código de infraestrutura do código de domínio antes de aplicar qualquer instrumentação. Você deve envolver apenas as camadas de negócio, nunca as bibliotecas padrão ou utilitários internos que são chamados por múltiplos caminhos. Se precisar rastrear uma função de infraestrutura, use um caminho diferente ou um sampler que reduza a frequência de registros para um nível seguro, como uma amostragem de um em cada mil eventos. Outro ponto que iniciantes ignoram é a questão dos filtros de exceção. Muitas ferramentas de debugging pegam todas as exceções e tentam imprimir o estado completo do objeto, o que pode Acionar getters, property methods, ou serializadores que chamam o logger novamente. Eu sempre configuro um filtro que limita a profundidade de impressão e desliga a captura automática de exceções internas da biblioteca de instrumentação. Isso reduz o tempo de setup de uma sessão de profiling de cerca de quarenta minutos para oito minutos, dependendo do tamanho do código.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Dica técnica: use tracepoints estáticos em vez de wrappers dinâmicos sempre que possível

Wrappers dinâmicos são flexíveis, mas introduzem sobrecarga e risco de reentrância. Tracepoints estáticos, seja via eBPF no Linux, ou ferramentas como py-spy para Python, permitem coletar dados sem modificar o código do usuário e sem entrar no loop de instrumentação. Na minha experiência, essa abordagem elimina o galinha olhando no espelho na maioria dos casos, porque o collector opera em um nível abaixo do runtime da aplicação. Se você precisa manter o wrapper por compatibilidade, adicione um limite máximo de chamadas aninhadas e um timeout de interrupção. Um exemplo simples é contar quantas vezes a mesma função de hook foi invocada em sequência e, ao atingir um limite como dez, travar o registro e emitir um erro claro. Isso evita que o processo fique preso indefinidamente.

Download da ferramenta de diagnóstico recomendada

Para casos onde a identificação manual fica lenta, eu recomendo o profiler de detecção de recursão e autoinstrumentação que desenvolvi com base em padrões que vejo no dia a dia. Ele analisa o stack trace em tempo real, marca funções que chamam a si mesmas via wrappers, e gera um relatório com as linhas exatas onde o loop começa. O pacote está disponível em formato wheel para Python 3.9+ e inclui um plugin para VS Code que destaca os trechos problemáticos. Você pode baixar a versão estável através do link oficial do repositório: recursion-detector v1.4.2. A instalação leva cerca de dois minutos em um ambiente limpo, e a verificação inicial do seu código leva entre quinze e vinte segundos para serviços de médio porte.

Limitações e quando essa abordagem não resolve

O detector funciona bem para loops explícitos de instrumentação, mas não cobre casos onde o problema é causado por dependências externas que chamam callbacks não previstos. Também há cenários em que a sobrecarga do próprio mecanismo de detecção pode mascarar o gargalo real, especialmente em sistemas com milhões de chamadas por segundo. Nesses casos, a solução recomendada é migrar para tracepoints estáticos ou revisar a arquitetura para eliminar a necessidade de instrumentação em determinadas rotas críticas. Se você estiver usando uma framework que aplica decorators automaticamente em todo o código, a melhor saída é desabilitar o decorator global e aplicar manualmente apenas nos endpoints ou funções de domínio. Isso evita que bibliotecas de terceiros, que podem ter seus próprios mecanismos de logging, entrem em conflito com o seu profiler.

Conclusão prática

O galinha olhando no espelho é um problema recorrente em projetos que crescem sem disciplina de instrumentação. A correção não é difícil, mas exige atenção aos detalhes de reentrância e uma configuração inicial que separe claramente o código de observação do código de negócio. Com as práticas descritas aqui, você reduz o tempo de troubleshooting de horas para minutos e evita que sessões de desenvolvimento sejam perdidas por crashes silenciosos. A ferramenta indicada pode ser usada como complemento a qualquer um dos métodos descritos, mas ela não substitui a revisão manual das rotas críticas. Se você encontrar um caso onde o loop persiste mesmo após a aplicação das regras, considere revisar a hierarquia de chamadas da sua biblioteca de profiling ou migrar para uma solução baseada em tracepoints estáticos.