cassandra-driver 1.0.0.beta.3-java → 1.0.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -14
  3. data/lib/cassandra.rb +164 -78
  4. data/lib/cassandra/address_resolution.rb +36 -0
  5. data/lib/cassandra/address_resolution/policies.rb +2 -0
  6. data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +56 -0
  7. data/lib/cassandra/address_resolution/policies/none.rb +35 -0
  8. data/lib/cassandra/auth.rb +1 -1
  9. data/lib/cassandra/auth/providers/password.rb +1 -1
  10. data/lib/cassandra/cluster.rb +18 -5
  11. data/lib/cassandra/cluster/client.rb +175 -101
  12. data/lib/cassandra/{client/connection_manager.rb → cluster/connection_pool.rb} +5 -5
  13. data/lib/cassandra/cluster/connector.rb +142 -56
  14. data/lib/cassandra/cluster/control_connection.rb +385 -134
  15. data/lib/cassandra/{client/null_logger.rb → cluster/failed_connection.rb} +12 -14
  16. data/lib/cassandra/cluster/options.rb +13 -2
  17. data/lib/cassandra/cluster/registry.rb +19 -9
  18. data/lib/cassandra/column.rb +5 -0
  19. data/lib/cassandra/compression.rb +1 -1
  20. data/lib/cassandra/compression/compressors/lz4.rb +1 -1
  21. data/lib/cassandra/compression/compressors/snappy.rb +1 -1
  22. data/lib/cassandra/driver.rb +29 -21
  23. data/lib/cassandra/errors.rb +325 -35
  24. data/lib/cassandra/execution/options.rb +13 -6
  25. data/lib/cassandra/execution/trace.rb +4 -4
  26. data/lib/cassandra/future.rb +7 -3
  27. data/lib/cassandra/keyspace.rb +5 -0
  28. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +13 -4
  29. data/lib/cassandra/load_balancing/policies/token_aware.rb +2 -4
  30. data/lib/cassandra/load_balancing/policies/white_list.rb +3 -6
  31. data/lib/cassandra/null_logger.rb +35 -0
  32. data/lib/cassandra/protocol.rb +0 -16
  33. data/lib/cassandra/protocol/cql_byte_buffer.rb +18 -18
  34. data/lib/cassandra/protocol/cql_protocol_handler.rb +78 -8
  35. data/lib/cassandra/protocol/frame_decoder.rb +2 -2
  36. data/lib/cassandra/protocol/frame_encoder.rb +1 -1
  37. data/lib/cassandra/protocol/requests/query_request.rb +1 -11
  38. data/lib/cassandra/protocol/response.rb +1 -1
  39. data/lib/cassandra/protocol/responses/detailed_error_response.rb +16 -1
  40. data/lib/cassandra/protocol/responses/error_response.rb +17 -0
  41. data/lib/cassandra/protocol/responses/event_response.rb +1 -1
  42. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +1 -1
  43. data/lib/cassandra/protocol/responses/result_response.rb +1 -1
  44. data/lib/cassandra/protocol/responses/rows_result_response.rb +1 -1
  45. data/lib/cassandra/protocol/type_converter.rb +4 -3
  46. data/lib/cassandra/reconnection.rb +1 -1
  47. data/lib/cassandra/result.rb +4 -6
  48. data/lib/cassandra/retry.rb +3 -5
  49. data/lib/cassandra/session.rb +14 -5
  50. data/lib/cassandra/statements/prepared.rb +5 -1
  51. data/lib/cassandra/table.rb +6 -1
  52. data/lib/cassandra/time_uuid.rb +21 -83
  53. data/lib/cassandra/util.rb +131 -1
  54. data/lib/cassandra/uuid.rb +6 -4
  55. data/lib/cassandra/uuid/generator.rb +207 -0
  56. data/lib/cassandra/version.rb +1 -1
  57. data/lib/cassandra_murmur3.jar +0 -0
  58. metadata +43 -49
  59. data/lib/cassandra/client.rb +0 -144
  60. data/lib/cassandra/client/batch.rb +0 -212
  61. data/lib/cassandra/client/client.rb +0 -591
  62. data/lib/cassandra/client/column_metadata.rb +0 -54
  63. data/lib/cassandra/client/connector.rb +0 -277
  64. data/lib/cassandra/client/execute_options_decoder.rb +0 -59
  65. data/lib/cassandra/client/peer_discovery.rb +0 -50
  66. data/lib/cassandra/client/prepared_statement.rb +0 -314
  67. data/lib/cassandra/client/query_result.rb +0 -230
  68. data/lib/cassandra/client/request_runner.rb +0 -71
  69. data/lib/cassandra/client/result_metadata.rb +0 -48
  70. data/lib/cassandra/client/void_result.rb +0 -78
@@ -39,14 +39,21 @@ module Cassandra
39
39
  timeout = options[:timeout]
40
40
  serial_consistency = options[:serial_consistency]
41
41
 
42
- raise ::ArgumentError, ":consistency must be one of #{CONSISTENCIES.inspect}, #{consistency.inspect} given" unless CONSISTENCIES.include?(consistency)
43
- raise ::ArgumentError, ":serial_consistency must be one of #{SERIAL_CONSISTENCIES.inspect}, #{serial_consistency.inspect} given" if serial_consistency && !SERIAL_CONSISTENCIES.include?(serial_consistency)
42
+ Util.assert_one_of(CONSISTENCIES, consistency) { ":consistency must be one of #{CONSISTENCIES.inspect}, #{consistency.inspect} given" }
44
43
 
45
- page_size = page_size && Integer(page_size)
46
- timeout = timeout && Integer(timeout)
44
+ unless serial_consistency.nil?
45
+ Util.assert_one_of(SERIAL_CONSISTENCIES, serial_consistency) { ":serial_consistency must be one of #{SERIAL_CONSISTENCIES.inspect}, #{serial_consistency.inspect} given" }
46
+ end
47
47
 
48
- raise ::ArgumentError, ":page_size must be greater than 0, #{page_size.inspect} given" if page_size && page_size <= 0
49
- raise ::ArgumentError, ":timeout must be greater than 0, #{timeout.inspect} given" if timeout && timeout <= 0
48
+ unless page_size.nil?
49
+ page_size = options[:page_size] = Integer(page_size)
50
+ Util.assert(page_size > 0) { ":page_size must be a positive integer, #{page_size.inspect} given" }
51
+ end
52
+
53
+ unless timeout.nil?
54
+ Util.assert_instance_of(::Numeric, timeout) { ":timeout must be a number of seconds, #{timeout} given" }
55
+ Util.assert(timeout > 0) { ":timeout must be greater than 0, #{timeout} given" }
56
+ end
50
57
 
51
58
  @consistency = consistency
52
59
  @page_size = page_size
@@ -108,16 +108,16 @@ module Cassandra
108
108
  private
109
109
 
110
110
  # @private
111
- SELECT_SESSION = "SELECT * FROM system_traces.sessions WHERE session_id = ?"
111
+ SELECT_SESSION = "SELECT * FROM system_traces.sessions WHERE session_id = %s"
112
112
  # @private
113
- SELECT_EVENTS = "SELECT * FROM system_traces.events WHERE session_id = ?"
113
+ SELECT_EVENTS = "SELECT * FROM system_traces.events WHERE session_id = %s"
114
114
 
115
115
  # @private
116
116
  def load
117
117
  synchronize do
118
118
  return if @loaded
119
119
 
120
- data = @client.query(Statements::Simple.new(SELECT_SESSION, @id), VOID_OPTIONS).get.first
120
+ data = @client.query(Statements::Simple.new(SELECT_SESSION % @id), VOID_OPTIONS).get.first
121
121
  raise ::RuntimeError, "unable to load trace #{@id}" if data.nil?
122
122
 
123
123
  @coordinator = data['coordinator']
@@ -138,7 +138,7 @@ module Cassandra
138
138
 
139
139
  @events = []
140
140
 
141
- @client.query(Statements::Simple.new(SELECT_EVENTS, @id), VOID_OPTIONS).get.each do |row|
141
+ @client.query(Statements::Simple.new(SELECT_EVENTS % @id), VOID_OPTIONS).get.each do |row|
142
142
  @events << Event.new(row['event_id'], row['activity'], row['source'], row['source_elapsed'], row['thread'])
143
143
  end
144
144
 
@@ -48,6 +48,8 @@ module Cassandra
48
48
  def initialize(error)
49
49
  raise ::ArgumentError, "error must be an exception, #{error.inspect} given" unless error.is_a?(::Exception)
50
50
 
51
+ error.set_backtrace(caller) unless error.backtrace
52
+
51
53
  @error = error
52
54
  end
53
55
 
@@ -188,7 +190,7 @@ module Cassandra
188
190
  monitor = Monitor.new
189
191
  promise = Promise.new
190
192
  remaining = futures.length
191
- values = Array.new(length)
193
+ values = Array.new(remaining)
192
194
 
193
195
  futures.each_with_index do |future, i|
194
196
  future.on_complete do |v, e|
@@ -390,11 +392,11 @@ module Cassandra
390
392
  end
391
393
 
392
394
  def success(value)
393
- @block.call(nil, value)
395
+ @block.call(value, nil)
394
396
  end
395
397
 
396
398
  def failure(error)
397
- @block.call(error, nil)
399
+ @block.call(nil, error)
398
400
  end
399
401
  end
400
402
 
@@ -471,6 +473,8 @@ module Cassandra
471
473
  raise ::ArgumentError, "error must be an exception, #{error.inspect} given"
472
474
  end
473
475
 
476
+ error.set_backtrace(caller) unless error.backtrace
477
+
474
478
  return unless @state == :pending
475
479
 
476
480
  listeners = nil
@@ -106,6 +106,11 @@ module Cassandra
106
106
  end
107
107
  alias :== :eql?
108
108
 
109
+ # @return [String] a CLI-friendly keyspace representation
110
+ def inspect
111
+ "#<#{self.class.name}:0x#{self.object_id.to_s(16)} @name=#{@name}>"
112
+ end
113
+
109
114
  # @private
110
115
  def update_table(table)
111
116
  tables = @tables.dup
@@ -56,12 +56,17 @@ module Cassandra
56
56
 
57
57
  include MonitorMixin
58
58
 
59
- def initialize(datacenter, max_remote_hosts_to_use = nil, use_remote_hosts_for_local_consistency = false)
60
- datacenter = String(datacenter)
59
+ def initialize(datacenter = nil, max_remote_hosts_to_use = nil, use_remote_hosts_for_local_consistency = false)
60
+ datacenter = datacenter && String(datacenter)
61
61
  max_remote_hosts_to_use = max_remote_hosts_to_use && Integer(max_remote_hosts_to_use)
62
62
 
63
- raise ::ArgumentError, "datacenter cannot be nil" if datacenter.nil?
64
- raise ::ArgumentError, "max_remote_hosts_to_use must be nil or >= 0" if max_remote_hosts_to_use && max_remote_hosts_to_use < 0
63
+ unless datacenter.nil?
64
+ Util.assert_not_empty(datacenter) { "datacenter cannot be empty" }
65
+ end
66
+
67
+ unless max_remote_hosts_to_use.nil?
68
+ Util.assert(max_remote_hosts_to_use >= 0) { "max_remote_hosts_to_use must be nil or >= 0" }
69
+ end
65
70
 
66
71
  @datacenter = datacenter
67
72
  @max_remote = max_remote_hosts_to_use
@@ -75,6 +80,10 @@ module Cassandra
75
80
  end
76
81
 
77
82
  def host_up(host)
83
+ if !@datacenter && host.datacenter
84
+ @datacenter = host.datacenter
85
+ end
86
+
78
87
  if host.datacenter.nil? || host.datacenter == @datacenter
79
88
  synchronize { @local = @local.dup.push(host) }
80
89
  else
@@ -92,9 +92,7 @@ module Cassandra
92
92
  def initialize(wrapped_policy)
93
93
  methods = [:host_up, :host_down, :host_found, :host_lost, :distance, :plan]
94
94
 
95
- unless methods.all? {|method| wrapped_policy.respond_to?(method)}
96
- raise ::ArgumentError, "supplied policy must be a Cassandra::LoadBalancing::Policy, #{wrapped_policy.inspect} given"
97
- end
95
+ Util.assert_responds_to_all(methods, wrapped_policy) { "supplied policy must respond to #{methods.inspect}, but doesn't" }
98
96
 
99
97
  @policy = wrapped_policy
100
98
  end
@@ -111,7 +109,7 @@ module Cassandra
111
109
  replicas = @cluster.find_replicas(keyspace, statement)
112
110
  return @policy.plan(keyspace, statement, options) if replicas.empty?
113
111
 
114
- Plan.new(replicas.dup, @policy, keyspace, statement, options)
112
+ Plan.new(replicas.shuffle, @policy, keyspace, statement, options)
115
113
  end
116
114
  end
117
115
  end
@@ -35,12 +35,9 @@ module Cassandra
35
35
  # @param wrapped_policy [Cassandra::LoadBalancing::Policy] actual policy to filter
36
36
  # @raise [ArgumentError] if arguments are of unexpected types
37
37
  def initialize(ips, wrapped_policy)
38
- raise ::ArgumentError, "ips must be enumerable" unless ips.respond_to?(:each)
38
+ Util.assert_instance_of(::Enumerable, ips) { "ips must be an Enumerable, #{ips.inspect} given" }
39
39
  methods = [:host_up, :host_down, :host_found, :host_lost, :distance, :plan]
40
-
41
- unless methods.all? {|method| wrapped_policy.respond_to?(method)}
42
- raise ::ArgumentError, "supplied policy must be a Cassandra::LoadBalancing::Policy, #{wrapped_policy.inspect} given"
43
- end
40
+ Util.assert_responds_to_all(methods, wrapped_policy) { "supplied policy must respond to #{methods.inspect}, but doesn't" }
44
41
 
45
42
  @ips = ::Set.new
46
43
  @policy = wrapped_policy
@@ -52,7 +49,7 @@ module Cassandra
52
49
  when ::String
53
50
  @ips << ::IPAddr.new(ip)
54
51
  else
55
- raise ::ArgumentError, "ips must contain only instance of String or IPAddr, #{ip.inspect} given"
52
+ raise ::ArgumentError, "each ip must be a String or IPAddr, #{ip.inspect} given"
56
53
  end
57
54
  end
58
55
  end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ # @private
21
+ class NullLogger
22
+ def close(*); end
23
+ def debug(*); end
24
+ def debug?; false end
25
+ def error(*); end
26
+ def error?; false end
27
+ def fatal(*); end
28
+ def fatal?; false end
29
+ def info(*); end
30
+ def info?; false end
31
+ def unknown(*); end
32
+ def warn(*); end
33
+ def warn?; false end
34
+ end
35
+ end
@@ -17,24 +17,8 @@
17
17
  #++
18
18
 
19
19
  module Cassandra
20
- # @private
21
- ProtocolError = Class.new(Error)
22
-
23
20
  # @private
24
21
  module Protocol
25
- DecodingError = Class.new(ProtocolError)
26
- EncodingError = Class.new(ProtocolError)
27
- InvalidStreamIdError = Class.new(ProtocolError)
28
- InvalidValueError = Class.new(ProtocolError)
29
- UnsupportedOperationError = Class.new(ProtocolError)
30
- UnsupportedFrameTypeError = Class.new(ProtocolError)
31
- UnsupportedResultKindError = Class.new(ProtocolError)
32
- UnsupportedColumnTypeError = Class.new(ProtocolError)
33
- UnsupportedEventTypeError = Class.new(ProtocolError)
34
- UnsupportedFeatureError = Class.new(ProtocolError)
35
- UnexpectedCompressionError = Class.new(ProtocolError)
36
- UnmaterializedRowsError = Class.new(ProtocolError)
37
-
38
22
  module Formats
39
23
  CHAR_FORMAT = 'c'.freeze
40
24
  DOUBLE_FORMAT = 'G'.freeze
@@ -22,7 +22,7 @@ module Cassandra
22
22
  def read_unsigned_byte
23
23
  read_byte
24
24
  rescue RangeError => e
25
- raise DecodingError, e.message, e.backtrace
25
+ raise Errors::DecodingError, e.message, e.backtrace
26
26
  end
27
27
 
28
28
  def read_varint(len=bytesize, signed=true)
@@ -36,7 +36,7 @@ module Cassandra
36
36
  end
37
37
  n
38
38
  rescue RangeError => e
39
- raise DecodingError, e.message, e.backtrace
39
+ raise Errors::DecodingError, e.message, e.backtrace
40
40
  end
41
41
 
42
42
  def read_decimal(len=bytesize)
@@ -57,8 +57,8 @@ module Cassandra
57
57
  fraction_string << number_string[number_string.length - size, number_string.length]
58
58
  end
59
59
  BigDecimal.new(fraction_string)
60
- rescue DecodingError => e
61
- raise DecodingError, e.message, e.backtrace
60
+ rescue Errors::DecodingError => e
61
+ raise Errors::DecodingError, e.message, e.backtrace
62
62
  end
63
63
 
64
64
  def read_long
@@ -68,19 +68,19 @@ module Cassandra
68
68
  bottom ^= 0xffffffff
69
69
  -((top << 32) | bottom) - 1
70
70
  rescue RangeError => e
71
- raise DecodingError, e.message, e.backtrace
71
+ raise Errors::DecodingError, e.message, e.backtrace
72
72
  end
73
73
 
74
74
  def read_double
75
75
  read(8).unpack(Formats::DOUBLE_FORMAT).first
76
76
  rescue RangeError => e
77
- raise DecodingError, "Not enough bytes available to decode a double: #{e.message}", e.backtrace
77
+ raise Errors::DecodingError, "Not enough bytes available to decode a double: #{e.message}", e.backtrace
78
78
  end
79
79
 
80
80
  def read_float
81
81
  read(4).unpack(Formats::FLOAT_FORMAT).first
82
82
  rescue RangeError => e
83
- raise DecodingError, "Not enough bytes available to decode a float: #{e.message}", e.backtrace
83
+ raise Errors::DecodingError, "Not enough bytes available to decode a float: #{e.message}", e.backtrace
84
84
  end
85
85
 
86
86
  def read_signed_int
@@ -88,13 +88,13 @@ module Cassandra
88
88
  return n if n <= 0x7fffffff
89
89
  n - 0xffffffff - 1
90
90
  rescue RangeError => e
91
- raise DecodingError, "Not enough bytes available to decode an int: #{e.message}", e.backtrace
91
+ raise Errors::DecodingError, "Not enough bytes available to decode an int: #{e.message}", e.backtrace
92
92
  end
93
93
 
94
94
  def read_unsigned_short
95
95
  read_short
96
96
  rescue RangeError => e
97
- raise DecodingError, "Not enough bytes available to decode a short: #{e.message}", e.backtrace
97
+ raise Errors::DecodingError, "Not enough bytes available to decode a short: #{e.message}", e.backtrace
98
98
  end
99
99
 
100
100
  def read_string
@@ -103,7 +103,7 @@ module Cassandra
103
103
  string.force_encoding(::Encoding::UTF_8)
104
104
  string
105
105
  rescue RangeError => e
106
- raise DecodingError, "Not enough bytes available to decode a string: #{e.message}", e.backtrace
106
+ raise Errors::DecodingError, "Not enough bytes available to decode a string: #{e.message}", e.backtrace
107
107
  end
108
108
 
109
109
  def read_long_string
@@ -112,13 +112,13 @@ module Cassandra
112
112
  string.force_encoding(::Encoding::UTF_8)
113
113
  string
114
114
  rescue RangeError => e
115
- raise DecodingError, "Not enough bytes available to decode a long string: #{e.message}", e.backtrace
115
+ raise Errors::DecodingError, "Not enough bytes available to decode a long string: #{e.message}", e.backtrace
116
116
  end
117
117
 
118
118
  def read_uuid(impl=Uuid)
119
119
  impl.new(read_varint(16, false))
120
- rescue DecodingError => e
121
- raise DecodingError, "Not enough bytes available to decode a UUID: #{e.message}", e.backtrace
120
+ rescue Errors::DecodingError => e
121
+ raise Errors::DecodingError, "Not enough bytes available to decode a UUID: #{e.message}", e.backtrace
122
122
  end
123
123
 
124
124
  def read_string_list
@@ -131,7 +131,7 @@ module Cassandra
131
131
  return nil if size & 0x80000000 == 0x80000000
132
132
  read(size)
133
133
  rescue RangeError => e
134
- raise DecodingError, "Not enough bytes available to decode a bytes: #{e.message}", e.backtrace
134
+ raise Errors::DecodingError, "Not enough bytes available to decode a bytes: #{e.message}", e.backtrace
135
135
  end
136
136
 
137
137
  def read_short_bytes
@@ -139,7 +139,7 @@ module Cassandra
139
139
  return nil if size & 0x8000 == 0x8000
140
140
  read(size)
141
141
  rescue RangeError => e
142
- raise DecodingError, "Not enough bytes available to decode a short bytes: #{e.message}", e.backtrace
142
+ raise Errors::DecodingError, "Not enough bytes available to decode a short bytes: #{e.message}", e.backtrace
143
143
  end
144
144
 
145
145
  def read_option
@@ -157,12 +157,12 @@ module Cassandra
157
157
  port = read_int
158
158
  [ip_addr, port]
159
159
  rescue RangeError => e
160
- raise DecodingError, "Not enough bytes available to decode an INET: #{e.message}", e.backtrace
160
+ raise Errors::DecodingError, "Not enough bytes available to decode an INET: #{e.message}", e.backtrace
161
161
  end
162
162
 
163
163
  def read_consistency
164
164
  index = read_unsigned_short
165
- raise DecodingError, "Unknown consistency index #{index}" if index >= CONSISTENCIES.size || CONSISTENCIES[index].nil?
165
+ raise Errors::DecodingError, "Unknown consistency index #{index}" if index >= CONSISTENCIES.size || CONSISTENCIES[index].nil?
166
166
  CONSISTENCIES[index]
167
167
  end
168
168
 
@@ -241,7 +241,7 @@ module Cassandra
241
241
 
242
242
  def append_consistency(consistency)
243
243
  index = CONSISTENCIES.index(consistency)
244
- raise EncodingError, %(Unknown consistency "#{consistency}") if index.nil? || CONSISTENCIES[index].nil?
244
+ raise Errors::EncodingError, %(Unknown consistency "#{consistency}") if index.nil? || CONSISTENCIES[index].nil?
245
245
  append_short(index)
246
246
  end
247
247
 
@@ -35,7 +35,7 @@ module Cassandra
35
35
  # @return [String] the current keyspace for the underlying connection
36
36
  attr_reader :keyspace
37
37
 
38
- def initialize(connection, scheduler, protocol_version, compressor=nil)
38
+ def initialize(connection, scheduler, protocol_version, compressor=nil, heartbeat_interval = 30, idle_timeout = 60)
39
39
  @connection = connection
40
40
  @scheduler = scheduler
41
41
  @compressor = compressor
@@ -53,6 +53,10 @@ module Cassandra
53
53
  @lock = Mutex.new
54
54
  @closed_promise = Ione::Promise.new
55
55
  @keyspace = nil
56
+ @heartbeat = nil
57
+ @terminate = nil
58
+ @heartbeat_interval = heartbeat_interval
59
+ @idle_timeout = idle_timeout
56
60
  end
57
61
 
58
62
  # Returns the hostname of the underlying connection
@@ -128,7 +132,7 @@ module Cassandra
128
132
  # closes the futures of all active requests will be failed with the error
129
133
  # that caused the connection to close, or nil.
130
134
  #
131
- # When `timeout` is specified the future will fail with {Cassandra::TimeoutError}
135
+ # When `timeout` is specified the future will fail with {Cassandra::Errors::TimeoutError}
132
136
  # after that many seconds have passed. If a response arrives after that
133
137
  # time it will be lost. If a response never arrives for the request the
134
138
  # channel occupied by the request will _not_ be reused.
@@ -138,8 +142,9 @@ module Cassandra
138
142
  # failing the request
139
143
  # @return [Ione::Future<Cassandra::Protocol::Response>] a future that resolves to
140
144
  # the response
141
- def send_request(request, timeout=nil)
142
- return Ione::Future.failed(Errors::NotConnectedError.new) if closed?
145
+ def send_request(request, timeout=nil, with_heartbeat = true)
146
+ return Ione::Future.failed(Errors::IOError.new('Connection closed')) if closed?
147
+ schedule_heartbeat if with_heartbeat
143
148
  promise = RequestPromise.new(request, @frame_encoder)
144
149
  id = nil
145
150
  @lock.lock
@@ -174,8 +179,18 @@ module Cassandra
174
179
  # Closes the underlying connection.
175
180
  #
176
181
  # @return [Ione::Future] a future that completes when the connection has closed
177
- def close
178
- @connection.close
182
+ def close(cause = nil)
183
+ if @heartbeat
184
+ @scheduler.cancel_timer(@heartbeat)
185
+ @heartbeat = nil
186
+ end
187
+
188
+ if @terminate
189
+ @scheduler.cancel_timer(@terminate)
190
+ @terminate = nil
191
+ end
192
+
193
+ @connection.close(cause)
179
194
  @closed_promise.future
180
195
  end
181
196
 
@@ -199,7 +214,7 @@ module Cassandra
199
214
  def time_out!
200
215
  unless future.completed?
201
216
  @timed_out = true
202
- fail(TimeoutError.new)
217
+ fail(Errors::TimeoutError.new('Timed out'))
203
218
  end
204
219
  end
205
220
 
@@ -209,6 +224,7 @@ module Cassandra
209
224
  end
210
225
 
211
226
  def receive_data(data)
227
+ reschedule_termination
212
228
  @read_buffer << data
213
229
  @current_frame = @frame_decoder.decode_frame(@read_buffer, @current_frame)
214
230
  while @current_frame.complete?
@@ -248,6 +264,9 @@ module Cassandra
248
264
  if response.is_a?(Protocol::SetKeyspaceResultResponse)
249
265
  @keyspace = response.keyspace
250
266
  end
267
+ if response.is_a?(Protocol::SchemaChangeResultResponse) && response.change == 'DROPPED' && response.keyspace == @keyspace && response.table.empty?
268
+ @keyspace = nil
269
+ end
251
270
  flush_request_queue
252
271
  unless promise.timed_out?
253
272
  promise.fulfill(response)
@@ -291,9 +310,22 @@ module Cassandra
291
310
  end
292
311
 
293
312
  def socket_closed(cause)
294
- request_failure_cause = cause || Io::ConnectionClosedError.new
313
+ if cause
314
+ e = Errors::IOError.new(cause.message)
315
+ e.set_backtrace(cause.backtrace)
316
+
317
+ cause = e
318
+ end
319
+
320
+ request_failure_cause = cause || Errors::IOError.new('Connection closed')
295
321
  promises_to_fail = nil
296
322
  @lock.synchronize do
323
+ @scheduler.cancel_timer(@heartbeat) if @heartbeat
324
+ @scheduler.cancel_timer(@terminate) if @terminate
325
+
326
+ @heartbeat = nil
327
+ @terminate = nil
328
+
297
329
  promises_to_fail = @promises.compact
298
330
  promises_to_fail.concat(@request_queue_in)
299
331
  promises_to_fail.concat(@request_queue_out)
@@ -311,6 +343,41 @@ module Cassandra
311
343
  end
312
344
  end
313
345
 
346
+ def schedule_heartbeat
347
+ return unless @heartbeat_interval
348
+
349
+ timer = nil
350
+
351
+ @lock.synchronize do
352
+ @scheduler.cancel_timer(@heartbeat) if @heartbeat && !@heartbeat.resolved?
353
+
354
+ @heartbeat = timer = @scheduler.schedule_timer(@heartbeat_interval)
355
+ end
356
+
357
+ timer.on_value do
358
+ send_request(HEARTBEAT, nil, false).on_value do
359
+ schedule_heartbeat
360
+ end
361
+ end
362
+ end
363
+
364
+ def reschedule_termination
365
+ return unless @idle_timeout
366
+
367
+ timer = nil
368
+
369
+ @lock.synchronize do
370
+ @scheduler.cancel_timer(@terminate) if @terminate
371
+
372
+ @terminate = timer = @scheduler.schedule_timer(@idle_timeout)
373
+ end
374
+
375
+ timer.on_value do
376
+ @terminate = nil
377
+ @connection.close(TERMINATED)
378
+ end
379
+ end
380
+
314
381
  def next_stream_id
315
382
  if (stream_id = @promises.index(nil))
316
383
  stream_id
@@ -318,6 +385,9 @@ module Cassandra
318
385
  nil
319
386
  end
320
387
  end
388
+
389
+ HEARTBEAT = OptionsRequest.new
390
+ TERMINATED = Errors::TimeoutError.new('Terminated due to inactivity')
321
391
  end
322
392
  end
323
393
  end