yugabyte-ycql-driver 3.2.3.1
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.
- checksums.yaml +7 -0
- data/.yardopts +13 -0
- data/README.md +242 -0
- data/ext/cassandra_murmur3/cassandra_murmur3.c +178 -0
- data/ext/cassandra_murmur3/extconf.rb +2 -0
- data/lib/cassandra/address_resolution.rb +36 -0
- data/lib/cassandra/address_resolution/policies.rb +2 -0
- data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +56 -0
- data/lib/cassandra/address_resolution/policies/none.rb +35 -0
- data/lib/cassandra/aggregate.rb +123 -0
- data/lib/cassandra/argument.rb +51 -0
- data/lib/cassandra/attr_boolean.rb +33 -0
- data/lib/cassandra/auth.rb +100 -0
- data/lib/cassandra/auth/providers.rb +17 -0
- data/lib/cassandra/auth/providers/password.rb +65 -0
- data/lib/cassandra/cassandra_logger.rb +80 -0
- data/lib/cassandra/cluster.rb +331 -0
- data/lib/cassandra/cluster/client.rb +1612 -0
- data/lib/cassandra/cluster/connection_pool.rb +78 -0
- data/lib/cassandra/cluster/connector.rb +372 -0
- data/lib/cassandra/cluster/control_connection.rb +962 -0
- data/lib/cassandra/cluster/failed_connection.rb +35 -0
- data/lib/cassandra/cluster/metadata.rb +142 -0
- data/lib/cassandra/cluster/options.rb +145 -0
- data/lib/cassandra/cluster/registry.rb +284 -0
- data/lib/cassandra/cluster/schema.rb +405 -0
- data/lib/cassandra/cluster/schema/cql_type_parser.rb +112 -0
- data/lib/cassandra/cluster/schema/fetchers.rb +1627 -0
- data/lib/cassandra/cluster/schema/fqcn_type_parser.rb +175 -0
- data/lib/cassandra/cluster/schema/partitioners.rb +21 -0
- data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +45 -0
- data/lib/cassandra/cluster/schema/partitioners/ordered.rb +37 -0
- data/lib/cassandra/cluster/schema/partitioners/random.rb +37 -0
- data/lib/cassandra/cluster/schema/replication_strategies.rb +21 -0
- data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +102 -0
- data/lib/cassandra/cluster/schema/replication_strategies/none.rb +39 -0
- data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +44 -0
- data/lib/cassandra/column.rb +66 -0
- data/lib/cassandra/column_container.rb +326 -0
- data/lib/cassandra/compression.rb +69 -0
- data/lib/cassandra/compression/compressors/lz4.rb +73 -0
- data/lib/cassandra/compression/compressors/snappy.rb +69 -0
- data/lib/cassandra/custom_data.rb +53 -0
- data/lib/cassandra/driver.rb +260 -0
- data/lib/cassandra/errors.rb +784 -0
- data/lib/cassandra/execution/info.rb +69 -0
- data/lib/cassandra/execution/options.rb +267 -0
- data/lib/cassandra/execution/profile.rb +153 -0
- data/lib/cassandra/execution/profile_manager.rb +71 -0
- data/lib/cassandra/execution/trace.rb +192 -0
- data/lib/cassandra/executors.rb +113 -0
- data/lib/cassandra/function.rb +156 -0
- data/lib/cassandra/function_collection.rb +85 -0
- data/lib/cassandra/future.rb +794 -0
- data/lib/cassandra/host.rb +102 -0
- data/lib/cassandra/index.rb +118 -0
- data/lib/cassandra/keyspace.rb +473 -0
- data/lib/cassandra/listener.rb +87 -0
- data/lib/cassandra/load_balancing.rb +121 -0
- data/lib/cassandra/load_balancing/policies.rb +20 -0
- data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +172 -0
- data/lib/cassandra/load_balancing/policies/round_robin.rb +141 -0
- data/lib/cassandra/load_balancing/policies/token_aware.rb +149 -0
- data/lib/cassandra/load_balancing/policies/white_list.rb +100 -0
- data/lib/cassandra/materialized_view.rb +92 -0
- data/lib/cassandra/null_logger.rb +56 -0
- data/lib/cassandra/protocol.rb +102 -0
- data/lib/cassandra/protocol/coder.rb +1085 -0
- data/lib/cassandra/protocol/cql_byte_buffer.rb +418 -0
- data/lib/cassandra/protocol/cql_protocol_handler.rb +448 -0
- data/lib/cassandra/protocol/request.rb +41 -0
- data/lib/cassandra/protocol/requests/auth_response_request.rb +51 -0
- data/lib/cassandra/protocol/requests/batch_request.rb +117 -0
- data/lib/cassandra/protocol/requests/credentials_request.rb +51 -0
- data/lib/cassandra/protocol/requests/execute_request.rb +122 -0
- data/lib/cassandra/protocol/requests/options_request.rb +39 -0
- data/lib/cassandra/protocol/requests/prepare_request.rb +59 -0
- data/lib/cassandra/protocol/requests/query_request.rb +112 -0
- data/lib/cassandra/protocol/requests/register_request.rb +38 -0
- data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
- data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
- data/lib/cassandra/protocol/response.rb +28 -0
- data/lib/cassandra/protocol/responses/already_exists_error_response.rb +50 -0
- data/lib/cassandra/protocol/responses/auth_challenge_response.rb +36 -0
- data/lib/cassandra/protocol/responses/auth_success_response.rb +36 -0
- data/lib/cassandra/protocol/responses/authenticate_response.rb +36 -0
- data/lib/cassandra/protocol/responses/error_response.rb +142 -0
- data/lib/cassandra/protocol/responses/event_response.rb +30 -0
- data/lib/cassandra/protocol/responses/function_failure_error_response.rb +52 -0
- data/lib/cassandra/protocol/responses/prepared_result_response.rb +62 -0
- data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +59 -0
- data/lib/cassandra/protocol/responses/read_failure_error_response.rb +71 -0
- data/lib/cassandra/protocol/responses/read_timeout_error_response.rb +61 -0
- data/lib/cassandra/protocol/responses/ready_response.rb +43 -0
- data/lib/cassandra/protocol/responses/result_response.rb +42 -0
- data/lib/cassandra/protocol/responses/rows_result_response.rb +39 -0
- data/lib/cassandra/protocol/responses/schema_change_event_response.rb +73 -0
- data/lib/cassandra/protocol/responses/schema_change_result_response.rb +70 -0
- data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +37 -0
- data/lib/cassandra/protocol/responses/status_change_event_response.rb +39 -0
- data/lib/cassandra/protocol/responses/supported_response.rb +36 -0
- data/lib/cassandra/protocol/responses/topology_change_event_response.rb +33 -0
- data/lib/cassandra/protocol/responses/unavailable_error_response.rb +58 -0
- data/lib/cassandra/protocol/responses/unprepared_error_response.rb +48 -0
- data/lib/cassandra/protocol/responses/void_result_response.rb +34 -0
- data/lib/cassandra/protocol/responses/write_failure_error_response.rb +73 -0
- data/lib/cassandra/protocol/responses/write_timeout_error_response.rb +63 -0
- data/lib/cassandra/protocol/v1.rb +326 -0
- data/lib/cassandra/protocol/v3.rb +358 -0
- data/lib/cassandra/protocol/v4.rb +478 -0
- data/lib/cassandra/reconnection.rb +49 -0
- data/lib/cassandra/reconnection/policies.rb +20 -0
- data/lib/cassandra/reconnection/policies/constant.rb +46 -0
- data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
- data/lib/cassandra/result.rb +276 -0
- data/lib/cassandra/retry.rb +154 -0
- data/lib/cassandra/retry/policies.rb +21 -0
- data/lib/cassandra/retry/policies/default.rb +53 -0
- data/lib/cassandra/retry/policies/downgrading_consistency.rb +73 -0
- data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
- data/lib/cassandra/session.rb +270 -0
- data/lib/cassandra/statement.rb +32 -0
- data/lib/cassandra/statements.rb +23 -0
- data/lib/cassandra/statements/batch.rb +146 -0
- data/lib/cassandra/statements/bound.rb +65 -0
- data/lib/cassandra/statements/prepared.rb +235 -0
- data/lib/cassandra/statements/simple.rb +118 -0
- data/lib/cassandra/statements/void.rb +38 -0
- data/lib/cassandra/table.rb +240 -0
- data/lib/cassandra/time.rb +103 -0
- data/lib/cassandra/time_uuid.rb +78 -0
- data/lib/cassandra/timestamp_generator.rb +37 -0
- data/lib/cassandra/timestamp_generator/simple.rb +38 -0
- data/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb +58 -0
- data/lib/cassandra/trigger.rb +67 -0
- data/lib/cassandra/tuple.rb +131 -0
- data/lib/cassandra/types.rb +1704 -0
- data/lib/cassandra/udt.rb +443 -0
- data/lib/cassandra/util.rb +464 -0
- data/lib/cassandra/uuid.rb +110 -0
- data/lib/cassandra/uuid/generator.rb +212 -0
- data/lib/cassandra/version.rb +21 -0
- data/lib/datastax/cassandra.rb +47 -0
- data/lib/ycql.rb +842 -0
- metadata +243 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# Copyright 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
|
+
module Protocol
|
|
21
|
+
class CqlByteBuffer < Ione::ByteBuffer
|
|
22
|
+
# @private
|
|
23
|
+
MINUS = '-'.freeze
|
|
24
|
+
# @private
|
|
25
|
+
ZERO = '0'.freeze
|
|
26
|
+
# @private
|
|
27
|
+
DECIMAL_POINT = '.'.freeze
|
|
28
|
+
# @private
|
|
29
|
+
FLOAT_STRING_FORMAT = 'F'.freeze
|
|
30
|
+
# @private
|
|
31
|
+
NO_CHAR = ''.freeze
|
|
32
|
+
|
|
33
|
+
def inspect
|
|
34
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} #{to_str.inspect}>"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def read_unsigned_byte
|
|
38
|
+
read_byte
|
|
39
|
+
rescue RangeError => e
|
|
40
|
+
raise Errors::DecodingError, e.message, e.backtrace
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def read_varint(len = bytesize, signed = true)
|
|
44
|
+
bytes = read(len)
|
|
45
|
+
n = 0
|
|
46
|
+
bytes.each_byte do |b|
|
|
47
|
+
n = (n << 8) | b
|
|
48
|
+
end
|
|
49
|
+
n -= 2**(bytes.length * 8) if signed && bytes.getbyte(0) & 0x80 == 0x80
|
|
50
|
+
n
|
|
51
|
+
rescue RangeError => e
|
|
52
|
+
raise Errors::DecodingError, e.message, e.backtrace
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def read_decimal(len = bytesize)
|
|
56
|
+
scale = read_signed_int
|
|
57
|
+
number_string = read_varint(len - 4).to_s
|
|
58
|
+
if scale <= 0
|
|
59
|
+
# Special case where the actual scale is positive; scale in the protocol is actually negative of
|
|
60
|
+
# reality.
|
|
61
|
+
BigDecimal.new(number_string + '0' * -scale)
|
|
62
|
+
else
|
|
63
|
+
if number_string.length <= scale
|
|
64
|
+
if number_string.start_with?(MINUS)
|
|
65
|
+
number_string = number_string[1, number_string.length - 1]
|
|
66
|
+
fraction_string = MINUS + ZERO << DECIMAL_POINT
|
|
67
|
+
else
|
|
68
|
+
fraction_string = ZERO + DECIMAL_POINT
|
|
69
|
+
end
|
|
70
|
+
(scale - number_string.length).times { fraction_string << ZERO }
|
|
71
|
+
fraction_string << number_string
|
|
72
|
+
else
|
|
73
|
+
fraction_string = number_string[0, number_string.length - scale]
|
|
74
|
+
fraction_string << DECIMAL_POINT
|
|
75
|
+
fraction_string <<
|
|
76
|
+
number_string[number_string.length - scale, number_string.length]
|
|
77
|
+
end
|
|
78
|
+
BigDecimal.new(fraction_string)
|
|
79
|
+
end
|
|
80
|
+
rescue Errors::DecodingError => e
|
|
81
|
+
raise Errors::DecodingError, e.message, e.backtrace
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def read_long
|
|
85
|
+
top, bottom = read(8).unpack(Formats::TWO_INTS_FORMAT)
|
|
86
|
+
return (top << 32) | bottom if top <= 0x7fffffff
|
|
87
|
+
top ^= 0xffffffff
|
|
88
|
+
bottom ^= 0xffffffff
|
|
89
|
+
-((top << 32) | bottom) - 1
|
|
90
|
+
rescue RangeError => e
|
|
91
|
+
raise Errors::DecodingError, e.message, e.backtrace
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def read_double
|
|
95
|
+
read(8).unpack(Formats::DOUBLE_FORMAT).first
|
|
96
|
+
rescue RangeError => e
|
|
97
|
+
raise Errors::DecodingError,
|
|
98
|
+
"Not enough bytes available to decode a double: #{e.message}", e.backtrace
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def read_double_le
|
|
102
|
+
read(8).unpack(Formats::DOUBLE_FORMAT_LE).first
|
|
103
|
+
rescue RangeError => e
|
|
104
|
+
raise Errors::DecodingError,
|
|
105
|
+
"Not enough bytes available to decode a double: #{e.message}", e.backtrace
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def read_float
|
|
109
|
+
read(4).unpack(Formats::FLOAT_FORMAT).first
|
|
110
|
+
rescue RangeError => e
|
|
111
|
+
raise Errors::DecodingError,
|
|
112
|
+
"Not enough bytes available to decode a float: #{e.message}", e.backtrace
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def read_signed_int
|
|
116
|
+
n = read_int
|
|
117
|
+
return n if n <= 0x7fffffff
|
|
118
|
+
n - 0xffffffff - 1
|
|
119
|
+
rescue RangeError => e
|
|
120
|
+
raise Errors::DecodingError,
|
|
121
|
+
"Not enough bytes available to decode an int: #{e.message}", e.backtrace
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def read_unsigned_int_le
|
|
125
|
+
read(4).unpack(Formats::INT_FORMAT_LE).first
|
|
126
|
+
rescue RangeError => e
|
|
127
|
+
raise Errors::DecodingError,
|
|
128
|
+
"Not enough bytes available to decode an int: #{e.message}", e.backtrace
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def read_unsigned_short
|
|
132
|
+
read_short
|
|
133
|
+
rescue RangeError => e
|
|
134
|
+
raise Errors::DecodingError,
|
|
135
|
+
"Not enough bytes available to decode a short: #{e.message}", e.backtrace
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def read_unsigned_short_le
|
|
139
|
+
read(2).unpack(Formats::SHORT_FORMAT_LE).first
|
|
140
|
+
rescue RangeError => e
|
|
141
|
+
raise Errors::DecodingError,
|
|
142
|
+
"Not enough bytes available to decode a short: #{e.message}", e.backtrace
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def read_string
|
|
146
|
+
length = read_unsigned_short
|
|
147
|
+
string = read(length)
|
|
148
|
+
string.force_encoding(::Encoding::UTF_8)
|
|
149
|
+
string
|
|
150
|
+
rescue RangeError => e
|
|
151
|
+
raise Errors::DecodingError,
|
|
152
|
+
"Not enough bytes available to decode a string: #{e.message}", e.backtrace
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def read_long_string
|
|
156
|
+
length = read_signed_int
|
|
157
|
+
string = read(length)
|
|
158
|
+
string.force_encoding(::Encoding::UTF_8)
|
|
159
|
+
string
|
|
160
|
+
rescue RangeError => e
|
|
161
|
+
raise Errors::DecodingError,
|
|
162
|
+
"Not enough bytes available to decode a long string: #{e.message}",
|
|
163
|
+
e.backtrace
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def read_uuid(impl = Uuid)
|
|
167
|
+
impl.new(read_varint(16, false))
|
|
168
|
+
rescue Errors::DecodingError => e
|
|
169
|
+
raise Errors::DecodingError,
|
|
170
|
+
"Not enough bytes available to decode a UUID: #{e.message}", e.backtrace
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def read_string_list
|
|
174
|
+
size = read_unsigned_short
|
|
175
|
+
Array.new(size) { read_string }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def read_bytes
|
|
179
|
+
size = read_signed_int
|
|
180
|
+
return nil if size & 0x80000000 == 0x80000000
|
|
181
|
+
read(size)
|
|
182
|
+
rescue RangeError => e
|
|
183
|
+
raise Errors::DecodingError,
|
|
184
|
+
"Not enough bytes available to decode a bytes: #{e.message}", e.backtrace
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def read_short_bytes
|
|
188
|
+
read(read_unsigned_short)
|
|
189
|
+
rescue RangeError => e
|
|
190
|
+
raise Errors::DecodingError,
|
|
191
|
+
"Not enough bytes available to decode a short bytes: #{e.message}",
|
|
192
|
+
e.backtrace
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def read_option
|
|
196
|
+
id = read_unsigned_short
|
|
197
|
+
value = nil
|
|
198
|
+
value = yield id, self if block_given?
|
|
199
|
+
[id, value]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def read_inet
|
|
203
|
+
size = read_byte
|
|
204
|
+
ip_addr = IPAddr.new_ntoh(read(size))
|
|
205
|
+
port = read_int
|
|
206
|
+
[ip_addr, port]
|
|
207
|
+
rescue RangeError => e
|
|
208
|
+
raise Errors::DecodingError,
|
|
209
|
+
"Not enough bytes available to decode an INET: #{e.message}",
|
|
210
|
+
e.backtrace
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def read_inet_addr
|
|
214
|
+
size = read_byte
|
|
215
|
+
IPAddr.new_ntoh(read(size))
|
|
216
|
+
rescue RangeError => e
|
|
217
|
+
raise Errors::DecodingError,
|
|
218
|
+
"Not enough bytes available to decode an INET addr: #{e.message}",
|
|
219
|
+
e.backtrace
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def read_consistency
|
|
223
|
+
index = read_unsigned_short
|
|
224
|
+
if index >= CONSISTENCIES.size || CONSISTENCIES[index].nil?
|
|
225
|
+
raise Errors::DecodingError, "Unknown consistency index #{index}"
|
|
226
|
+
end
|
|
227
|
+
CONSISTENCIES[index]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def read_string_map
|
|
231
|
+
map = {}
|
|
232
|
+
map_size = read_unsigned_short
|
|
233
|
+
map_size.times do
|
|
234
|
+
key = read_string
|
|
235
|
+
map[key] = read_string
|
|
236
|
+
end
|
|
237
|
+
map
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def read_bytes_map
|
|
241
|
+
map = {}
|
|
242
|
+
map_size = read_unsigned_short
|
|
243
|
+
map_size.times do
|
|
244
|
+
key = read_string
|
|
245
|
+
map[key] = read_bytes
|
|
246
|
+
end
|
|
247
|
+
map
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def read_reason_map
|
|
251
|
+
# reason_map is new in v5. Starts with an int indicating the number of key-value pairs, followed by
|
|
252
|
+
# the key-value pairs. Keys are inet's, values are short int's.
|
|
253
|
+
map = {}
|
|
254
|
+
map_size = read_int
|
|
255
|
+
map_size.times do
|
|
256
|
+
key = read_inet_addr
|
|
257
|
+
map[key] = read_short
|
|
258
|
+
end
|
|
259
|
+
map
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def read_string_multimap
|
|
263
|
+
map = {}
|
|
264
|
+
map_size = read_unsigned_short
|
|
265
|
+
map_size.times do
|
|
266
|
+
key = read_string
|
|
267
|
+
map[key] = read_string_list
|
|
268
|
+
end
|
|
269
|
+
map
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def read_smallint
|
|
273
|
+
n = read_short
|
|
274
|
+
return n if n <= 0x7fff
|
|
275
|
+
n - 0xffff - 1
|
|
276
|
+
rescue RangeError => e
|
|
277
|
+
raise Errors::DecodingError,
|
|
278
|
+
"Not enough bytes available to decode a smallint: #{e.message}", e.backtrace
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def read_tinyint
|
|
282
|
+
n = read_byte
|
|
283
|
+
return n if n <= 0x7f
|
|
284
|
+
n - 0xff - 1
|
|
285
|
+
rescue RangeError => e
|
|
286
|
+
raise Errors::DecodingError,
|
|
287
|
+
"Not enough bytes available to decode a tinyint: #{e.message}", e.backtrace
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def append_tinyint(n)
|
|
291
|
+
append([n].pack(Formats::CHAR_FORMAT))
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def append_smallint(n)
|
|
295
|
+
append_short(n)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def append_int(n)
|
|
299
|
+
append([n].pack(Formats::INT_FORMAT))
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def append_short(n)
|
|
303
|
+
append([n].pack(Formats::SHORT_FORMAT))
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def append_string(str)
|
|
307
|
+
str = str.to_s
|
|
308
|
+
append_short(str.bytesize)
|
|
309
|
+
append(str)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def append_long_string(str)
|
|
313
|
+
append_int(str.bytesize)
|
|
314
|
+
append(str)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def append_uuid(uuid)
|
|
318
|
+
v = uuid.value
|
|
319
|
+
append_int((v >> 96) & 0xffffffff)
|
|
320
|
+
append_int((v >> 64) & 0xffffffff)
|
|
321
|
+
append_int((v >> 32) & 0xffffffff)
|
|
322
|
+
append_int((v >> 0) & 0xffffffff)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def append_string_list(strs)
|
|
326
|
+
append_short(strs.size)
|
|
327
|
+
strs.each do |str|
|
|
328
|
+
append_string(str)
|
|
329
|
+
end
|
|
330
|
+
self
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def append_bytes(bytes)
|
|
334
|
+
if bytes
|
|
335
|
+
append_int(bytes.bytesize)
|
|
336
|
+
append(bytes)
|
|
337
|
+
else
|
|
338
|
+
append_int(-1)
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def append_short_bytes(bytes)
|
|
343
|
+
if bytes
|
|
344
|
+
append_short(bytes.bytesize)
|
|
345
|
+
append(bytes)
|
|
346
|
+
else
|
|
347
|
+
append_short(-1)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def append_consistency(consistency)
|
|
352
|
+
index = CONSISTENCIES.index(consistency)
|
|
353
|
+
raise Errors::EncodingError, %(Unknown consistency "#{consistency}") if index.nil? || CONSISTENCIES[index].nil?
|
|
354
|
+
append_short(index)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def append_string_map(map)
|
|
358
|
+
append_short(map.size)
|
|
359
|
+
map.each do |key, value|
|
|
360
|
+
append_string(key)
|
|
361
|
+
append_string(value)
|
|
362
|
+
end
|
|
363
|
+
self
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def append_bytes_map(map)
|
|
367
|
+
append_short(map.size)
|
|
368
|
+
map.each do |key, value|
|
|
369
|
+
append_string(key)
|
|
370
|
+
append_bytes(value)
|
|
371
|
+
end
|
|
372
|
+
self
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def append_long(n)
|
|
376
|
+
top = n >> 32
|
|
377
|
+
bottom = n & 0xffffffff
|
|
378
|
+
append_int(top)
|
|
379
|
+
append_int(bottom)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def append_varint(n)
|
|
383
|
+
num = n
|
|
384
|
+
bytes = []
|
|
385
|
+
loop do
|
|
386
|
+
bytes << (num & 0xff)
|
|
387
|
+
num >>= 8
|
|
388
|
+
break if (num == 0 || num == -1) && (bytes.last[7] == num[7])
|
|
389
|
+
end
|
|
390
|
+
append(bytes.reverse.pack(Formats::BYTES_FORMAT))
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def append_decimal(n)
|
|
394
|
+
str = n.to_s(FLOAT_STRING_FORMAT)
|
|
395
|
+
size = str.index(DECIMAL_POINT)
|
|
396
|
+
number_string = str.gsub(DECIMAL_POINT, NO_CHAR)
|
|
397
|
+
|
|
398
|
+
num = number_string.to_i
|
|
399
|
+
raw = self.class.new.append_varint(num)
|
|
400
|
+
append_int(number_string.length - size)
|
|
401
|
+
append(raw)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def append_double(n)
|
|
405
|
+
append([n].pack(Formats::DOUBLE_FORMAT))
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def append_float(n)
|
|
409
|
+
append([n].pack(Formats::FLOAT_FORMAT))
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def eql?(other)
|
|
413
|
+
other.eql?(to_str)
|
|
414
|
+
end
|
|
415
|
+
alias == eql?
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# Copyright 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
|
+
module Protocol
|
|
21
|
+
# This class wraps a single connection and translates between request/
|
|
22
|
+
# response frames and raw bytes.
|
|
23
|
+
#
|
|
24
|
+
# You send requests with #send_request, and receive responses through the
|
|
25
|
+
# returned future.
|
|
26
|
+
#
|
|
27
|
+
# Instances of this class are thread safe.
|
|
28
|
+
#
|
|
29
|
+
# @example Sending an OPTIONS request
|
|
30
|
+
# future = protocol_handler.send_request(Cassandra::Protocol::OptionsRequest.new)
|
|
31
|
+
# response = future.get
|
|
32
|
+
# puts "These options are supported: #{response.options}"
|
|
33
|
+
class CqlProtocolHandler
|
|
34
|
+
# @return [String] the current keyspace for the underlying connection
|
|
35
|
+
attr_reader :keyspace
|
|
36
|
+
|
|
37
|
+
# @return [Exception] outstanding error, from a failed connection.
|
|
38
|
+
attr_reader :error
|
|
39
|
+
|
|
40
|
+
# @return [Integer] the version of the protocol to use in communicating with C*.
|
|
41
|
+
attr_reader :protocol_version
|
|
42
|
+
|
|
43
|
+
def initialize(connection,
|
|
44
|
+
scheduler,
|
|
45
|
+
protocol_version,
|
|
46
|
+
compressor = nil,
|
|
47
|
+
heartbeat_interval = 30,
|
|
48
|
+
idle_timeout = 60,
|
|
49
|
+
requests_per_connection = 128,
|
|
50
|
+
custom_type_handlers = {})
|
|
51
|
+
@protocol_version = protocol_version
|
|
52
|
+
@connection = connection
|
|
53
|
+
@scheduler = scheduler
|
|
54
|
+
@compressor = compressor
|
|
55
|
+
@connection.on_data(&method(:receive_data))
|
|
56
|
+
@connection.on_closed(&method(:socket_closed))
|
|
57
|
+
|
|
58
|
+
@streams = Array.new(requests_per_connection) {|i| i}
|
|
59
|
+
|
|
60
|
+
@promises = {}
|
|
61
|
+
|
|
62
|
+
if protocol_version > 3
|
|
63
|
+
@frame_encoder = V4::Encoder.new(@compressor, protocol_version)
|
|
64
|
+
@frame_decoder = V4::Decoder.new(self, @compressor, custom_type_handlers)
|
|
65
|
+
elsif protocol_version > 2
|
|
66
|
+
@frame_encoder = V3::Encoder.new(@compressor, protocol_version)
|
|
67
|
+
@frame_decoder = V3::Decoder.new(self, @compressor)
|
|
68
|
+
else
|
|
69
|
+
@frame_encoder = V1::Encoder.new(@compressor, protocol_version)
|
|
70
|
+
@frame_decoder = V1::Decoder.new(self, @compressor)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@request_queue_in = []
|
|
74
|
+
@request_queue_out = []
|
|
75
|
+
@event_listeners = []
|
|
76
|
+
@data = {}
|
|
77
|
+
@lock = Mutex.new
|
|
78
|
+
@closed_promise = Ione::Promise.new
|
|
79
|
+
@keyspace = nil
|
|
80
|
+
@heartbeat = nil
|
|
81
|
+
@terminate = nil
|
|
82
|
+
@heartbeat_interval = heartbeat_interval
|
|
83
|
+
@idle_timeout = idle_timeout
|
|
84
|
+
@error = nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns the hostname of the underlying connection
|
|
88
|
+
#
|
|
89
|
+
# @return [String] the hostname
|
|
90
|
+
def host
|
|
91
|
+
@connection.host
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the port of the underlying connection
|
|
95
|
+
#
|
|
96
|
+
# @return [Integer] the port
|
|
97
|
+
def port
|
|
98
|
+
@connection.port
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Associate arbitrary data with this protocol handler object. This is
|
|
102
|
+
# useful in situations where additional metadata can be loaded after the
|
|
103
|
+
# connection has been set up, or to keep statistics specific to the
|
|
104
|
+
# connection this protocol handler wraps.
|
|
105
|
+
def []=(key, value)
|
|
106
|
+
@lock.lock
|
|
107
|
+
@data[key] = value
|
|
108
|
+
ensure
|
|
109
|
+
@lock.unlock
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @see {#[]=}
|
|
113
|
+
# @return the value associated with the key
|
|
114
|
+
def [](key)
|
|
115
|
+
@lock.lock
|
|
116
|
+
@data[key]
|
|
117
|
+
ensure
|
|
118
|
+
@lock.unlock
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [true, false] true if the underlying connection is connected
|
|
122
|
+
def connected?
|
|
123
|
+
@connection.connected?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return [true, false] true if the underlying connection is closed
|
|
127
|
+
def closed?
|
|
128
|
+
@connection.closed?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Register to receive notification when the underlying connection has
|
|
132
|
+
# closed. If the connection closed abruptly the error will be passed
|
|
133
|
+
# to the listener, otherwise it will not receive any parameters.
|
|
134
|
+
#
|
|
135
|
+
# @yieldparam error [nil, Error] the error that caused the connection to
|
|
136
|
+
# close, if any
|
|
137
|
+
def on_closed(&listener)
|
|
138
|
+
@closed_promise.future.on_value(&listener)
|
|
139
|
+
@closed_promise.future.on_failure(&listener)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Register to receive server sent events, like schema changes, nodes going
|
|
143
|
+
# up or down, etc. To actually receive events you also need to send a
|
|
144
|
+
# REGISTER request for the events you wish to receive.
|
|
145
|
+
#
|
|
146
|
+
# @yieldparam event [Cassandra::Protocol::EventResponse] an event sent by the server
|
|
147
|
+
def on_event(&listener)
|
|
148
|
+
@lock.lock
|
|
149
|
+
@event_listeners += [listener]
|
|
150
|
+
ensure
|
|
151
|
+
@lock.unlock
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Serializes and send a request over the underlying connection.
|
|
155
|
+
#
|
|
156
|
+
# Returns a future that will resolve to the response. When the connection
|
|
157
|
+
# closes the futures of all active requests will be failed with the error
|
|
158
|
+
# that caused the connection to close, or nil.
|
|
159
|
+
#
|
|
160
|
+
# When `timeout` is specified the future will fail with
|
|
161
|
+
# {Cassandra::Errors::TimeoutError} after that many seconds have passed. If a
|
|
162
|
+
# response arrives after that time it will be lost. If a response never arrives
|
|
163
|
+
# for the request the channel occupied by the request will _not_ be reused.
|
|
164
|
+
#
|
|
165
|
+
# @param [Cassandra::Protocol::Request] request
|
|
166
|
+
# @param [Float] timeout an optional number of seconds to wait until
|
|
167
|
+
# failing the request
|
|
168
|
+
# @return [Ione::Future<Cassandra::Protocol::Response>] a future that resolves to
|
|
169
|
+
# the response
|
|
170
|
+
def send_request(request, timeout = nil, with_heartbeat = true)
|
|
171
|
+
return Ione::Future.failed(Errors::IOError.new('Connection closed')) if closed?
|
|
172
|
+
schedule_heartbeat if with_heartbeat
|
|
173
|
+
promise = RequestPromise.new(request, timeout, @scheduler)
|
|
174
|
+
id = nil
|
|
175
|
+
@lock.lock
|
|
176
|
+
begin
|
|
177
|
+
if (id = next_stream_id)
|
|
178
|
+
@promises[id] = promise
|
|
179
|
+
end
|
|
180
|
+
ensure
|
|
181
|
+
@lock.unlock
|
|
182
|
+
end
|
|
183
|
+
if id
|
|
184
|
+
write_request(id, promise)
|
|
185
|
+
else
|
|
186
|
+
@lock.lock
|
|
187
|
+
begin
|
|
188
|
+
@request_queue_in << promise
|
|
189
|
+
ensure
|
|
190
|
+
@lock.unlock
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
promise.future
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Closes the underlying connection.
|
|
197
|
+
#
|
|
198
|
+
# @return [Ione::Future] a future that completes when the connection has closed
|
|
199
|
+
def close(cause = nil)
|
|
200
|
+
if @heartbeat
|
|
201
|
+
@scheduler.cancel_timer(@heartbeat)
|
|
202
|
+
@heartbeat = nil
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
if @terminate
|
|
206
|
+
@scheduler.cancel_timer(@terminate)
|
|
207
|
+
@terminate = nil
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
@scheduler.schedule_timer(0).on_value do
|
|
211
|
+
@connection.close(cause)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
@closed_promise.future
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def notify_event_listeners(event_response)
|
|
218
|
+
event_listeners = nil
|
|
219
|
+
@lock.lock
|
|
220
|
+
begin
|
|
221
|
+
event_listeners = @event_listeners
|
|
222
|
+
return if event_listeners.empty?
|
|
223
|
+
ensure
|
|
224
|
+
@lock.unlock
|
|
225
|
+
end
|
|
226
|
+
event_listeners.each do |listener|
|
|
227
|
+
listener.call(event_response)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def complete_request(id, response)
|
|
232
|
+
promise = nil
|
|
233
|
+
@lock.lock
|
|
234
|
+
begin
|
|
235
|
+
promise = @promises.delete(id)
|
|
236
|
+
@streams.unshift(id)
|
|
237
|
+
ensure
|
|
238
|
+
@lock.unlock
|
|
239
|
+
end
|
|
240
|
+
@keyspace = response.keyspace if response.is_a?(Protocol::SetKeyspaceResultResponse)
|
|
241
|
+
if response.is_a?(Protocol::SchemaChangeResultResponse) &&
|
|
242
|
+
response.change == 'DROPPED' &&
|
|
243
|
+
response.keyspace == @keyspace &&
|
|
244
|
+
response.target == Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE
|
|
245
|
+
@keyspace = nil
|
|
246
|
+
end
|
|
247
|
+
flush_request_queue
|
|
248
|
+
promise.fulfill(response) unless promise.timed_out?
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
private
|
|
252
|
+
|
|
253
|
+
# @private
|
|
254
|
+
class RequestPromise < Ione::Promise
|
|
255
|
+
extend AttrBoolean
|
|
256
|
+
|
|
257
|
+
attr_reader :request, :timeout
|
|
258
|
+
attr_boolean :timed_out
|
|
259
|
+
attr_accessor :timer
|
|
260
|
+
|
|
261
|
+
def initialize(request, timeout, scheduler)
|
|
262
|
+
@request = request
|
|
263
|
+
@timeout = timeout
|
|
264
|
+
@timed_out = false
|
|
265
|
+
@scheduler = scheduler
|
|
266
|
+
@lock = Mutex.new
|
|
267
|
+
@timer = nil
|
|
268
|
+
super()
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def time_out!
|
|
272
|
+
unless future.completed?
|
|
273
|
+
@timed_out = true
|
|
274
|
+
fail(Errors::TimeoutError.new('Timed out'))
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def maybe_start_timer
|
|
279
|
+
# This is more complicated than one would expect. First, we want to start a timer
|
|
280
|
+
# if a timeout is set. But there is a race condition where send_request creates
|
|
281
|
+
# a fresh promise and adds it to @promises, but another thread handles a socket
|
|
282
|
+
# closure event and fails all known promises. When a promise fails, we want to cancel
|
|
283
|
+
# the timer, if set. So, we synchronize access to @timer to be sure we don't set up
|
|
284
|
+
# and cancel the timer at the same time. However, if promise.fail runs first, there
|
|
285
|
+
# will be no timer to cancel, and then when maybe_start_timer gets called in the other
|
|
286
|
+
# thread, it'll create a timer on a promise that no one is going to action on going forward.
|
|
287
|
+
# So, that leads to leaking the timer until it times out. To avoid this, we want to
|
|
288
|
+
# check that the future of the promise isn't completed before starting the timer.
|
|
289
|
+
|
|
290
|
+
return if @timeout.nil?
|
|
291
|
+
return if @future.completed?
|
|
292
|
+
|
|
293
|
+
if @timer.nil?
|
|
294
|
+
@lock.synchronize do
|
|
295
|
+
if @timer.nil?
|
|
296
|
+
return if @future.completed?
|
|
297
|
+
@timer = @scheduler.schedule_timer(@timeout)
|
|
298
|
+
@timer.on_value { time_out! }
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def fulfill(response)
|
|
305
|
+
super
|
|
306
|
+
maybe_cancel_timer
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def fail(cause)
|
|
310
|
+
super
|
|
311
|
+
maybe_cancel_timer
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def maybe_cancel_timer
|
|
315
|
+
return if @timeout.nil?
|
|
316
|
+
timer = nil
|
|
317
|
+
if @timer
|
|
318
|
+
@lock.synchronize do
|
|
319
|
+
if @timer
|
|
320
|
+
timer = @timer
|
|
321
|
+
@timer = nil
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
@scheduler.cancel_timer(timer) if timer
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def receive_data(data)
|
|
330
|
+
reschedule_termination
|
|
331
|
+
@frame_decoder << data
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def flush_request_queue
|
|
335
|
+
@lock.lock
|
|
336
|
+
begin
|
|
337
|
+
if @request_queue_out.empty? && !@request_queue_in.empty?
|
|
338
|
+
@request_queue_out = @request_queue_in
|
|
339
|
+
@request_queue_in = []
|
|
340
|
+
end
|
|
341
|
+
ensure
|
|
342
|
+
@lock.unlock
|
|
343
|
+
end
|
|
344
|
+
loop do
|
|
345
|
+
id = nil
|
|
346
|
+
promise = nil
|
|
347
|
+
@lock.lock
|
|
348
|
+
begin
|
|
349
|
+
if @request_queue_out.any? && (id = next_stream_id)
|
|
350
|
+
promise = @request_queue_out.shift
|
|
351
|
+
next if promise.timed_out?
|
|
352
|
+
@promises[id] = promise
|
|
353
|
+
end
|
|
354
|
+
ensure
|
|
355
|
+
@lock.unlock
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
break unless id
|
|
359
|
+
write_request(id, promise)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def write_request(id, request_promise)
|
|
364
|
+
@connection.write do |buffer|
|
|
365
|
+
@frame_encoder.encode(buffer, request_promise.request, id)
|
|
366
|
+
end
|
|
367
|
+
request_promise.maybe_start_timer
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def socket_closed(cause)
|
|
371
|
+
if cause
|
|
372
|
+
e = Errors::IOError.new(cause.message)
|
|
373
|
+
e.set_backtrace(cause.backtrace)
|
|
374
|
+
|
|
375
|
+
cause = e
|
|
376
|
+
end
|
|
377
|
+
@error = cause
|
|
378
|
+
|
|
379
|
+
request_failure_cause = cause || Errors::IOError.new('Connection closed')
|
|
380
|
+
promises_to_fail = nil
|
|
381
|
+
@lock.synchronize do
|
|
382
|
+
@scheduler.cancel_timer(@heartbeat) if @heartbeat
|
|
383
|
+
@scheduler.cancel_timer(@terminate) if @terminate
|
|
384
|
+
|
|
385
|
+
@heartbeat = nil
|
|
386
|
+
@terminate = nil
|
|
387
|
+
|
|
388
|
+
promises_to_fail = @promises.values
|
|
389
|
+
promises_to_fail.concat(@request_queue_in)
|
|
390
|
+
promises_to_fail.concat(@request_queue_out)
|
|
391
|
+
@promises.clear
|
|
392
|
+
@request_queue_in.clear
|
|
393
|
+
@request_queue_out.clear
|
|
394
|
+
end
|
|
395
|
+
promises_to_fail.each do |promise|
|
|
396
|
+
promise.fail(request_failure_cause) unless promise.timed_out?
|
|
397
|
+
end
|
|
398
|
+
if cause
|
|
399
|
+
@closed_promise.fail(cause)
|
|
400
|
+
else
|
|
401
|
+
@closed_promise.fulfill
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def schedule_heartbeat
|
|
406
|
+
return unless @heartbeat_interval
|
|
407
|
+
|
|
408
|
+
timer = nil
|
|
409
|
+
|
|
410
|
+
@lock.synchronize do
|
|
411
|
+
@scheduler.cancel_timer(@heartbeat) if @heartbeat && !@heartbeat.resolved?
|
|
412
|
+
|
|
413
|
+
@heartbeat = timer = @scheduler.schedule_timer(@heartbeat_interval)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
timer.on_value do
|
|
417
|
+
send_request(HEARTBEAT, nil, false).on_value do
|
|
418
|
+
schedule_heartbeat
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def reschedule_termination
|
|
424
|
+
return unless @idle_timeout
|
|
425
|
+
|
|
426
|
+
timer = nil
|
|
427
|
+
|
|
428
|
+
@lock.synchronize do
|
|
429
|
+
@scheduler.cancel_timer(@terminate) if @terminate
|
|
430
|
+
|
|
431
|
+
@terminate = timer = @scheduler.schedule_timer(@idle_timeout)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
timer.on_value do
|
|
435
|
+
@terminate = nil
|
|
436
|
+
@connection.close(TERMINATED)
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def next_stream_id
|
|
441
|
+
@streams.shift
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
HEARTBEAT = OptionsRequest.new
|
|
445
|
+
TERMINATED = Errors::TimeoutError.new('Terminated due to inactivity')
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|