Skip to content

Commit 6c64f6d

Browse files
committed
RUBY-233 - Only retry failed queries that are idempotent when there is a client timeout.
1 parent 487c242 commit 6c64f6d

File tree

3 files changed

+112
-39
lines changed

3 files changed

+112
-39
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ Features:
33

44
Bug Fixes:
55
* [RUBY-219](https://datastax-oss.atlassian.net/browse/RUBY-219) Sometimes get stack trace in metadata.rb due to failure in SortedSet initialization.
6-
6+
* [RUBY-231](https://datastax-oss.atlassian.net/browse/RUBY-231) Driver ignores explicitly specified nil timeout (to indicate no time limit on query execution).
7+
* [RUBY-233](https://datastax-oss.atlassian.net/browse/RUBY-233) Client timeout errors are retried for non-idempotent statements.
78

89
# 3.0.0 GA
910
Features:

lib/cassandra/cluster/client.rb

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,38 +1390,44 @@ def handle_response(response_future,
13901390
end
13911391
else
13921392
response_future.on_failure do |ex|
1393-
errors[host] = ex
1394-
case request
1395-
when Protocol::QueryRequest, Protocol::PrepareRequest
1396-
send_request_by_plan(promise,
1397-
keyspace,
1398-
statement,
1399-
options,
1400-
request,
1401-
plan,
1402-
timeout,
1403-
errors,
1404-
hosts)
1405-
when Protocol::ExecuteRequest
1406-
execute_by_plan(promise,
1407-
keyspace,
1408-
statement,
1409-
options,
1410-
request,
1411-
plan,
1412-
timeout,
1413-
errors,
1414-
hosts)
1415-
when Protocol::BatchRequest
1416-
batch_by_plan(promise,
1417-
keyspace,
1418-
statement,
1419-
options,
1420-
request,
1421-
plan,
1422-
timeout,
1423-
errors,
1424-
hosts)
1393+
if ex.is_a?(Errors::HostError) ||
1394+
(ex.is_a?(Errors::TimeoutError) && statement.idempotent?)
1395+
1396+
errors[host] = ex
1397+
case request
1398+
when Protocol::QueryRequest, Protocol::PrepareRequest
1399+
send_request_by_plan(promise,
1400+
keyspace,
1401+
statement,
1402+
options,
1403+
request,
1404+
plan,
1405+
timeout,
1406+
errors,
1407+
hosts)
1408+
when Protocol::ExecuteRequest
1409+
execute_by_plan(promise,
1410+
keyspace,
1411+
statement,
1412+
options,
1413+
request,
1414+
plan,
1415+
timeout,
1416+
errors,
1417+
hosts)
1418+
when Protocol::BatchRequest
1419+
batch_by_plan(promise,
1420+
keyspace,
1421+
statement,
1422+
options,
1423+
request,
1424+
plan,
1425+
timeout,
1426+
errors,
1427+
hosts)
1428+
else
1429+
promise.break(ex)
1430+
end
14251431
else
14261432
promise.break(ex)
14271433
end

spec/cassandra/cluster/client_spec.rb

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ class Cluster
458458
attempts << connection.host
459459
if count == 0
460460
count += 1
461-
raise Cassandra::Errors::ClientError.new
461+
raise Cassandra::Errors::InternalError.new
462462
else
463463
Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil)
464464
end
@@ -474,6 +474,72 @@ class Cluster
474474
expect(attempts).to eq(hosts)
475475
end
476476

477+
it 'does not retry on client timeout error if statement is not idempotent' do
478+
attempts = []
479+
io_reactor.on_connection do |connection|
480+
connection.handle_request do |request|
481+
case request
482+
when Cassandra::Protocol::OptionsRequest
483+
Cassandra::Protocol::SupportedResponse.new({})
484+
when Cassandra::Protocol::StartupRequest
485+
Cassandra::Protocol::ReadyResponse.new
486+
when Cassandra::Protocol::QueryRequest
487+
case request.cql
488+
when 'SELECT * FROM songs'
489+
attempts << connection.host
490+
raise Cassandra::Errors::TimeoutError.new
491+
else
492+
Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil)
493+
end
494+
end
495+
end
496+
end
497+
client.connect.value
498+
future = client.query(Statements::Simple.new('SELECT * FROM songs'),
499+
Execution::Options.new(:consistency => :one))
500+
got_excp = false
501+
future.on_failure do |ex|
502+
got_excp = true
503+
expect(ex).to be_a(Cassandra::Errors::TimeoutError)
504+
end
505+
expect(got_excp)
506+
expect(attempts).to have(1).items
507+
expect(attempts.first).to eq(hosts.first)
508+
end
509+
510+
it 'retries on client timeout error if statement is idempotent' do
511+
count = 0
512+
attempts = []
513+
io_reactor.on_connection do |connection|
514+
connection.handle_request do |request|
515+
case request
516+
when Cassandra::Protocol::OptionsRequest
517+
Cassandra::Protocol::SupportedResponse.new({})
518+
when Cassandra::Protocol::StartupRequest
519+
Cassandra::Protocol::ReadyResponse.new
520+
when Cassandra::Protocol::QueryRequest
521+
case request.cql
522+
when 'SELECT * FROM songs'
523+
attempts << connection.host
524+
if count == 0
525+
count += 1
526+
raise raise Cassandra::Errors::TimeoutError.new
527+
else
528+
Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil)
529+
end
530+
else
531+
Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil)
532+
end
533+
end
534+
end
535+
end
536+
client.connect.value
537+
client.query(Statements::Simple.new('SELECT * FROM songs', nil, nil, true),
538+
Execution::Options.new(:consistency => :one)).get
539+
expect(attempts).to have(2).items
540+
expect(attempts).to eq(hosts)
541+
end
542+
477543
it 'raises if all hosts failed' do
478544
io_reactor.on_connection do |connection|
479545
connection.handle_request do |request|
@@ -485,7 +551,7 @@ class Cluster
485551
when Cassandra::Protocol::QueryRequest
486552
case request.cql
487553
when 'SELECT * FROM songs'
488-
raise Cassandra::Errors::ClientError.new
554+
raise Cassandra::Errors::InternalError.new
489555
else
490556
Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil)
491557
end
@@ -692,7 +758,7 @@ class Cluster
692758
attempts << connection.host
693759
if count == 0
694760
count += 1
695-
raise Cassandra::Errors::ClientError.new
761+
raise Cassandra::Errors::InternalError.new
696762
end
697763
Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil)
698764
end
@@ -769,7 +835,7 @@ class Cluster
769835
when Cassandra::Protocol::PrepareRequest
770836
Protocol::PreparedResultResponse.new(nil, nil, 123, [], [], nil, nil)
771837
when Cassandra::Protocol::ExecuteRequest
772-
raise Cassandra::Errors::ClientError.new
838+
raise Cassandra::Errors::InternalError.new
773839
end
774840
end
775841
end
@@ -918,7 +984,7 @@ class Cluster
918984
attempts << connection.host
919985
if count == 0
920986
count += 1
921-
raise Cassandra::Errors::ClientError.new
987+
raise Cassandra::Errors::InternalError.new
922988
end
923989
Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil)
924990
end
@@ -943,7 +1009,7 @@ class Cluster
9431009
when Cassandra::Protocol::StartupRequest
9441010
Cassandra::Protocol::ReadyResponse.new
9451011
when Cassandra::Protocol::BatchRequest
946-
raise Cassandra::Errors::ClientError.new
1012+
raise Cassandra::Errors::InternalError.new
9471013
end
9481014
end
9491015
end

0 commit comments

Comments
 (0)