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

Sign up to get free protection for your applications and to get access to all the features.
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