|
50 | 50 | import com.datastax.driver.core.exceptions.BusyPoolException; |
51 | 51 | import com.datastax.driver.core.exceptions.ConnectionException; |
52 | 52 | import com.datastax.driver.core.exceptions.DriverException; |
| 53 | +import com.datastax.driver.core.exceptions.InvalidQueryException; |
53 | 54 | import com.datastax.driver.core.exceptions.NoHostAvailableException; |
54 | 55 | import com.datastax.driver.core.policies.ConstantReconnectionPolicy; |
55 | 56 | import com.google.common.base.Function; |
|
62 | 63 | import com.google.common.util.concurrent.ListeningExecutorService; |
63 | 64 | import com.google.common.util.concurrent.MoreExecutors; |
64 | 65 | import com.google.common.util.concurrent.Uninterruptibles; |
| 66 | +import io.netty.buffer.ByteBuf; |
| 67 | +import io.netty.buffer.Unpooled; |
| 68 | +import io.netty.util.CharsetUtil; |
65 | 69 | import java.net.InetSocketAddress; |
66 | 70 | import java.nio.ByteBuffer; |
67 | 71 | import java.util.Collection; |
@@ -134,6 +138,16 @@ private void assertBorrowedConnection( |
134 | 138 | assertBorrowedConnections(requests, Collections.singletonList(expectedConnection)); |
135 | 139 | } |
136 | 140 |
|
| 141 | + private static Responses.Error errorResponse(ExceptionCode code, String message) { |
| 142 | + ByteBuf body = Unpooled.buffer(); |
| 143 | + body.writeInt(code.value); |
| 144 | + byte[] messageBytes = message.getBytes(CharsetUtil.UTF_8); |
| 145 | + body.writeShort(messageBytes.length); |
| 146 | + body.writeBytes(messageBytes); |
| 147 | + return Responses.Error.decoder.decode( |
| 148 | + body, ProtocolVersion.V4, CodecRegistry.DEFAULT_INSTANCE, ProtocolFeatureStore.EMPTY); |
| 149 | + } |
| 150 | + |
137 | 151 | /** |
138 | 152 | * Ensures that if a fixed-sized pool has filled its core connections and reached its maximum |
139 | 153 | * number of enqueued requests, then borrowConnection will fail instead of creating a new |
@@ -393,6 +407,158 @@ public void should_adjust_connection_keyspace_on_dequeue_if_pool_state_is_differ |
393 | 407 | } |
394 | 408 | } |
395 | 409 |
|
| 410 | + /** |
| 411 | + * Ensures that if keyspace setup fails while borrowing a connection, the borrow future fails and |
| 412 | + * pool accounting is restored. |
| 413 | + * |
| 414 | + * @test_category connection:connection_pool |
| 415 | + */ |
| 416 | + @Test(groups = "short") |
| 417 | + public void should_restore_pool_accounting_when_keyspace_setup_fails_on_borrow() |
| 418 | + throws Exception { |
| 419 | + Cluster cluster = createClusterBuilder().build(); |
| 420 | + try { |
| 421 | + HostConnectionPool pool = createPool(cluster, 1, 1); |
| 422 | + Connection connection = spy(pool.connections[0].get(0)); |
| 423 | + pool.connections[0].set(0, connection); |
| 424 | + |
| 425 | + pool.manager.poolsState.setKeyspace("newkeyspace"); |
| 426 | + ConnectionException failure = |
| 427 | + new ConnectionException(pool.host.getEndPoint(), "Write attempt on defunct connection"); |
| 428 | + doReturn(Futures.<Connection>immediateFailedFuture(failure)) |
| 429 | + .when(connection) |
| 430 | + .setKeyspaceAsync("newkeyspace"); |
| 431 | + |
| 432 | + MockRequest request = MockRequest.send(pool); |
| 433 | + |
| 434 | + try { |
| 435 | + Uninterruptibles.getUninterruptibly(request.connectionFuture, 5, TimeUnit.SECONDS); |
| 436 | + fail("Should have failed to borrow connection"); |
| 437 | + } catch (ExecutionException e) { |
| 438 | + assertThat(e.getCause()).isSameAs(failure); |
| 439 | + } |
| 440 | + assertThat(connection.inFlight.get()).isEqualTo(0); |
| 441 | + assertThat(pool.totalInFlight.get()).isEqualTo(0); |
| 442 | + } finally { |
| 443 | + cluster.close(); |
| 444 | + } |
| 445 | + } |
| 446 | + |
| 447 | + /** |
| 448 | + * Ensures that a synchronous write failure while setting the keyspace is reported through the |
| 449 | + * returned future and does not leave the failed keyspace attempt in-flight. |
| 450 | + * |
| 451 | + * @test_category connection:connection_pool |
| 452 | + */ |
| 453 | + @Test(groups = "short") |
| 454 | + public void should_fail_keyspace_future_and_clear_attempt_when_connection_is_defunct() |
| 455 | + throws Exception { |
| 456 | + Cluster cluster = createClusterBuilder().build(); |
| 457 | + try { |
| 458 | + HostConnectionPool pool = createPool(cluster, 1, 1); |
| 459 | + Connection connection = pool.connections[0].get(0); |
| 460 | + |
| 461 | + connection.defunct( |
| 462 | + new ConnectionException(pool.host.getEndPoint(), "Test defunct connection")); |
| 463 | + |
| 464 | + ListenableFuture<Connection> firstAttempt = connection.setKeyspaceAsync("newkeyspace"); |
| 465 | + ListenableFuture<Connection> secondAttempt = connection.setKeyspaceAsync("newkeyspace"); |
| 466 | + |
| 467 | + assertThat(firstAttempt).isNotSameAs(secondAttempt); |
| 468 | + try { |
| 469 | + Uninterruptibles.getUninterruptibly(firstAttempt, 5, TimeUnit.SECONDS); |
| 470 | + fail("Should have failed keyspace attempt"); |
| 471 | + } catch (ExecutionException e) { |
| 472 | + assertThat(e.getCause()) |
| 473 | + .isInstanceOf(ConnectionException.class) |
| 474 | + .hasMessageContaining("Write attempt on defunct connection"); |
| 475 | + } |
| 476 | + try { |
| 477 | + Uninterruptibles.getUninterruptibly(secondAttempt, 5, TimeUnit.SECONDS); |
| 478 | + fail("Should have failed keyspace attempt"); |
| 479 | + } catch (ExecutionException e) { |
| 480 | + assertThat(e.getCause()) |
| 481 | + .isInstanceOf(ConnectionException.class) |
| 482 | + .hasMessageContaining("Write attempt on defunct connection"); |
| 483 | + } |
| 484 | + } finally { |
| 485 | + cluster.close(); |
| 486 | + } |
| 487 | + } |
| 488 | + |
| 489 | + /** |
| 490 | + * Ensures that keyspace validation errors are surfaced as query errors and do not defunct the |
| 491 | + * connection. |
| 492 | + * |
| 493 | + * @test_category connection:connection_pool |
| 494 | + */ |
| 495 | + @Test(groups = "short") |
| 496 | + public void should_not_defunct_connection_when_keyspace_setup_fails_with_validation_error() |
| 497 | + throws Exception { |
| 498 | + Cluster cluster = createClusterBuilder().build(); |
| 499 | + try { |
| 500 | + HostConnectionPool pool = createPool(cluster, 1, 1); |
| 501 | + Connection connection = spy(pool.connections[0].get(0)); |
| 502 | + pool.connections[0].set(0, connection); |
| 503 | + SettableConnectionFuture useFuture = |
| 504 | + new SettableConnectionFuture(new Requests.Query("USE \"missingkeyspace\"")); |
| 505 | + |
| 506 | + doReturn(useFuture).when(connection).write(any(Message.Request.class)); |
| 507 | + ListenableFuture<Connection> keyspaceFuture = connection.setKeyspaceAsync("missingkeyspace"); |
| 508 | + useFuture.setResponse( |
| 509 | + errorResponse(ExceptionCode.INVALID, "Keyspace missingkeyspace does not exist")); |
| 510 | + |
| 511 | + try { |
| 512 | + Uninterruptibles.getUninterruptibly(keyspaceFuture, 5, TimeUnit.SECONDS); |
| 513 | + fail("Should have failed keyspace attempt"); |
| 514 | + } catch (ExecutionException e) { |
| 515 | + assertThat(e.getCause()).isInstanceOf(InvalidQueryException.class); |
| 516 | + } |
| 517 | + assertThat(connection.isDefunct()).isFalse(); |
| 518 | + assertThat(pool.opened()).isEqualTo(1); |
| 519 | + } finally { |
| 520 | + cluster.close(); |
| 521 | + } |
| 522 | + } |
| 523 | + |
| 524 | + /** |
| 525 | + * Ensures that permanent keyspace setup failures fail the request directly instead of being |
| 526 | + * retried across hosts as connection failures. |
| 527 | + * |
| 528 | + * @test_category connection:connection_pool |
| 529 | + */ |
| 530 | + @Test(groups = "short") |
| 531 | + public void should_fail_fast_when_keyspace_setup_fails_with_validation_error() throws Exception { |
| 532 | + Cluster cluster = createClusterBuilder().build(); |
| 533 | + try { |
| 534 | + SessionManager session = (SessionManager) cluster.connect(); |
| 535 | + Host host = TestUtils.findHost(cluster, 1); |
| 536 | + HostConnectionPool pool = session.pools.get(host); |
| 537 | + Connection connection = spy(pool.connections[0].get(0)); |
| 538 | + pool.connections[0].set(0, connection); |
| 539 | + InvalidQueryException failure = |
| 540 | + new InvalidQueryException( |
| 541 | + pool.host.getEndPoint(), "Keyspace missingkeyspace does not exist"); |
| 542 | + session.poolsState.setKeyspace("missingkeyspace"); |
| 543 | + doReturn(Futures.<Connection>immediateFailedFuture(failure)) |
| 544 | + .when(connection) |
| 545 | + .setKeyspaceAsync("missingkeyspace"); |
| 546 | + activityClient.clearAllRecordedActivity(); |
| 547 | + |
| 548 | + try { |
| 549 | + session.execute("select * from tbl"); |
| 550 | + fail("Should have failed the request with the keyspace validation error"); |
| 551 | + } catch (InvalidQueryException e) { |
| 552 | + // expected |
| 553 | + } |
| 554 | + assertThat(connection.isDefunct()).isFalse(); |
| 555 | + assertThat(pool.opened()).isEqualTo(1); |
| 556 | + assertThat(activityClient.retrieveQueries()).extractingResultOf("getQuery").isEmpty(); |
| 557 | + } finally { |
| 558 | + cluster.close(); |
| 559 | + } |
| 560 | + } |
| 561 | + |
396 | 562 | /** |
397 | 563 | * Ensures that on borrowConnection if a set keyspace attempt is in progress on that connection |
398 | 564 | * for a different keyspace than the pool state that the borrowConnection future returned is |
@@ -501,6 +667,9 @@ public void should_adjust_connection_keyspace_on_dequeue_if_pool_state_is_differ |
501 | 667 | .contains( |
502 | 668 | "Aborting attempt to set keyspace to 'newkeyspace' since there is already an in flight attempt to set keyspace to 'slowks'."); |
503 | 669 | } |
| 670 | + assertThat(connection.inFlight.get()).isEqualTo(0); |
| 671 | + assertThat(pool.totalInFlight.get()).isEqualTo(0); |
| 672 | + assertThat(pool.pendingBorrowCount.get()).isEqualTo(0); |
504 | 673 | } finally { |
505 | 674 | MockRequest.completeAll(requests); |
506 | 675 | cluster.close(); |
@@ -1743,4 +1912,14 @@ public void onFailure(Throwable t) { |
1743 | 1912 | return future; |
1744 | 1913 | } |
1745 | 1914 | } |
| 1915 | + |
| 1916 | + private static class SettableConnectionFuture extends Connection.Future { |
| 1917 | + SettableConnectionFuture(Message.Request request) { |
| 1918 | + super(request); |
| 1919 | + } |
| 1920 | + |
| 1921 | + void setResponse(Message.Response response) { |
| 1922 | + super.set(response); |
| 1923 | + } |
| 1924 | + } |
1746 | 1925 | } |
0 commit comments