2222package com .datastax .driver .core ;
2323
2424import static org .assertj .core .api .Assertions .assertThat ;
25+ import static org .mockito .Matchers .any ;
26+ import static org .mockito .Matchers .anyInt ;
27+ import static org .mockito .Matchers .anyLong ;
28+ import static org .mockito .Mockito .doReturn ;
29+ import static org .mockito .Mockito .spy ;
30+ import static org .scassandra .http .client .PrimingRequest .preparedStatementBuilder ;
31+ import static org .scassandra .http .client .PrimingRequest .queryBuilder ;
2532import static org .scassandra .http .client .PrimingRequest .then ;
33+ import static org .scassandra .http .client .Result .unprepared ;
34+ import static org .testng .Assert .fail ;
2635
36+ import com .datastax .driver .core .exceptions .ConnectionException ;
37+ import com .datastax .driver .core .exceptions .NoHostAvailableException ;
2738import com .google .common .collect .ImmutableMap ;
39+ import com .google .common .util .concurrent .Futures ;
40+ import java .nio .ByteBuffer ;
2841import java .util .Collections ;
2942import java .util .List ;
3043import java .util .Map ;
3144import java .util .concurrent .TimeUnit ;
3245import java .util .concurrent .TimeoutException ;
3346import org .scassandra .Scassandra ;
34- import org .scassandra .http .client .PrimingRequest ;
47+ import org .scassandra .http .client .UnpreparedConfig ;
3548import org .testng .annotations .Test ;
3649
3750public class RequestHandlerTest {
3851
52+ @ Test (groups = "short" )
53+ public void should_try_next_host_when_borrow_connection_future_fails () throws Exception {
54+ ScassandraCluster scassandra = ScassandraCluster .builder ().withNodes (2 ).build ();
55+ Cluster cluster = null ;
56+
57+ try {
58+ scassandra .init ();
59+ scassandra
60+ .node (2 )
61+ .primingClient ()
62+ .prime (
63+ queryBuilder ()
64+ .withQuery ("mock query" )
65+ .withThen (then ().withRows (row ("result" , "result2" )))
66+ .build ());
67+
68+ cluster = newCluster (scassandra );
69+ Session session = cluster .connect ();
70+ Host host1 = TestUtils .findHost (cluster , 1 );
71+ Host host2 = TestUtils .findHost (cluster , 2 );
72+
73+ failBorrowingFrom (session , host1 , new ConnectionException (host1 .getEndPoint (), "mock" ));
74+
75+ ResultSet rs = session .execute ("mock query" );
76+
77+ assertThat (rs .one ().getString ("result" )).isEqualTo ("result2" );
78+ assertThat (rs .getExecutionInfo ().getQueriedHost ()).isEqualTo (host2 );
79+ } finally {
80+ if (cluster != null ) cluster .close ();
81+ scassandra .stop ();
82+ }
83+ }
84+
85+ @ Test (groups = "short" )
86+ public void should_report_no_host_when_unprepared_prepare_write_fails_synchronously ()
87+ throws Exception {
88+ final Scassandra scassandra = TestUtils .createScassandraServer ();
89+ Cluster cluster = null ;
90+
91+ try {
92+ scassandra .start ();
93+ ScassandraCluster .primeSystemLocalRow (scassandra );
94+ String query = "SELECT v FROM mock_table" ;
95+ scassandra
96+ .primingClient ()
97+ .prime (
98+ preparedStatementBuilder ()
99+ .withQuery (query )
100+ .withThen (then ().withRows (row ("result" , "result1" )))
101+ .build ());
102+
103+ cluster =
104+ Cluster .builder ()
105+ .addContactPoint (TestUtils .ipOfNode (1 ))
106+ .withPort (scassandra .getBinaryPort ())
107+ .withPoolingOptions (
108+ new PoolingOptions ()
109+ .setCoreConnectionsPerHost (HostDistance .LOCAL , 1 )
110+ .setMaxConnectionsPerHost (HostDistance .LOCAL , 1 )
111+ .setHeartbeatIntervalSeconds (0 ))
112+ .build ();
113+ Session session = cluster .connect ();
114+ Host host = TestUtils .findHost (cluster , 1 );
115+ PreparedStatement prepared = session .prepare (query );
116+
117+ scassandra .primingClient ().clearPreparedPrimes ();
118+ scassandra
119+ .primingClient ()
120+ .prime (
121+ preparedStatementBuilder ()
122+ .withQuery (query )
123+ .withThen (
124+ then ()
125+ .withResult (unprepared )
126+ .withFixedDelay (200L )
127+ .withConfig (
128+ new UnpreparedConfig (
129+ prepared .getPreparedId ().boundValuesMetadata .id .toString ())))
130+ .build ());
131+
132+ Connection connection = getSingleConnection (session );
133+ ResultSetFuture future = session .executeAsync (prepared .bind ());
134+ waitForInFlight (connection );
135+ markDefunctWithoutClosing (connection );
136+
137+ try {
138+ future .getUninterruptibly (5 , TimeUnit .SECONDS );
139+ fail ("expected a NoHostAvailableException" );
140+ } catch (NoHostAvailableException e ) {
141+ assertThat (e .getErrors ()).containsKey (host .getEndPoint ());
142+ assertThat (e .getErrors ().get (host .getEndPoint ()))
143+ .isInstanceOf (ConnectionException .class )
144+ .hasMessageContaining ("Write attempt on defunct connection" );
145+ }
146+ } finally {
147+ if (cluster != null ) cluster .close ();
148+ scassandra .stop ();
149+ }
150+ }
151+
39152 @ Test (groups = "short" )
40153 public void should_handle_race_between_response_and_cancellation () {
41154 final Scassandra scassandra = TestUtils .createScassandraServer ();
@@ -50,7 +163,7 @@ public void should_handle_race_between_response_and_cancellation() {
50163 scassandra
51164 .primingClient ()
52165 .prime (
53- PrimingRequest . queryBuilder ()
166+ queryBuilder ()
54167 .withQuery ("mock query" )
55168 .withThen (then ().withRows (rows ).withFixedDelay (10L ))
56169 .build ());
@@ -100,4 +213,56 @@ private Connection getSingleConnection(Session session) {
100213 HostConnectionPool pool = ((SessionManager ) session ).pools .values ().iterator ().next ();
101214 return pool .connections [0 ].get (0 );
102215 }
216+
217+ private Cluster newCluster (ScassandraCluster scassandra ) {
218+ return Cluster .builder ()
219+ .addContactPoints (scassandra .address (1 ).getAddress ())
220+ .withPort (scassandra .getBinaryPort ())
221+ .withLoadBalancingPolicy (new SortingLoadBalancingPolicy ())
222+ .withPoolingOptions (
223+ new PoolingOptions ()
224+ .setCoreConnectionsPerHost (HostDistance .LOCAL , 1 )
225+ .setMaxConnectionsPerHost (HostDistance .LOCAL , 1 )
226+ .setHeartbeatIntervalSeconds (0 ))
227+ .build ();
228+ }
229+
230+ private void failBorrowingFrom (Session session , Host host , ConnectionException failure )
231+ throws Exception {
232+ SessionManager manager = (SessionManager ) session ;
233+ HostConnectionPool pool = spy (manager .pools .get (host ));
234+ manager .pools .put (host , pool );
235+ doReturn (Futures .<Connection >immediateFailedFuture (failure ))
236+ .when (pool )
237+ .borrowConnection (
238+ anyLong (),
239+ (TimeUnit ) any (),
240+ anyInt (),
241+ (Token .Factory ) any (),
242+ (ByteBuffer ) any (),
243+ (String ) any (),
244+ (String ) any ());
245+ }
246+
247+ private void waitForInFlight (Connection connection ) throws InterruptedException {
248+ long deadline = System .nanoTime () + TimeUnit .SECONDS .toNanos (5 );
249+ boolean observedInFlight = false ;
250+ while (System .nanoTime () < deadline ) {
251+ if (connection .inFlight .get () > 0 ) {
252+ observedInFlight = true ;
253+ break ;
254+ }
255+ Thread .sleep (1 );
256+ }
257+ assertThat (observedInFlight ).isTrue ();
258+ }
259+
260+ private void markDefunctWithoutClosing (Connection connection ) {
261+ // Keep the pending EXECUTE alive; only the following PREPARE write should see defunct.
262+ connection .markDefunctForTest ();
263+ }
264+
265+ private static List <Map <String , ?>> row (String key , String value ) {
266+ return Collections .<Map <String , ?>>singletonList (ImmutableMap .of (key , value ));
267+ }
103268}
0 commit comments