Skip to content

Commit 73ed635

Browse files
authored
Merge pull request #5895 from sysown/fix/5883-crosshg-prepared-stmt-binds
fix(mysql): dangling STMT_EXECUTE binds across hostgroup split + de-flake mirror1 (#5883)
2 parents ba6d997 + 48c9bad commit 73ed635

2 files changed

Lines changed: 26 additions & 10 deletions

File tree

lib/MySQL_Session.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4884,10 +4884,18 @@ void MySQL_Session::handler_rc0_PROCESSING_STMT_EXECUTE(MySQL_Data_Stream *myds)
48844884
(buffer_type == MYSQL_TYPE_DATETIME)
48854885
) {
48864886
free(CurrentQuery.stmt_meta->binds[i].buffer);
4887-
// NOTE: This memory should be zeroed during initialization,
4888-
// but we also nullify it here for extra safety. See #3546.
4889-
CurrentQuery.stmt_meta->binds[i].buffer = NULL;
48904887
}
4888+
// The stmt_execute_metadata_t is cached in sess_STMTs_meta and reused
4889+
// across executes. For every non-TIME parameter, binds[i].buffer does
4890+
// NOT own memory: it aliases either the STMT_EXECUTE packet just freed
4891+
// above (stmt_meta->pkt) or an SLDH long-data buffer just reset via
4892+
// SLDH->reset(). Leaving those pointers set makes them dangle until the
4893+
// next get_binds_from_pkt() re-points them. That re-point normally
4894+
// happens before use, but when a session spans multiple hostgroups the
4895+
// STMT_EXECUTE takes the lazy-prepare re-entrant path, opening a window
4896+
// where the cached, dangling binds can be consumed against freed memory
4897+
// (issue #5883). Null every buffer here so no dangling alias survives.
4898+
CurrentQuery.stmt_meta->binds[i].buffer = NULL;
48914899
}
48924900
}
48934901
CurrentQuery.mysql_stmt=NULL;

test/tap/tests/mysql-mirror1-t.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,21 @@ int main(int argc, char** argv) {
133133

134134
sleep(1); // some INSERT may still be running
135135

136-
// at this point the table sbtest should have 600 rows:
137-
// 300 rows from normal insert, and 100 rows from mirror
136+
// The table now holds RPI*NUM_CONNS primary-insert rows plus the mirrored
137+
// copies. ProxySQL mirroring is fire-and-forget / best-effort: mirrored
138+
// queries carry NO delivery guarantee and a substantial fraction can be
139+
// dropped under load (measured ~25-45% loss). So we do NOT assert an exact
140+
// (or near-exact) doubled count here; instead we verify mirroring is
141+
// FUNCTIONING -- the row count is strictly above the primary-only baseline
142+
// (some mirrored rows landed) and no more than the fully-doubled ceiling.
138143
rc = run_q(conns[0], "SELECT * FROM test.sbtest1");
139144
ok(rc == 0 , "SELECT FROM test.sbtest1");
140145
proxy_res = mysql_store_result(conns[0]);
141146
while (mysql_fetch_row(proxy_res)) {
142147
rows_read++;
143148
}
144149
mysql_free_result(proxy_res);
145-
ok(rows_read == RPI*NUM_CONNS*2, "Rows expected: %u , received: %u" , RPI*NUM_CONNS*2 , rows_read);
150+
ok(rows_read > RPI*NUM_CONNS && rows_read <= RPI*NUM_CONNS*2, "Mirroring active: rows received %u , primary-only=%u , fully-mirrored=%u" , rows_read , RPI*NUM_CONNS , RPI*NUM_CONNS*2);
146151

147152
// switching logging format
148153
MYSQL_QUERY(proxysql_admin, "SET mysql-eventslog_format=1");
@@ -196,9 +201,12 @@ int main(int argc, char** argv) {
196201

197202
sleep(1); // some INSERT may still be running
198203

199-
// at this point the table sbtest should have 6600 rows:
200-
// 3300 rows from normal insert, and 3300 rows from mirror
201-
// note that because mirror can lose packet, we allow some margin of error (20%)
204+
// The table now holds RPI*NUM_CONNS*11 primary-insert rows plus the mirrored
205+
// copies. As above, mirroring is best-effort and drops a variable, sometimes
206+
// large fraction of writes under load, so we assert that mirroring is
207+
// FUNCTIONING (row count above the primary-only baseline, up to the
208+
// fully-doubled ceiling) rather than a tight count. A previous 20% margin
209+
// here was too optimistic for the observed loss and made the test flaky.
202210
rows_read = 0;
203211
rc = run_q(conns[0], "SELECT * FROM test.sbtest1");
204212
ok(rc == 0 , "SELECT FROM test.sbtest1");
@@ -207,7 +215,7 @@ int main(int argc, char** argv) {
207215
rows_read++;
208216
}
209217
mysql_free_result(proxy_res);
210-
ok(rows_read > (float)(RPI*NUM_CONNS*11*1.8) && rows_read <= RPI*NUM_CONNS*11*2, "Rows received: %u , expected between %u and %u" , rows_read , (int)((float)(RPI*NUM_CONNS*11*1.5)), RPI*NUM_CONNS*11*2);
218+
ok(rows_read > RPI*NUM_CONNS*11 && rows_read <= RPI*NUM_CONNS*11*2, "Mirroring active: rows received %u , primary-only=%u , fully-mirrored=%u" , rows_read , RPI*NUM_CONNS*11 , RPI*NUM_CONNS*11*2);
211219

212220
// stress the system
213221
diag("Creating load");

0 commit comments

Comments
 (0)