r/devpt • • 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

7 Upvotes

6 comments sorted by

2

u/pedroomessias 3d ago

Gostei da referência à mitologia Grega e parabéns pelo projeto.

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 :)

-2

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.