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 .doThrow ;
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 java .lang .reflect .Field ;
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 ;
46+ import java .util .concurrent .atomic .AtomicBoolean ;
3347import org .scassandra .Scassandra ;
34- import org .scassandra .http .client .PrimingRequest ;
48+ import org .scassandra .http .client .UnpreparedConfig ;
3549import org .testng .annotations .Test ;
3650
3751public class RequestHandlerTest {
3852
53+ @ Test (groups = "short" )
54+ public void should_try_next_host_when_borrow_connection_fails_synchronously () throws Exception {
55+ ScassandraCluster scassandra = ScassandraCluster .builder ().withNodes (2 ).build ();
56+ Cluster cluster = null ;
57+
58+ try {
59+ scassandra .init ();
60+ scassandra
61+ .node (2 )
62+ .primingClient ()
63+ .prime (
64+ queryBuilder ()
65+ .withQuery ("mock query" )
66+ .withThen (then ().withRows (row ("result" , "result2" )))
67+ .build ());
68+
69+ cluster = newCluster (scassandra );
70+ Session session = cluster .connect ();
71+ Host host1 = TestUtils .findHost (cluster , 1 );
72+ Host host2 = TestUtils .findHost (cluster , 2 );
73+
74+ failBorrowingFrom (session , host1 , new ConnectionException (host1 .getEndPoint (), "mock" ));
75+
76+ ResultSet rs = session .execute ("mock query" );
77+
78+ assertThat (rs .one ().getString ("result" )).isEqualTo ("result2" );
79+ assertThat (rs .getExecutionInfo ().getQueriedHost ()).isEqualTo (host2 );
80+ } finally {
81+ if (cluster != null ) cluster .close ();
82+ scassandra .stop ();
83+ }
84+ }
85+
86+ @ Test (groups = "short" )
87+ public void should_report_no_host_when_unprepared_prepare_write_fails_synchronously ()
88+ throws Exception {
89+ final Scassandra scassandra = TestUtils .createScassandraServer ();
90+ Cluster cluster = null ;
91+
92+ try {
93+ scassandra .start ();
94+ ScassandraCluster .primeSystemLocalRow (scassandra );
95+ String query = "SELECT v FROM mock_table" ;
96+ scassandra
97+ .primingClient ()
98+ .prime (
99+ preparedStatementBuilder ()
100+ .withQuery (query )
101+ .withThen (then ().withRows (row ("result" , "result1" )))
102+ .build ());
103+
104+ cluster =
105+ Cluster .builder ()
106+ .addContactPoint (TestUtils .ipOfNode (1 ))
107+ .withPort (scassandra .getBinaryPort ())
108+ .withPoolingOptions (
109+ new PoolingOptions ()
110+ .setCoreConnectionsPerHost (HostDistance .LOCAL , 1 )
111+ .setMaxConnectionsPerHost (HostDistance .LOCAL , 1 )
112+ .setHeartbeatIntervalSeconds (0 ))
113+ .build ();
114+ Session session = cluster .connect ();
115+ Host host = TestUtils .findHost (cluster , 1 );
116+ PreparedStatement prepared = session .prepare (query );
117+
118+ scassandra .primingClient ().clearPreparedPrimes ();
119+ scassandra
120+ .primingClient ()
121+ .prime (
122+ preparedStatementBuilder ()
123+ .withQuery (query )
124+ .withThen (
125+ then ()
126+ .withResult (unprepared )
127+ .withFixedDelay (200L )
128+ .withConfig (
129+ new UnpreparedConfig (
130+ prepared .getPreparedId ().boundValuesMetadata .id .toString ())))
131+ .build ());
132+
133+ Connection connection = getSingleConnection (session );
134+ ResultSetFuture future = session .executeAsync (prepared .bind ());
135+ waitForInFlight (connection );
136+ markDefunctWithoutClosing (connection );
137+
138+ try {
139+ future .getUninterruptibly (5 , TimeUnit .SECONDS );
140+ fail ("expected a NoHostAvailableException" );
141+ } catch (NoHostAvailableException e ) {
142+ assertThat (e .getErrors ()).containsKey (host .getEndPoint ());
143+ assertThat (e .getErrors ().get (host .getEndPoint ()))
144+ .isInstanceOf (ConnectionException .class )
145+ .hasMessageContaining ("Write attempt on defunct connection" );
146+ }
147+ } finally {
148+ if (cluster != null ) cluster .close ();
149+ scassandra .stop ();
150+ }
151+ }
152+
39153 @ Test (groups = "short" )
40154 public void should_handle_race_between_response_and_cancellation () {
41155 final Scassandra scassandra = TestUtils .createScassandraServer ();
@@ -50,7 +164,7 @@ public void should_handle_race_between_response_and_cancellation() {
50164 scassandra
51165 .primingClient ()
52166 .prime (
53- PrimingRequest . queryBuilder ()
167+ queryBuilder ()
54168 .withQuery ("mock query" )
55169 .withThen (then ().withRows (rows ).withFixedDelay (10L ))
56170 .build ());
@@ -100,4 +214,53 @@ private Connection getSingleConnection(Session session) {
100214 HostConnectionPool pool = ((SessionManager ) session ).pools .values ().iterator ().next ();
101215 return pool .connections [0 ].get (0 );
102216 }
217+
218+ private Cluster newCluster (ScassandraCluster scassandra ) {
219+ return Cluster .builder ()
220+ .addContactPoints (scassandra .address (1 ).getAddress ())
221+ .withPort (scassandra .getBinaryPort ())
222+ .withLoadBalancingPolicy (new SortingLoadBalancingPolicy ())
223+ .withPoolingOptions (
224+ new PoolingOptions ()
225+ .setCoreConnectionsPerHost (HostDistance .LOCAL , 1 )
226+ .setMaxConnectionsPerHost (HostDistance .LOCAL , 1 )
227+ .setHeartbeatIntervalSeconds (0 ))
228+ .build ();
229+ }
230+
231+ private void failBorrowingFrom (Session session , Host host , ConnectionException failure )
232+ throws Exception {
233+ SessionManager manager = (SessionManager ) session ;
234+ HostConnectionPool pool = spy (manager .pools .get (host ));
235+ manager .pools .put (host , pool );
236+ doThrow (failure )
237+ .when (pool )
238+ .borrowConnection (
239+ anyLong (),
240+ (TimeUnit ) any (),
241+ anyInt (),
242+ (Token .Factory ) any (),
243+ (ByteBuffer ) any (),
244+ (String ) any (),
245+ (String ) any ());
246+ }
247+
248+ private void waitForInFlight (Connection connection ) throws InterruptedException {
249+ long deadline = System .nanoTime () + TimeUnit .SECONDS .toNanos (5 );
250+ while (connection .inFlight .get () == 0 && System .nanoTime () < deadline ) {
251+ Thread .sleep (1 );
252+ }
253+ assertThat (connection .inFlight .get ()).isGreaterThan (0 );
254+ }
255+
256+ private void markDefunctWithoutClosing (Connection connection ) throws Exception {
257+ // Keep the pending EXECUTE alive; only the following PREPARE write should see defunct.
258+ Field isDefunct = Connection .class .getDeclaredField ("isDefunct" );
259+ isDefunct .setAccessible (true );
260+ ((AtomicBoolean ) isDefunct .get (connection )).set (true );
261+ }
262+
263+ private static List <Map <String , ?>> row (String key , String value ) {
264+ return Collections .<Map <String , ?>>singletonList (ImmutableMap .of (key , value ));
265+ }
103266}
0 commit comments