Skip to content

Commit 57fbc2b

Browse files
authored
Merge pull request #278 from schveiguy/fixserverdrop
Attempt to detect when the server connection becomes stale.
2 parents 1bcf0bc + 71aad34 commit 57fbc2b

File tree

3 files changed

+135
-59
lines changed

3 files changed

+135
-59
lines changed

Diff for: source/mysql/exceptions.d

+33
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,36 @@ class MYXInvalidatedRange: MYX
156156
super(msg, file, line);
157157
}
158158
}
159+
160+
/++
161+
Thrown when a stale connection to the server is detected.
162+
163+
To properly use this, it is suggested to use the following construct:
164+
165+
----
166+
retry:
167+
try {
168+
conn.exec(...); // do the command
169+
// or prepare, query, etc.
170+
}
171+
catch(MYXStaleConnection)
172+
{
173+
goto retry;
174+
}
175+
----
176+
177+
In the future, when the protocol code is rewritten, this may become a built-in
178+
feature so the user does not have to do this on their own.
179+
180+
NOTE: this is a new mechanism to try and capture this case so user code can
181+
properly handle the retry. Any bugs in this happening as an infinite loop,
182+
please file an issue with the exact case.
183+
+/
184+
class MYXStaleConnection: MYX
185+
{
186+
@safe pure:
187+
this(string msg, string file = __FILE__, size_t line = __LINE__)
188+
{
189+
super(msg, file, line);
190+
}
191+
}

Diff for: source/mysql/impl/connection.d

+6-6
Original file line numberDiff line numberDiff line change
@@ -631,8 +631,8 @@ public:
631631
+/
632632
void selectDB(string dbName)
633633
{
634-
this.sendCmd(CommandType.INIT_DB, dbName);
635-
this.getCmdResponse();
634+
auto packet = this.sendCmd(CommandType.INIT_DB, dbName);
635+
this.getCmdResponse(packet);
636636
_db = dbName;
637637
}
638638

@@ -645,8 +645,8 @@ public:
645645
+/
646646
OKErrorPacket pingServer()
647647
{
648-
this.sendCmd(CommandType.PING, []);
649-
return this.getCmdResponse();
648+
auto packet = this.sendCmd(CommandType.PING, []);
649+
return this.getCmdResponse(packet);
650650
}
651651

652652
/++
@@ -658,8 +658,8 @@ public:
658658
+/
659659
OKErrorPacket refreshServer(RefreshFlags flags)
660660
{
661-
this.sendCmd(CommandType.REFRESH, [flags]);
662-
return this.getCmdResponse();
661+
auto packet = this.sendCmd(CommandType.REFRESH, [flags]);
662+
return this.getCmdResponse(packet);
663663
}
664664

665665
/++

Diff for: source/mysql/protocol/comms.d

+96-53
Original file line numberDiff line numberDiff line change
@@ -391,36 +391,60 @@ package struct ProtocolPrepared
391391
}
392392
}
393393

394-
static void sendCommand(Connection conn, uint hStmt, PreparedStmtHeaders psh,
394+
static ubyte[] sendCommand(Connection conn, uint hStmt, PreparedStmtHeaders psh,
395395
MySQLVal[] inParams, ParameterSpecialization[] psa)
396396
{
397-
conn.autoPurge();
397+
ubyte[] impl()
398+
{
399+
conn.autoPurge();
398400

399-
ubyte[] packet;
400-
conn.resetPacket();
401+
ubyte[] packet;
402+
conn.resetPacket();
403+
404+
ubyte[] prefix = makePSPrefix(hStmt, 0);
405+
size_t len = prefix.length;
406+
bool longData;
407+
408+
if (psh.paramCount)
409+
{
410+
ubyte[] one = [ 1 ];
411+
ubyte[] vals;
412+
ubyte[] types = analyseParams(inParams, psa, vals, longData);
413+
ubyte[] nbm = makeBitmap(inParams);
414+
packet = prefix ~ nbm ~ one ~ types ~ vals;
415+
}
416+
else
417+
packet = prefix;
401418

402-
ubyte[] prefix = makePSPrefix(hStmt, 0);
403-
size_t len = prefix.length;
404-
bool longData;
419+
if (longData)
420+
sendLongData(conn._socket, hStmt, psa);
405421

406-
if (psh.paramCount)
422+
assert(packet.length <= uint.max);
423+
packet.setPacketHeader(conn.pktNumber);
424+
conn.bumpPacket();
425+
conn._socket.send(packet);
426+
427+
return conn.getPacket();
428+
}
429+
430+
if(conn._socket.connected)
407431
{
408-
ubyte[] one = [ 1 ];
409-
ubyte[] vals;
410-
ubyte[] types = analyseParams(inParams, psa, vals, longData);
411-
ubyte[] nbm = makeBitmap(inParams);
412-
packet = prefix ~ nbm ~ one ~ types ~ vals;
432+
try
433+
{
434+
return impl();
435+
}
436+
catch(Exception)
437+
{
438+
// convert all exceptions here as stale connections. This might be the
439+
// first try to send a request to the server on an
440+
// already-existing connection.
441+
throw new MYXStaleConnection("Possible stale connection");
442+
}
413443
}
414444
else
415-
packet = prefix;
416-
417-
if (longData)
418-
sendLongData(conn._socket, hStmt, psa);
419-
420-
assert(packet.length <= uint.max);
421-
packet.setPacketHeader(conn.pktNumber);
422-
conn.bumpPacket();
423-
conn._socket.send(packet);
445+
{
446+
return impl();
447+
}
424448
}
425449
}
426450

@@ -454,22 +478,21 @@ package(mysql) bool execQueryImpl(Connection conn, ExecQueryImplInfo info, out u
454478
scope(failure) conn.kill();
455479

456480
// Send data
481+
ubyte[] packet;
457482
if(info.isPrepared)
458483
{
459484
logTrace("prepared SQL: %s", info.hStmt);
460485

461-
ProtocolPrepared.sendCommand(conn, info.hStmt, info.psh, info.inParams, info.psa);
486+
packet = ProtocolPrepared.sendCommand(conn, info.hStmt, info.psh, info.inParams, info.psa);
462487
}
463488
else
464489
{
465490
logTrace("exec query: %s", info.sql);
466491

467-
conn.sendCmd(CommandType.QUERY, info.sql);
492+
packet = conn.sendCmd(CommandType.QUERY, info.sql);
468493
conn._fieldCount = 0;
469494
}
470495

471-
// Handle response
472-
ubyte[] packet = conn.getPacket();
473496
bool rv;
474497
if (packet.front == ResultPacketMarker.ok || packet.front == ResultPacketMarker.error)
475498
{
@@ -688,7 +711,8 @@ do
688711
_socket.write(data);
689712
}
690713

691-
package(mysql) void sendCmd(T)(Connection conn, CommandType cmd, const(T)[] data)
714+
// returns the first packet received
715+
package(mysql) ubyte[] sendCmd(T)(Connection conn, CommandType cmd, const(T)[] data)
692716
in
693717
{
694718
// Internal thread states. Clients shouldn't use this
@@ -709,39 +733,62 @@ in
709733
out
710734
{
711735
// at this point we should have sent a command
712-
assert(conn.pktNumber == 1);
736+
//assert(conn.pktNumber == 1);
713737
}
714738
do
715739
{
716-
scope(failure) conn.kill();
717-
718740
conn._lastCommandID++;
719741

720-
if(!conn._socket.connected)
742+
ubyte[] impl()
721743
{
722-
if(cmd == CommandType.QUIT)
723-
return; // Don't bother reopening connection just to quit
744+
scope(failure) conn.kill();
724745

725-
conn._open = Connection.OpenState.notConnected;
726-
conn.connect(conn._clientCapabilities);
727-
}
746+
conn.autoPurge();
728747

729-
conn.autoPurge();
748+
conn.resetPacket();
730749

731-
conn.resetPacket();
750+
ubyte[] header;
751+
header.length = 4 /*header*/ + 1 /*cmd*/;
752+
header.setPacketHeader(conn.pktNumber, cast(uint)data.length +1/*cmd byte*/);
753+
header[4] = cmd;
754+
conn.bumpPacket();
732755

733-
ubyte[] header;
734-
header.length = 4 /*header*/ + 1 /*cmd*/;
735-
header.setPacketHeader(conn.pktNumber, cast(uint)data.length +1/*cmd byte*/);
736-
header[4] = cmd;
737-
conn.bumpPacket();
756+
conn._socket.send(header, cast(const(ubyte)[])data);
757+
758+
// now, get the first packet, but only if the command is not QUIT
759+
if(cmd == CommandType.QUIT)
760+
return null;
761+
762+
return conn.getPacket();
763+
}
764+
765+
if(conn._socket.connected)
766+
{
767+
try
768+
{
769+
return impl();
770+
}
771+
catch(Exception)
772+
{
773+
// convert all exceptions here as stale connections. This is the
774+
// first try to send a request to the server on an already-existing connection.
775+
throw new MYXStaleConnection("Possible stale connection");
776+
}
777+
}
778+
else
779+
{
780+
if(cmd == CommandType.QUIT)
781+
return null; // Don't bother reopening connection just to quit
738782

739-
conn._socket.send(header, cast(const(ubyte)[])data);
783+
conn._open = Connection.OpenState.notConnected;
784+
conn.connect(conn._clientCapabilities);
785+
return impl();
786+
}
740787
}
741788

742-
package(mysql) OKErrorPacket getCmdResponse(Connection conn, bool asString = false)
789+
package(mysql) OKErrorPacket getCmdResponse(Connection conn, ubyte[] packet)
743790
{
744-
auto okp = OKErrorPacket(conn.getPacket());
791+
auto okp = OKErrorPacket(packet);
745792
enforcePacketOK(okp);
746793
conn._serverStatus = okp.serverStatus;
747794
return okp;
@@ -960,10 +1007,9 @@ package(mysql) PreparedServerInfo performRegister(Connection conn, const(char[])
9601007

9611008
PreparedServerInfo info;
9621009

963-
conn.sendCmd(CommandType.STMT_PREPARE, sql);
1010+
ubyte[] packet = conn.sendCmd(CommandType.STMT_PREPARE, sql);
9641011
conn._fieldCount = 0;
9651012

966-
ubyte[] packet = conn.getPacket();
9671013
if(packet.front == ResultPacketMarker.ok)
9681014
{
9691015
packet.popFront();
@@ -1046,8 +1092,7 @@ Get a textual report on the server status.
10461092
+/
10471093
package(mysql) string serverStats(Connection conn)
10481094
{
1049-
conn.sendCmd(CommandType.STATISTICS, []);
1050-
auto result = conn.getPacket();
1095+
auto result = conn.sendCmd(CommandType.STATISTICS, []);
10511096
return (() @trusted => cast(string)result)();
10521097
}
10531098

@@ -1071,10 +1116,8 @@ package(mysql) void enableMultiStatements(Connection conn, bool on)
10711116
t.length = 2;
10721117
t[0] = on ? 0 : 1;
10731118
t[1] = 0;
1074-
conn.sendCmd(CommandType.STMT_OPTION, t);
1075-
10761119
// For some reason this command gets an EOF packet as response
1077-
auto packet = conn.getPacket();
1120+
auto packet = conn.sendCmd(CommandType.STMT_OPTION, t);
10781121
enforce!MYXProtocol(packet[0] == 254 && packet.length == 5, "Unexpected response to SET_OPTION command");
10791122
}
10801123

0 commit comments

Comments
 (0)