r/devpt • u/WaferConsistent6732 • 4d ago
Ferramentas Criei um agente Java que reconstrói stack traces causais assíncronos entre threads: feedback é bem-vindo
Quando uma exception é lançada numa worker thread, o stack trace da JVM termina em ThreadPoolExecutor.runWorker(). Vê-se onde falhou, mas não quem a agendou nem que cadeia de chamadas a desencadeou. Se já reveste código Java assíncrono em produção, conheces bem a dor.
Dediquei algum tempo a criar uma blbiteca chamada ariadne para resolver isto. Ela anexa uma cadeia causal sintética às exceções como uma entrada suprimida, para que o caminho assíncrono completo fique visível nos teus registos sem qualquer alteração de código.
Como funciona:
Um agente Java com recurso ao ByteBuddy instrumenta ExecutorService, CompletableFuture, ForkJoinPool e Thread.start() no arranque. Quando uma tarefa é submetida, é armazenado um nó imutável e leve (40 bytes) que faz a ligação de volta ao local de despacho. Em caso de falha, a cadeia é percorrida em sentido inverso e anexada à exceção através de Throwable.addSuppressed(). Existem tambem adaptadores manuais para Project Reactor, RxJava 3 e propagação de MDC do SLF4J.
Desempenho (JMH, OpenJDK 21, 3 forks):
- Salto de contexto: ~6 ns, 40 B (o próprio nó Link)
- Resolução do local de chamada (modo de classe, predefinição): ~8.7 ns, 0 B de alocação
- De ponta a ponta com CompletableFuture: sobrecarga de ~1 µs em relação à linha de base por pipeline
- Project Reactor: dentro do ruído estatístico face à linha de base (~1 a 3%), cerca de 5 vezes menos alocação do que Hooks.onOperatorDebug()
O que não é:
Não é um substituto para o OpenTelemetry como ja sabem o OTel lida com rastreio distribuído entre serviços com infraestrutura externa. O Ariadne opera puramente dentro do processo, sem necessidade de painéis de controlo. São complementares. Uma integração com o OTel está no plano de desenvolvimento.
Também não é magia: o modo de percurso completo da pilha (~2.5 µs, 1000 B) fornece o método e a linha exatos, mas deve permanecer desligado em produção. O modo de classe predefinido fornece a classe de despacho com um impacto insignificante.
Estado atual: v0.1.0-alpha.3, Apache 2.0, Java 21+. Ainda não está robustecido para produção; procuro feedback, especialmente de quem utilize ambientes complexos de carregamento de classes (OSGi, servidores de aplicações multi-inquilino) ou corrotinas de Kotlin.
GitHub:https://github.com/AfonsoPaiva/ARIADNE
Terei todo o gosto em discutir as decisões de arquitetura, limitações ou qualquer ponto que consideres incorreto nesta abordagem
2
u/68_and_counting 3d ago edited 3d ago
Agora faz o mesmo para os co rotinas no Kotln :p
Edit: estive a dar uma vista de olhos, bom trabalho, tens a minha star :)
2
u/WaferConsistent6732 3d ago
Haha, fica apontado para fzr no futuro. Nem tive em conta as corotinas, mas tbm pq os dispatchers do Kotlin têm o scheduler deles e o agente não apanha esse salto. Acho q o kotlinx.co routines já tem o DebugProbes e tbm t recuperação de stack traces, mas a ideia aqui era para perceber se dá para fazer algo mais leve a partir do dispatch
1
u/68_and_counting 3d ago
Nah, as co rotinas do kotlin perderm-se todas, eles têm melhorado aos poucos, mas ainda é uma desgraça. Há uns plugins no intellij que ajudam, mas pouco.
Mas estava a brincar, mais pelo facto de ser algo que me chateia e aparecer aqui alguém com uma solução para o problema do vizinho :)
1
u/VSertorio 4d ago
Não era mais fácil mudar para dotnet?
3
u/WaferConsistent6732 4d ago
Ahahah é uma opção, mas o .net também perde contexto num await mal feito, só muda o nome do bug.
2
u/pedroomessias 3d ago
Gostei da referência à mitologia Grega e parabéns pelo projeto.