@@ -56,6 +56,7 @@ class RequestHandler implements Connection.ResponseCallback {
5656 private volatile List <Host > triedHosts ;
5757 private volatile HostConnectionPool currentPool ;
5858 private final AtomicReference <QueryState > queryStateRef ;
59+ private volatile boolean shouldCancelConnectionHandler ;
5960
6061 // This represents the number of times a retry has been triggered by the RetryPolicy (this is different from
6162 // queryStateRef.get().retryCount, because some retries don't involve the policy, for example after an
@@ -67,7 +68,6 @@ class RequestHandler implements Connection.ResponseCallback {
6768
6869 private volatile Map <InetSocketAddress , Throwable > errors ;
6970
70- private volatile boolean isCanceled ;
7171 private volatile Connection .ResponseHandler connectionHandler ;
7272
7373 private final Timer .Context timerContext ;
@@ -99,7 +99,7 @@ private Metrics metrics() {
9999
100100 public void sendRequest () {
101101 try {
102- while (queryPlan .hasNext () && ! isCanceled ) {
102+ while (queryPlan .hasNext () && queryStateRef . get () != QueryState . CANCELLED ) {
103103 Host host = queryPlan .next ();
104104 logger .trace ("Querying node {}" , host );
105105 if (query (host ))
@@ -126,7 +126,7 @@ private boolean query(Host host) {
126126 triedHosts .add (current );
127127 }
128128 current = host ;
129- connectionHandler = connection . write (this );
129+ write (connection , this );
130130 return true ;
131131 } catch (ConnectionException e ) {
132132 // If we have any problem with the connection, move to the next node.
@@ -154,6 +154,33 @@ private boolean query(Host host) {
154154 }
155155 }
156156
157+ private void write (Connection connection , Connection .ResponseCallback responseCallback ) throws ConnectionException , BusyConnectionException {
158+ // Make sure cancel() does not see a stale connectionHandler if it sees the new query state
159+ // before connection.write has completed
160+ connectionHandler = null ;
161+
162+ // Set query state to "in progress" at next iteration
163+ while (true ) {
164+ QueryState previous = queryStateRef .get ();
165+ assert !previous .inProgress ;
166+ if (previous == QueryState .CANCELLED ) {
167+ return ;
168+ }
169+ if (queryStateRef .compareAndSet (previous , previous .startNext ()))
170+ break ;
171+ }
172+
173+ connectionHandler = connection .write (responseCallback );
174+
175+ // N.B: onSet/onException could have already been called at this point
176+
177+ // If cancel was called after we set the state to "in progress", but before connection.write, it might have seen a null connectionHandler.
178+ // In this case we need to cancel it now to release the connection (because the next onSet/onException/onTimeout will see the CANCELLED
179+ // state and return immediately).
180+ if (shouldCancelConnectionHandler )
181+ connectionHandler .cancelHandler ();
182+ }
183+
157184 private void logError (InetSocketAddress address , Throwable exception ) {
158185 logger .debug ("Error querying {}, trying next host (error is: {})" , address , exception .toString ());
159186 if (errors == null )
@@ -162,15 +189,15 @@ private void logError(InetSocketAddress address, Throwable exception) {
162189 }
163190
164191 private void retry (final boolean retryCurrent , ConsistencyLevel newConsistencyLevel ) {
165- queryStateRef .set (queryStateRef .get ().startNext ());
166-
167192 final Host h = current ;
168193 this .retryConsistencyLevel = newConsistencyLevel ;
169194
170195 // We should not retry on the current thread as this will be an IO thread.
171196 manager .executor ().execute (new Runnable () {
172197 @ Override
173198 public void run () {
199+ if (queryStateRef .get () == QueryState .CANCELLED )
200+ return ;
174201 try {
175202 if (retryCurrent ) {
176203 if (query (h ))
@@ -185,9 +212,13 @@ public void run() {
185212 }
186213
187214 public void cancel () {
188- isCanceled = true ;
189- if (connectionHandler != null )
190- connectionHandler .cancelHandler ();
215+ QueryState previous = queryStateRef .getAndSet (QueryState .CANCELLED );
216+ if (previous .inProgress ) {
217+ if (connectionHandler != null )
218+ connectionHandler .cancelHandler ();
219+ else
220+ shouldCancelConnectionHandler = true ;
221+ }
191222 }
192223
193224 @ Override
@@ -388,9 +419,8 @@ public void onSet(Connection connection, Message.Response response, long latency
388419 connection .setKeyspace (prepareKeyspace );
389420 }
390421
391- queryStateRef .set (queryStateRef .get ().startNext ());
392422 try {
393- connection . write (prepareAndRetry (toPrepare .getQueryString ()));
423+ write (connection , prepareAndRetry (toPrepare .getQueryString ()));
394424 } finally {
395425 // Always reset the previous keyspace if needed
396426 if (connection .keyspace () == null || !connection .keyspace ().equals (currentKeyspace ))
@@ -583,10 +613,12 @@ interface Callback extends Connection.ResponseCallback {
583613 }
584614
585615 // This is used to prevent races between request completion (either success or error) and timeout.
586- // A retry is in progress once we have written the request to the connection and until we get back a response or a timeout.
616+ // A retry is in progress once we have written the request to the connection and until we get back a response (see onSet
617+ // or onException) or a timeout (see onTimeout).
587618 // The count increments on each retry.
588619 static class QueryState {
589- static QueryState INITIAL = new QueryState (0 , true );
620+ static final QueryState INITIAL = new QueryState (-1 , false );
621+ static final QueryState CANCELLED = new QueryState (Integer .MIN_VALUE , false );
590622
591623 final int retryCount ;
592624 final boolean inProgress ;
0 commit comments