55
66package com .microsoft .sqlserver .jdbc ;
77
8+ import java .lang .Thread .State ;
9+ import java .util .concurrent .ScheduledFuture ;
810import java .util .concurrent .atomic .AtomicInteger ;
11+ import java .util .logging .Level ;
912
1013
1114class IdleConnectionResiliency {
15+ private static final java .util .logging .Logger loggerExternal = java .util .logging .Logger
16+ .getLogger ("com.microsoft.sqlserver.jdbc.IdleConnectionResiliency" );
1217 private boolean connectionRecoveryNegotiated ;
1318 private int connectRetryCount ;
1419 private SQLServerConnection connection ;
1520 private SessionStateTable sessionStateTable ;
1621 private ReconnectThread reconnectThread ;
1722 private AtomicInteger unprocessedResponseCount = new AtomicInteger ();
1823 private boolean connectionRecoveryPossible ;
24+ private SQLServerException reconnectErrorReceived = null ;
1925
2026 /*
2127 * Variables needed to perform a reconnect, these are not necessarily determined from just the connection string
@@ -27,7 +33,6 @@ class IdleConnectionResiliency {
2733
2834 IdleConnectionResiliency (SQLServerConnection connection ) {
2935 this .connection = connection ;
30- reconnectThread = new ReconnectThread (connection );
3136 }
3237
3338 boolean isConnectionRecoveryNegotiated () {
@@ -54,8 +59,8 @@ void setConnection(SQLServerConnection connection) {
5459 this .connection = connection ;
5560 }
5661
57- ReconnectThread getReconnectThread () {
58- return reconnectThread ;
62+ boolean isReconnectRunning () {
63+ return reconnectThread != null && ( reconnectThread . getState () != State . TERMINATED ) ;
5964 }
6065
6166 SessionStateTable getSessionStateTable () {
@@ -105,24 +110,34 @@ void parseInitialSessionStateData(byte[] data, byte[][] sessionStateInitial) thr
105110 }
106111
107112 void incrementUnprocessedResponseCount () {
108- if (connection .getRetryCount () > 0 && !getReconnectThread (). isAlive ()) {
109- if (unprocessedResponseCount .incrementAndGet () < 0 )
113+ if (connection .getRetryCount () > 0 && !isReconnectRunning ()) {
114+ if (unprocessedResponseCount .incrementAndGet () < 0 ) {
110115 /*
111116 * When this number rolls over, connection recovery is disabled for the rest of the life of the
112117 * connection.
113118 */
119+ if (loggerExternal .isLoggable (Level .FINER )) {
120+ loggerExternal .finer ("unprocessedResponseCount < 0 on increment. Disabling connection resiliency." );
121+ }
122+
114123 setConnectionRecoveryPossible (false );
124+ }
115125 }
116126 }
117127
118128 void decrementUnprocessedResponseCount () {
119- if (connection .getRetryCount () > 0 && !getReconnectThread (). isAlive ()) {
120- if (unprocessedResponseCount .decrementAndGet () < 0 )
129+ if (connection .getRetryCount () > 0 && !isReconnectRunning ()) {
130+ if (unprocessedResponseCount .decrementAndGet () < 0 ) {
121131 /*
122132 * When this number rolls over, connection recovery is disabled for the rest of the life of the
123133 * connection.
124134 */
135+ if (loggerExternal .isLoggable (Level .FINER )) {
136+ loggerExternal .finer ("unprocessedResponseCount < 0 on decrement. Disabling connection resiliency." );
137+ }
138+
125139 setConnectionRecoveryPossible (false );
140+ }
126141 }
127142 }
128143
@@ -148,6 +163,20 @@ FailoverInfo getFailoverInfo() {
148163 int getLoginTimeoutSeconds () {
149164 return loginLoginTimeoutSeconds ;
150165 }
166+
167+ void reconnect (TDSCommand cmd ) throws InterruptedException {
168+ reconnectErrorReceived = null ;
169+ reconnectThread = new ReconnectThread (this .connection , cmd );
170+ reconnectThread .start ();
171+ reconnectThread .join ();
172+ reconnectErrorReceived = reconnectThread .getException ();
173+ // Remove reference so GC can clean it up
174+ reconnectThread = null ;
175+ }
176+
177+ SQLServerException getReconnectException () {
178+ return reconnectErrorReceived ;
179+ }
151180}
152181
153182
@@ -370,6 +399,8 @@ public void reset() {
370399
371400
372401final class ReconnectThread extends Thread {
402+ static final java .util .logging .Logger loggerExternal = java .util .logging .Logger
403+ .getLogger ("com.microsoft.sqlserver.jdbc.ReconnectThread" );
373404 private SQLServerConnection con = null ;
374405 private SQLServerException eReceived = null ;
375406 private TDSCommand command = null ;
@@ -384,19 +415,23 @@ final class ReconnectThread extends Thread {
384415 @ SuppressWarnings ("unused" )
385416 private ReconnectThread () {};
386417
387- ReconnectThread (SQLServerConnection sqlC ) {
418+ ReconnectThread (SQLServerConnection sqlC , TDSCommand cmd ) {
388419 this .con = sqlC ;
389- }
390-
391- // Resets the thread
392- void init (TDSCommand cmd ) {
393420 this .command = cmd ;
394421 connectRetryCount = con .getRetryCount ();
395422 eReceived = null ;
396423 stopRequested = false ;
424+ if (loggerExternal .isLoggable (Level .FINER )) {
425+ loggerExternal .finer ("ReconnectThread initialized. Connection retry count = " + connectRetryCount
426+ + "; Command = " + cmd .toString ());
427+ }
428+
397429 }
398430
399431 public void run () {
432+ if (loggerExternal .isLoggable (Level .FINER )) {
433+ loggerExternal .finer ("Starting ReconnectThread for command: " + command .toString ());
434+ }
400435 boolean interruptsEnabled = command .getInterruptsEnabled ();
401436 /*
402437 * All TDSCommands are not interruptible before execution, and all the commands passed to here won't have been
@@ -405,10 +440,24 @@ public void run() {
405440 */
406441 command .setInterruptsEnabled (true );
407442 command .attachThread (this );
408- command .addToPoller ();
443+
444+ // We need a reference to the SharedTimer outside of the context of the connection
445+ SharedTimer timer = null ;
446+ ScheduledFuture <?> timeout = null ;
447+
448+ if (command .getQueryTimeoutSeconds () > 0 ) {
449+ timer = SharedTimer .getTimer ();
450+ timeout = timer .schedule (new TDSTimeoutTask (command , null ),
451+ command .getQueryTimeoutSeconds ());
452+ }
453+
409454 boolean keepRetrying = true ;
410455
411- while ((connectRetryCount != 0 ) && (!stopRequested ) && keepRetrying ) {
456+ while ((connectRetryCount > 0 ) && (!stopRequested ) && keepRetrying ) {
457+ if (loggerExternal .isLoggable (Level .FINER )) {
458+ loggerExternal .finer ("Running reconnect for command: " + command .toString () + " ; ConnectRetryCount = "
459+ + connectRetryCount );
460+ }
412461 try {
413462 eReceived = null ;
414463 con .connect (null , con .getPooledConnectionParent ());
@@ -451,10 +500,26 @@ public void run() {
451500 }
452501
453502 command .setInterruptsEnabled (interruptsEnabled );
454- return ;
503+
504+ if (loggerExternal .isLoggable (Level .FINER )) {
505+ loggerExternal .finer ("ReconnectThread exiting for command: " + command .toString ());
506+ }
507+
508+ if (timeout != null ) {
509+ timeout .cancel (false );
510+ timeout = null ;
511+ }
512+
513+ if (timer != null ) {
514+ timer .removeRef ();
515+ timer = null ;
516+ }
455517 }
456518
457519 void stop (boolean blocking ) {
520+ if (loggerExternal .isLoggable (Level .FINER )) {
521+ loggerExternal .finer ("ReconnectThread stop requested for command: " + command .toString ());
522+ }
458523 stopRequested = true ;
459524 if (blocking && this .isAlive ()) {
460525 while (this .getState () != State .TERMINATED ) {
0 commit comments