fix(mysql/replay): synthesize OK for unmocked COM_STMT_RESET by khareyash05 · Pull Request #4217 · keploy/keploy · GitHub
Skip to content

fix(mysql/replay): synthesize OK for unmocked COM_STMT_RESET#4217

Merged
khareyash05 merged 2 commits into
mainfrom
fix/mysql-stmt-reset-synthetic-ok
May 25, 2026
Merged

fix(mysql/replay): synthesize OK for unmocked COM_STMT_RESET#4217
khareyash05 merged 2 commits into
mainfrom
fix/mysql-stmt-reset-synthetic-ok

Conversation

@khareyash05

Copy link
Copy Markdown
Contributor

ServerPreparedStatement re-execution under Connector/J 8.x calls ServerPreparedQuery.prepareExecutePacket(), which opportunistically emits COM_STMT_RESET to clear cursor / long-data state before sending the next COM_STMT_EXECUTE. The record run is typically single-tenant and the driver doesn't think a reset is needed, so no recorded mock exists for that packet. On the second+ replay execution of the same prepared statement the driver decides a reset IS needed; the replayer then walked the entire mock pool without finding a match, fell through the COM_QUERY-only synthetic-OK fallback (match.go:478), and tripped query.go:136 ("Connection closing due to no matching mock found"), tearing down the TCP connection.

To the application that surfaces as:

CJCommunicationsException: Communications link failure
SQLSTATE 08S01
Caused by: java.io.IOException: Socket is closed.

HikariCP marks the connection broken; Spring's JpaTransactionManager then tries to rollback on that already-broken connection and fails with "Unable to rollback against JDBC Connection" — which is what the HTTP 500 body shows in the failing replay (the SET autocommit=0/1 lines that appear alongside are just HikariCP opening a fresh connection to replace the broken one, not the cause).

Fix is local to matchCommand: declare sCOM_STMT_RESET, and after the existing COM_QUERY graceful-OK block, add a parallel branch that returns a synthetic mysql.OKPacket for unmocked COM_STMT_RESET. The packet is stateless from the mock's perspective — the server's reply is defined to be OK on success and ERR only for an unknown statement ID — so synthesizing OK is correct protocol behavior and avoids the connection-close cascade. The statement_id is preserved in the debug log for traceability.

Repro path (Spring Boot 3.x + Hibernate 6.x + HikariCP + mysql-connector-j 8.2 against TiDB on port 4000):

  1. Record a flow that executes the same @query repository method.
  2. Replay; first invocation passes, subsequent invocations of the same prepared statement hit the reset path and fail with 500 "Unable to rollback against JDBC Connection" + agent-log ERROR "Connection closing due to no matching mock found" with request_type=COM_STMT_RESET.

Describe the changes that are made

Links & References

Closes: #[issue number that will be closed through this PR]

  • NA (if very small change like typo, linting, etc.)

🔗 Related PRs

  • NA

🐞 Related Issues

  • NA

📄 Related Documents

  • NA

What type of PR is this? (check all applicable)

  • 📦 Chore
  • 🍕 Feature
  • 🐞 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • 🧑‍💻 Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🔁 CI
  • ⏩ Revert

Added e2e test pipeline?

  • 👍 yes
  • 🙅 no, because they aren't needed
  • 🙋 no, because I need help

Added comments for hard-to-understand areas?

  • 👍 yes
  • 🙅 no, because the code is self-explanatory

Added to documentation?

  • 📜 README.md
  • 📓 Wiki
  • 🙅 no documentation needed

Are there any sample code or steps to test the changes?

  • 👍 yes, mentioned below
  • 🙅 no, because it is not needed

Self Review done?

  • ✅ yes
  • ❌ no, because I need help

Any relevant screenshots, recordings or logs?

  • NA

🧠 Semantics for PR Title & Branch Name

Please ensure your PR title and branch name follow the Keploy semantics:

📌 PR Semantics Guide
📌 Branch Semantics Guide

Examples:

  • PR Title: fix: patch MongoDB document update bug
  • Branch Name: feat/#1-login-flow (You may skip mentioning the issue number in the branch name if the change is small and the PR description clearly explains it.)

Additional checklist:

ServerPreparedStatement re-execution under Connector/J 8.x calls
ServerPreparedQuery.prepareExecutePacket(), which opportunistically
emits COM_STMT_RESET to clear cursor / long-data state before sending
the next COM_STMT_EXECUTE. The record run is typically single-tenant
and the driver doesn't think a reset is needed, so no recorded mock
exists for that packet. On the second+ replay execution of the same
prepared statement the driver decides a reset IS needed; the replayer
then walked the entire mock pool without finding a match, fell through
the COM_QUERY-only synthetic-OK fallback (match.go:478), and tripped
query.go:136 ("Connection closing due to no matching mock found"),
tearing down the TCP connection.

To the application that surfaces as:

  CJCommunicationsException: Communications link failure
    SQLSTATE 08S01
    Caused by: java.io.IOException: Socket is closed.

HikariCP marks the connection broken; Spring's JpaTransactionManager
then tries to rollback on that already-broken connection and fails
with "Unable to rollback against JDBC Connection" — which is what the
HTTP 500 body shows in the failing replay (the SET autocommit=0/1
lines that appear alongside are just HikariCP opening a fresh
connection to replace the broken one, not the cause).

Fix is local to matchCommand: declare sCOM_STMT_RESET, and after the
existing COM_QUERY graceful-OK block, add a parallel branch that
returns a synthetic mysql.OKPacket for unmocked COM_STMT_RESET. The
packet is stateless from the mock's perspective — the server's reply
is defined to be OK on success and ERR only for an unknown statement
ID — so synthesizing OK is correct protocol behavior and avoids the
connection-close cascade. The statement_id is preserved in the debug
log for traceability.

Repro path (Spring Boot 3.x + Hibernate 6.x + HikariCP + mysql-connector-j 8.2
against TiDB on port 4000):
  1. Record a flow that executes the same @query repository method.
  2. Replay; first invocation passes, subsequent invocations of the
     same prepared statement hit the reset path and fail with 500
     "Unable to rollback against JDBC Connection" + agent-log ERROR
     "Connection closing due to no matching mock found" with
     request_type=COM_STMT_RESET.

Signed-off-by: Yash Khare <khareyash05@gmail.com>
@khareyash05 khareyash05 requested a review from gouravkrosx as a code owner May 25, 2026 13:20
@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

🚀 Keploy Performance Test Results

Multi-Run Validation: Tests run 3 times, pipeline fails only if 2+ runs show regression.

Run P50 P90 P99 RPS Error Rate Status
1 2.56ms 3.31ms 4.67ms 100.02 0.00% ✅ PASS
2 2.49ms 3.21ms 4.27ms 100.03 0.00% ✅ PASS
3 2.47ms 3.21ms 4.38ms 100.00 0.00% ✅ PASS

Thresholds: P50 < 5ms, P90 < 15ms, P99 < 70ms, RPS >= 100 (±1% tolerance), Error Rate < 1%

Result: PASSED - Only 0 out of 3 runs failed (threshold: 2)

P50, P90, and P99 percentiles naturally filter out outliers

@khareyash05 khareyash05 merged commit 9e16ae4 into main May 25, 2026
140 checks passed
@khareyash05 khareyash05 deleted the fix/mysql-stmt-reset-synthetic-ok branch May 25, 2026 13:55
@github-actions github-actions Bot locked and limited conversation to collaborators May 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants