Details
-
Bug
-
Status: Closed (View Workflow)
-
Major
-
Resolution: Fixed
-
3.3.8
-
None
Description
Closing a statement that has not started retrieval while there is a cursor opened in another statement causes the mariadb client to loose sync with the physical protocol state which in turn causes erratic behaviour in following interactions with the API: wrong return codes, crashes, hangs, etc
Correct behaviour should be for "mysql_stmt_close" to close the current cursor (regardless of what statement opened it). Then attempts to fetch data from a cursor while there is none active (regardless of the statement used in the mysql_stmt_fetch call) should return an error saying there is no cursor opened.
The interaction causing the issue to surface is:
create stmt1
prepare stmt1
create stmt2
prepare stmt2
fetch stmt1
close stmt2 -> from this point onwards the client is in an inconsistent state. Any interaction with the client from this point onwards will be subject to undeterministic behavior.
NOTE: The application I work on and where this causes wrong behaviour (crashes in my case) allows customers to choose how statements interact with connections. The customer can for instance use a connection per stmt or can decide to have concurrent statements using the same connection. This flexibility is in place since this application supports many other c connectors (oracle, postgres, etc) where retrieving data concurrently from different statements is supported by their respectives clients. In short, getting real useful errors indicating the interaction attempted is not supported by the underlying connector is ok but getting crashes or undeterministic behaviour is not.
For your convenience I have attached a patch.diff (generated from "master") with test cases that reproduce this issue. The patch adds this test cases in the "ps_bugs" executable that comes as part of the mariadb c connector test suite.
Also, I have done an initial investigation which shows that the culprit seems to be in line 379 (this is from "master" at the time of writing) in function mthd_stmt_flush_unbuffered() in file mariadb_stmt.c:
369 void mthd_stmt_flush_unbuffered(MYSQL_STMT *stmt)
|
370 {
|
371 ulong packet_len;
|
372 int in_resultset= stmt->state > MYSQL_STMT_EXECUTED &&
|
373 stmt->state < MYSQL_STMT_FETCH_DONE;
|
374 while ((packet_len = ma_net_safe_read(stmt->mysql)) != packet_error)
|
375 {
|
376 unsigned int last_status= stmt->mysql->server_status;
|
377 uchar *pos= stmt->mysql->net.read_pos;
|
378
|
|
"in_resultset" is 0 since "stmt" has not started fetching yet. However, there is
|
a cursor open at this point on a different statement so "pos" is not pointing
|
at an OK packet but at the header (also 0x00) of a row packet.
|
 |
Fix: The mariadb client should not be using the state of the stmt struct to
|
know if it is in the middle of a fetch but the state of the MYSQL struct.
|
|
379 if (!in_resultset && *pos == 0) /* OK */
|
380 {
|
381 pos++;
|
382 net_field_length(&pos);
|
383 net_field_length(&pos);
|
384 stmt->mysql->server_status= uint2korr(pos);
|
385 ma_status_callback(stmt->mysql, last_status);
|
386 goto end;
|
387 }
|
388 if (packet_len < 8 && *pos == 254) /* EOF */
|
389 {
|
390 if (mariadb_connection(stmt->mysql))
|
391 {
|
392 stmt->mysql->server_status= uint2korr(pos + 3);
|
393 ma_status_callback(stmt->mysql, last_status);
|
394 if (in_resultset)
|
395 goto end;
|
396 in_resultset= 1;
|
397 }
|
398 else
|
399 goto end;
|
400 }
|
401 }
|
402 end:
|
403 stmt->state= MYSQL_STMT_FETCH_DONE;
|
404 }
|
|
Please have a look.
Thanks