Skip to content

Thread-aware connection tracking for Java: eBPF TID correlation + optional Java agent for async (fixes test-case↔mock matching under connection pooling) #4333

Description

@sneha0099

Problem

When a Java application uses connection pooling (HikariCP, DBCP, etc.), Keploy cannot reliably attribute outgoing calls to the incoming test case that caused them.

Example: two requests arrive concurrently while recording —

Request A: GET /users/1
Request B: GET /orders/5

The app borrows pooled MySQL connections, and the proxy sees:

pooled conn #3: SELECT * FROM users  WHERE id=1
pooled conn #7: SELECT * FROM orders WHERE id=5

The proxy only sees bytes on TCP connections. The pooled connections were opened at app startup (often by pool housekeeping threads), so connection identity tells us nothing about which logical request a query belongs to. Under concurrency this leads to mocks being attached to the wrong test case. Pool housekeeping traffic (e.g. SELECT 1 validation pings) is also wrongly swept into whichever test case is being recorded.

Today the eBPF layer tracks connections at process granularity only (DestInfo.KernelPid in pkg/agent/hooks/structs), with no thread identity anywhere in the pipeline.

Key insight

In blocking Java stacks (Spring MVC, servlet containers, blocking JDBC — the majority of Java apps), each request is handled by one thread from start to finish. Java platform threads are 1:1 with Linux kernel threads, and every socket read/write syscall exposes its TID to eBPF via bpf_get_current_pid_tgid() (lower 32 bits).

So if we attribute traffic per syscall by TID instead of per connection, pooling stops being a problem:

Thread-1 read  Request A            → Thread-1 is now serving A
Thread-1 wrote SELECT ... users     → belongs to A ✓
Thread-2 read  Request B
Thread-2 wrote SELECT ... orders    → belongs to B ✓
pool thread wrote SELECT 1          → never read an incoming request → belongs to no test ✓

Proposal

Phase 1 — kernel only, no app changes:

  • eBPF probes on socket read/write emit (TID, connection, direction, timestamp).
  • Rule: the thread that read incoming request R owns it until it writes the response; every outgoing call by that thread in between belongs to R.
  • Fixes pooling for most Java apps (Spring MVC, blocking JDBC). Pool health-pings automatically match no test.

Phase 2 — async/virtual-thread apps:

  • TID isn't meaningful there (shared event-loop threads), so a thin -javaagent tracks "thread X is now running request R" and Keploy writes TID → requestID into a BPF map.
  • The same kernel probes stamp each I/O event with the request ID from the map. Traffic bytes are never modified.

Activity

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

Metadata

Metadata

Assignees

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