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.
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 —
The app borrows pooled MySQL connections, and the proxy sees:
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 1validation pings) is also wrongly swept into whichever test case is being recorded.Today the eBPF layer tracks connections at process granularity only (
DestInfo.KernelPidinpkg/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/writesyscall exposes its TID to eBPF viabpf_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:
Proposal
Phase 1 — kernel only, no app changes:
Phase 2 — async/virtual-thread apps: