/* * Copyright (C) 2012-2015 DataStax Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.datastax.driver.core; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.ListenableFuture; import org.scassandra.http.client.PrimingRequest; import org.testng.annotations.Test; import java.util.Collection; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.assertj.core.api.Assertions.assertThat; import static org.scassandra.http.client.PrimingRequest.then; import static org.testng.Assert.fail; public class ConnectionReleaseTest extends ScassandraTestBase { /** * <p/> * Validates that when a future is set that the stream associated with the future's request is released. * This prevents situations where a user may not be specifying a separate executor on a callback/ * transform to a ResultSetFuture, which is not recommended, causing executeAsync to block in borrowConnection * until stream ids become available. * <p/> * Executes the following: * <p/> * <ol> * <li>Sets # of connections per host to 1.</li> * <li>Sends MAX_STREAM_PER_CONNECTION-1 requests that take 10 seconds to execute.</li> * <li>Calls executeAsync to retrieve records from test1 with k=1.</li> * <li>Transforms executeAsync to take the 'c' column from the result and query test2. * This is done without an executor to ensure the netty worker is used and has to wait for the function * completion.</li> * <li>Asserts that the transformed future completes within pool timeout and the value is as expected.</li> * </ol> * * @jira_ticket JAVA-666 * @expected_result Are able to transform a Future without hanging in executeAsync as connection should be freed * before the transform function is called. * @test_category queries:async * @since 2.0.10, 2.1.6 */ @SuppressWarnings("unchecked") @Test(groups = "short") public void should_release_connection_before_completing_future() throws Exception { Cluster cluster = null; Collection<ResultSetFuture> mockFutures = Lists.newArrayList(); try { primingClient.prime( PrimingRequest.queryBuilder() .withQuery("mock query") .withThen(then().withRows(ImmutableMap.of("key", 1)) .withFixedDelay(10000L)) .build() ); primingClient.prime( PrimingRequest.queryBuilder() .withQuery("select c from test1 where k=1") .withThen(then().withRows(ImmutableMap.of("c", "hello"))) .build() ); primingClient.prime( PrimingRequest.queryBuilder() .withQuery("select n from test2 where c='hello'") .withThen(then().withRows(ImmutableMap.of("n", "world"))) .build() ); cluster = Cluster.builder() .addContactPoints(hostAddress.getAddress()) .withPort(scassandra.getBinaryPort()) .withPoolingOptions(new PoolingOptions() .setCoreConnectionsPerHost(HostDistance.LOCAL, 1) .setMaxConnectionsPerHost(HostDistance.LOCAL, 1)) .build(); final Session session = cluster.connect("ks"); // Consume all stream ids except one. for (int i = 0; i < StreamIdGenerator.MAX_STREAM_PER_CONNECTION_V2 - 1; i++) mockFutures.add(session.executeAsync("mock query")); ListenableFuture<ResultSet> future = GuavaCompatibility.INSTANCE.transformAsync(session.executeAsync("select c from test1 where k=1"), new AsyncFunction<ResultSet, ResultSet>() { @Override public ListenableFuture<ResultSet> apply(ResultSet result) { Row row = result.one(); String c = row.getString("c"); // Execute async might hang if no streams are available. This happens if the connection // was not release. return session.executeAsync("select n from test2 where c='" + c + "'"); } }); long waitTimeInMs = 2000; try { ResultSet result = future.get(waitTimeInMs, TimeUnit.MILLISECONDS); assertThat(result.one().getString("n")).isEqualTo("world"); } catch (TimeoutException e) { fail("Future timed out after " + waitTimeInMs + "ms. " + "There is a strong possibility connection is not being released."); } } finally { // Cancel all pending requests. for (ResultSetFuture future : mockFutures) future.cancel(true); if (cluster != null) cluster.close(); } } }