cql-rb 1.0.0.pre4 → 1.0.0.pre5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -3
- data/lib/cql/client.rb +65 -15
- data/lib/cql/io.rb +2 -0
- data/lib/cql/io/io_reactor.rb +90 -304
- data/lib/cql/io/node_connection.rb +206 -0
- data/lib/cql/protocol/encoding.rb +1 -0
- data/lib/cql/protocol/request_frame.rb +26 -0
- data/lib/cql/protocol/response_frame.rb +17 -0
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client_spec.rb +95 -56
- data/spec/cql/io/io_reactor_spec.rb +47 -37
- data/spec/cql/protocol/encoding_spec.rb +5 -0
- data/spec/cql/protocol/request_frame_spec.rb +74 -0
- data/spec/cql/protocol/response_frame_spec.rb +18 -0
- data/spec/integration/client_spec.rb +46 -9
- data/spec/integration/protocol_spec.rb +83 -14
- data/spec/integration/regression_spec.rb +2 -2
- data/spec/spec_helper.rb +6 -0
- metadata +3 -2
data/README.md
CHANGED
@@ -20,15 +20,14 @@ The native transport protocol (sometimes called binary protocol, or CQL protocol
|
|
20
20
|
|
21
21
|
require 'cql'
|
22
22
|
|
23
|
-
client = Cql::Client.
|
24
|
-
client.start!
|
23
|
+
client = Cql::Client.connect(host: 'cassandra.example.com')
|
25
24
|
client.use('system')
|
26
25
|
rows = client.execute('SELECT keyspace_name, columnfamily_name FROM schema_columnfamilies')
|
27
26
|
rows.each do |row|
|
28
27
|
puts "The keyspace #{row['keyspace_name']} has a table called #{row['columnfamily_name']}""
|
29
28
|
end
|
30
29
|
|
31
|
-
when you're done you can call `#
|
30
|
+
when you're done you can call `#close` to disconnect from Cassandra. You can connect to multiple Cassandra nodes by passing multiple comma separated host names to the `:host` option.
|
32
31
|
|
33
32
|
## Changing keyspaces
|
34
33
|
|
data/lib/cql/client.rb
CHANGED
@@ -11,15 +11,14 @@ module Cql
|
|
11
11
|
end
|
12
12
|
|
13
13
|
ClientError = Class.new(CqlError)
|
14
|
+
AuthenticationError = Class.new(ClientError)
|
14
15
|
|
15
16
|
# A CQL client manages connections to one or more Cassandra nodes and you use
|
16
17
|
# it run queries, insert and update data.
|
17
18
|
#
|
18
19
|
# @example Connecting and changing to a keyspace
|
19
|
-
# # create a client
|
20
|
-
# client = Cql::Client.
|
21
|
-
# # establish node connections
|
22
|
-
# client.start!
|
20
|
+
# # create a client and connect to two Cassandra nodes
|
21
|
+
# client = Cql::Client.connect(host: 'node01.cassandra.local,node02.cassandra.local')
|
23
22
|
# # change to a keyspace
|
24
23
|
# client.use('stuff')
|
25
24
|
#
|
@@ -47,8 +46,8 @@ module Cql
|
|
47
46
|
# Create a new client.
|
48
47
|
#
|
49
48
|
# Creating a client does not automatically connect to Cassandra, you need to
|
50
|
-
# call {#
|
51
|
-
# call after `new`.
|
49
|
+
# call {#connect} to connect, or use {Client.connect}. `#connect` returns
|
50
|
+
# `self` so you can chain that call after `new`.
|
52
51
|
#
|
53
52
|
# @param [Hash] options
|
54
53
|
# @option options [String] :host ('localhost') One or more (comma separated)
|
@@ -67,9 +66,14 @@ module Cql
|
|
67
66
|
@started = false
|
68
67
|
@shut_down = false
|
69
68
|
@initial_keyspace = options[:keyspace]
|
69
|
+
@credentials = options[:credentials]
|
70
70
|
@connection_keyspaces = {}
|
71
71
|
end
|
72
72
|
|
73
|
+
def self.connect(options={})
|
74
|
+
new(options).connect
|
75
|
+
end
|
76
|
+
|
73
77
|
# Connect to all nodes.
|
74
78
|
#
|
75
79
|
# You must call this method before you call any of the other methods of a
|
@@ -81,29 +85,37 @@ module Cql
|
|
81
85
|
#
|
82
86
|
# @return self
|
83
87
|
#
|
84
|
-
def
|
88
|
+
def connect
|
85
89
|
@lock.synchronize do
|
86
90
|
return if @started
|
91
|
+
@started = true
|
87
92
|
@io_reactor.start
|
88
93
|
hosts = @host.split(',')
|
89
|
-
|
90
|
-
connection_futures = hosts.map do |host|
|
91
|
-
@io_reactor.add_connection(host, @port).flat_map do |connection_id|
|
92
|
-
execute_request(start_request, connection_id).map { connection_id }
|
93
|
-
end
|
94
|
-
end
|
94
|
+
connection_futures = hosts.map { |host| connect_to_host(host) }
|
95
95
|
@connection_ids = Future.combine(*connection_futures).get
|
96
|
-
@started = true
|
97
96
|
end
|
98
97
|
use(@initial_keyspace) if @initial_keyspace
|
99
98
|
self
|
99
|
+
rescue => e
|
100
|
+
close
|
101
|
+
if e.is_a?(Cql::QueryError) && e.code == 0x100
|
102
|
+
raise AuthenticationError, e.message, e.backtrace
|
103
|
+
else
|
104
|
+
raise
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# @deprecated Use {#connect} or {.connect}
|
109
|
+
def start!
|
110
|
+
$stderr.puts('Client#start! is deprecated, use Client#connect, or Client.connect')
|
111
|
+
connect
|
100
112
|
end
|
101
113
|
|
102
114
|
# Disconnect from all nodes.
|
103
115
|
#
|
104
116
|
# @return self
|
105
117
|
#
|
106
|
-
def
|
118
|
+
def close
|
107
119
|
@lock.synchronize do
|
108
120
|
return if @shut_down || !@started
|
109
121
|
@shut_down = true
|
@@ -113,6 +125,12 @@ module Cql
|
|
113
125
|
self
|
114
126
|
end
|
115
127
|
|
128
|
+
# @deprecated Use {#close}
|
129
|
+
def shutdown!
|
130
|
+
$stderr.puts('Client#shutdown! is deprecated, use Client#close')
|
131
|
+
close
|
132
|
+
end
|
133
|
+
|
116
134
|
# Returns whether or not the client is connected.
|
117
135
|
#
|
118
136
|
def connected?
|
@@ -196,6 +214,28 @@ module Cql
|
|
196
214
|
true
|
197
215
|
end
|
198
216
|
|
217
|
+
def connect_to_host(host)
|
218
|
+
connected = @io_reactor.add_connection(host, @port)
|
219
|
+
connected.flat_map do |connection_id|
|
220
|
+
started = execute_request(Protocol::StartupRequest.new, connection_id)
|
221
|
+
started.flat_map { |response| maybe_authenticate(response, connection_id) }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def maybe_authenticate(response, connection_id)
|
226
|
+
case response
|
227
|
+
when AuthenticationRequired
|
228
|
+
if @credentials
|
229
|
+
credentials_request = Protocol::CredentialsRequest.new(@credentials)
|
230
|
+
execute_request(credentials_request, connection_id).map { connection_id }
|
231
|
+
else
|
232
|
+
Future.failed(AuthenticationError.new('Server requested authentication, but no credentials given'))
|
233
|
+
end
|
234
|
+
else
|
235
|
+
Future.completed(connection_id)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
199
239
|
def execute_request(request, connection_id=nil)
|
200
240
|
@io_reactor.queue_request(request, connection_id).map do |response, connection_id|
|
201
241
|
interpret_response!(response, connection_id)
|
@@ -215,6 +255,8 @@ module Cql
|
|
215
255
|
@last_keyspace_change = @connection_keyspaces[connection_id] = response.keyspace
|
216
256
|
end
|
217
257
|
nil
|
258
|
+
when Protocol::AuthenticateResponse
|
259
|
+
AuthenticationRequired.new(response.authentication_class)
|
218
260
|
else
|
219
261
|
nil
|
220
262
|
end
|
@@ -264,6 +306,14 @@ module Cql
|
|
264
306
|
end
|
265
307
|
end
|
266
308
|
|
309
|
+
class AuthenticationRequired
|
310
|
+
attr_reader :authentication_class
|
311
|
+
|
312
|
+
def initialize(authentication_class)
|
313
|
+
@authentication_class = authentication_class
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
267
317
|
class QueryResult
|
268
318
|
include Enumerable
|
269
319
|
|
data/lib/cql/io.rb
CHANGED
@@ -5,6 +5,7 @@ module Cql
|
|
5
5
|
|
6
6
|
module Io
|
7
7
|
ConnectionError = Class.new(IoError)
|
8
|
+
ConnectionTimeoutError = Class.new(ConnectionError)
|
8
9
|
NotRunningError = Class.new(CqlError)
|
9
10
|
ConnectionNotFoundError = Class.new(CqlError)
|
10
11
|
ConnectionBusyError = Class.new(CqlError)
|
@@ -12,3 +13,4 @@ module Cql
|
|
12
13
|
end
|
13
14
|
|
14
15
|
require 'cql/io/io_reactor'
|
16
|
+
require 'cql/io/node_connection'
|
data/lib/cql/io/io_reactor.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'socket'
|
4
|
-
require 'resolv-replace'
|
5
|
-
|
6
|
-
|
7
3
|
module Cql
|
8
4
|
module Io
|
9
5
|
# An instance of IO reactor manages the connections used by a client.
|
@@ -20,9 +16,9 @@ module Cql
|
|
20
16
|
def initialize(options={})
|
21
17
|
@connection_timeout = options[:connection_timeout] || 5
|
22
18
|
@lock = Mutex.new
|
23
|
-
@streams = []
|
24
19
|
@command_queue = []
|
25
|
-
@
|
20
|
+
@unblocker = UnblockerConnection.new(*IO.pipe)
|
21
|
+
@connections = [@unblocker]
|
26
22
|
@started_future = Future.new
|
27
23
|
@stopped_future = Future.new
|
28
24
|
@running = false
|
@@ -45,7 +41,6 @@ module Cql
|
|
45
41
|
@lock.synchronize do
|
46
42
|
unless @running
|
47
43
|
@running = true
|
48
|
-
@streams << CommandDispatcher.new(@queue_signal_receiver, @command_queue, @lock, @streams)
|
49
44
|
@reactor_thread = Thread.start do
|
50
45
|
begin
|
51
46
|
@started_future.complete!
|
@@ -70,6 +65,7 @@ module Cql
|
|
70
65
|
#
|
71
66
|
def stop
|
72
67
|
@running = false
|
68
|
+
command_queue_push(nil)
|
73
69
|
@stopped_future
|
74
70
|
end
|
75
71
|
|
@@ -82,20 +78,17 @@ module Cql
|
|
82
78
|
#
|
83
79
|
def add_connection(host, port)
|
84
80
|
connection = NodeConnection.new(host, port, @connection_timeout)
|
85
|
-
|
86
|
-
future.on_failure do
|
81
|
+
connection.on_close do
|
87
82
|
@lock.synchronize do
|
88
|
-
@
|
83
|
+
@connections.delete(connection)
|
89
84
|
end
|
90
85
|
end
|
91
|
-
|
92
|
-
command_queue_push(:connection_established, connection)
|
93
|
-
end
|
86
|
+
f = connection.open
|
94
87
|
@lock.synchronize do
|
95
|
-
@
|
88
|
+
@connections << connection
|
96
89
|
end
|
97
|
-
command_queue_push(
|
98
|
-
|
90
|
+
command_queue_push(nil)
|
91
|
+
f
|
99
92
|
end
|
100
93
|
|
101
94
|
# Sends a request over a random, or specific connection.
|
@@ -106,9 +99,9 @@ module Cql
|
|
106
99
|
# @return [Future<ResultResponse>] a future representing the result of the request
|
107
100
|
#
|
108
101
|
def queue_request(request, connection_id=nil)
|
109
|
-
|
110
|
-
command_queue_push(
|
111
|
-
future
|
102
|
+
command = connection_id ? TargetedRequestCommand.new(request, connection_id) : RequestCommand.new(request)
|
103
|
+
command_queue_push(command)
|
104
|
+
command.future
|
112
105
|
end
|
113
106
|
|
114
107
|
# Registers a listener to receive server sent events.
|
@@ -116,258 +109,133 @@ module Cql
|
|
116
109
|
# @yieldparam [Cql::Protocol::EventResponse] event the event sent by the server
|
117
110
|
#
|
118
111
|
def add_event_listener(&listener)
|
119
|
-
command_queue_push(
|
112
|
+
command_queue_push(EventListenerCommand.new(listener))
|
120
113
|
end
|
121
114
|
|
122
115
|
private
|
123
116
|
|
124
|
-
PING_BYTE = "\0".freeze
|
125
|
-
|
126
117
|
def io_loop
|
127
118
|
while running?
|
128
|
-
read_ready_streams = @
|
129
|
-
write_ready_streams = @
|
119
|
+
read_ready_streams = @connections.select(&:connected?)
|
120
|
+
write_ready_streams = @connections.select(&:can_write?)
|
130
121
|
readables, writables, _ = IO.select(read_ready_streams, write_ready_streams, nil, 1)
|
131
122
|
readables && readables.each(&:handle_read)
|
132
123
|
writables && writables.each(&:handle_write)
|
133
|
-
@
|
134
|
-
|
124
|
+
@connections.each do |connection|
|
125
|
+
if connection.connecting?
|
126
|
+
connection.handle_connecting
|
127
|
+
end
|
128
|
+
end
|
129
|
+
if running?
|
130
|
+
perform_queued_commands
|
131
|
+
end
|
135
132
|
end
|
136
133
|
ensure
|
137
134
|
stop
|
138
|
-
@
|
135
|
+
@connections.dup.each do |connection|
|
139
136
|
begin
|
140
|
-
|
141
|
-
rescue IOError
|
137
|
+
connection.close
|
138
|
+
rescue IOError => e
|
142
139
|
end
|
143
140
|
end
|
144
141
|
end
|
145
142
|
|
146
|
-
def command_queue_push(
|
147
|
-
if
|
143
|
+
def command_queue_push(command)
|
144
|
+
if command
|
148
145
|
@lock.synchronize do
|
149
|
-
@command_queue <<
|
146
|
+
@command_queue << command
|
150
147
|
end
|
151
148
|
end
|
152
|
-
@
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# @private
|
157
|
-
class NodeConnection
|
158
|
-
def initialize(*args)
|
159
|
-
@host, @port, @connection_timeout = args
|
160
|
-
@connected_future = Future.new
|
161
|
-
@io = nil
|
162
|
-
@addrinfo = nil
|
163
|
-
@write_buffer = ''
|
164
|
-
@read_buffer = ''
|
165
|
-
@current_frame = Protocol::ResponseFrame.new(@read_buffer)
|
166
|
-
@response_tasks = [nil] * 128
|
167
|
-
@event_listeners = Hash.new { |h, k| h[k] = [] }
|
149
|
+
@unblocker.unblock!
|
168
150
|
end
|
169
151
|
|
170
|
-
def
|
171
|
-
@
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
152
|
+
def perform_queued_commands
|
153
|
+
@lock.synchronize do
|
154
|
+
unexecuted_commands = []
|
155
|
+
while (command = @command_queue.shift)
|
156
|
+
case command
|
157
|
+
when EventListenerCommand
|
158
|
+
@connections.each do |connection|
|
159
|
+
connection.on_event(&command.listener)
|
160
|
+
end
|
161
|
+
when TargetedRequestCommand
|
162
|
+
connection = @connections.find { |c| c.connection_id == command.connection_id }
|
163
|
+
if connection && connection.connected? && connection.has_capacity?
|
164
|
+
connection.perform_request(command.request, command.future)
|
165
|
+
elsif connection && connection.connected?
|
166
|
+
command.future.fail!(ConnectionBusyError.new("Connection ##{command.connection_id} is busy"))
|
167
|
+
else
|
168
|
+
command.future.fail!(ConnectionNotFoundError.new("Connection ##{command.connection_id} does not exist"))
|
169
|
+
end
|
170
|
+
when RequestCommand
|
171
|
+
connection = @connections.select(&:has_capacity?).sample
|
172
|
+
if connection
|
173
|
+
connection.perform_request(command.request, command.future)
|
174
|
+
else
|
175
|
+
unexecuted_commands << command
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
@command_queue.unshift(*unexecuted_commands) if unexecuted_commands.any?
|
182
180
|
end
|
183
|
-
@connected_future
|
184
|
-
end
|
185
|
-
|
186
|
-
def connection_id
|
187
|
-
self.object_id
|
188
|
-
end
|
189
|
-
|
190
|
-
def to_io
|
191
|
-
@io
|
192
|
-
end
|
193
|
-
|
194
|
-
def on_event(&listener)
|
195
|
-
@event_listeners[:event] << listener
|
196
|
-
end
|
197
|
-
|
198
|
-
def on_close(&listener)
|
199
|
-
@event_listeners[:close] << listener
|
200
181
|
end
|
182
|
+
end
|
201
183
|
|
202
|
-
|
203
|
-
|
204
|
-
fail_connection!
|
205
|
-
end
|
206
|
-
end
|
184
|
+
class EventListenerCommand
|
185
|
+
attr_reader :listener
|
207
186
|
|
208
|
-
def
|
209
|
-
@
|
187
|
+
def initialize(listener)
|
188
|
+
@listener = listener
|
210
189
|
end
|
190
|
+
end
|
211
191
|
|
212
|
-
|
213
|
-
|
214
|
-
end
|
192
|
+
class RequestCommand
|
193
|
+
attr_reader :future, :request
|
215
194
|
|
216
|
-
def
|
217
|
-
|
195
|
+
def initialize(request)
|
196
|
+
@request = request
|
197
|
+
@future = Future.new
|
218
198
|
end
|
199
|
+
end
|
219
200
|
|
220
|
-
|
221
|
-
|
222
|
-
end
|
201
|
+
class TargetedRequestCommand < RequestCommand
|
202
|
+
attr_reader :connection_id
|
223
203
|
|
224
|
-
def
|
225
|
-
|
226
|
-
|
227
|
-
@response_tasks[stream_id] = future
|
228
|
-
rescue => e
|
229
|
-
case e
|
230
|
-
when CqlError
|
231
|
-
error = e
|
232
|
-
else
|
233
|
-
error = IoError.new(e.message)
|
234
|
-
error.set_backtrace(e.backtrace)
|
235
|
-
end
|
236
|
-
@response_tasks.delete(stream_id)
|
237
|
-
future.fail!(error)
|
204
|
+
def initialize(request, connection_id)
|
205
|
+
super(request)
|
206
|
+
@connection_id = connection_id
|
238
207
|
end
|
208
|
+
end
|
239
209
|
|
240
|
-
|
241
|
-
|
242
|
-
@
|
243
|
-
while @current_frame.complete?
|
244
|
-
stream_id = @current_frame.stream_id
|
245
|
-
if stream_id == EVENT_STREAM_ID
|
246
|
-
@event_listeners[:event].each { |listener| listener.call(@current_frame.body) }
|
247
|
-
elsif @response_tasks[stream_id]
|
248
|
-
@response_tasks[stream_id].complete!([@current_frame.body, connection_id])
|
249
|
-
@response_tasks[stream_id] = nil
|
250
|
-
else
|
251
|
-
# TODO dropping the request on the floor here, but we didn't send it
|
252
|
-
end
|
253
|
-
@current_frame = Protocol::ResponseFrame.new(@read_buffer)
|
254
|
-
end
|
255
|
-
rescue => e
|
256
|
-
force_close(e)
|
210
|
+
class UnblockerConnection
|
211
|
+
def initialize(*args)
|
212
|
+
@out, @in = args
|
257
213
|
end
|
258
214
|
|
259
|
-
def
|
260
|
-
|
261
|
-
handle_connected
|
262
|
-
elsif connected?
|
263
|
-
bytes_written = @io.write_nonblock(@write_buffer)
|
264
|
-
@write_buffer.slice!(0, bytes_written)
|
265
|
-
end
|
266
|
-
rescue => e
|
267
|
-
force_close(e)
|
215
|
+
def unblock!
|
216
|
+
@in.write(PING_BYTE)
|
268
217
|
end
|
269
218
|
|
270
|
-
def
|
271
|
-
|
272
|
-
when CqlError
|
273
|
-
error = e
|
274
|
-
else
|
275
|
-
error = IoError.new(e.message)
|
276
|
-
error.set_backtrace(e.backtrace)
|
277
|
-
end
|
278
|
-
@response_tasks.each do |listener|
|
279
|
-
listener.fail!(error) if listener
|
280
|
-
end
|
281
|
-
close
|
219
|
+
def to_io
|
220
|
+
@out
|
282
221
|
end
|
283
222
|
|
284
223
|
def close
|
285
|
-
if @io
|
286
|
-
begin
|
287
|
-
@io.close
|
288
|
-
rescue SystemCallError
|
289
|
-
# nothing to do, it wasn't open
|
290
|
-
end
|
291
|
-
@io = nil
|
292
|
-
if connecting?
|
293
|
-
succeed_connection!
|
294
|
-
end
|
295
|
-
@event_listeners[:close].each { |listener| listener.call(self) }
|
296
|
-
end
|
297
224
|
end
|
298
225
|
|
299
|
-
def
|
300
|
-
state = begin
|
301
|
-
if connected? then 'connected'
|
302
|
-
elsif connecting? then 'connecting'
|
303
|
-
else 'not connected'
|
304
|
-
end
|
305
|
-
end
|
306
|
-
%<NodeConnection(#{@host}:#{@port}, #{state})>
|
307
|
-
end
|
308
|
-
|
309
|
-
private
|
310
|
-
|
311
|
-
EVENT_STREAM_ID = -1
|
312
|
-
|
313
|
-
def connecting?
|
314
|
-
!(@connected_future.complete? || @connected_future.failed?)
|
315
|
-
end
|
316
|
-
|
317
|
-
def handle_connected
|
318
|
-
@io.connect_nonblock(@sockaddr)
|
319
|
-
succeed_connection!
|
320
|
-
rescue Errno::EISCONN
|
321
|
-
# ok
|
322
|
-
succeed_connection!
|
323
|
-
rescue SystemCallError, SocketError => e
|
324
|
-
fail_connection!(e)
|
325
|
-
end
|
326
|
-
|
327
|
-
def succeed_connection!
|
328
|
-
@connected_future.complete!(connection_id)
|
329
|
-
end
|
330
|
-
|
331
|
-
def fail_connection!(e=nil)
|
332
|
-
message = "Could not connect to #{@host}:#{@port}"
|
333
|
-
message << ": #{e.message} (#{e.class.name})" if e
|
334
|
-
error = ConnectionError.new(message)
|
335
|
-
error.set_backtrace(e.backtrace) if e
|
336
|
-
@connected_future.fail!(error)
|
337
|
-
close
|
338
|
-
end
|
339
|
-
|
340
|
-
def next_stream_id
|
341
|
-
@response_tasks.each_with_index do |task, index|
|
342
|
-
return index if task.nil?
|
343
|
-
end
|
344
|
-
nil
|
345
|
-
end
|
346
|
-
end
|
226
|
+
def on_event; end
|
347
227
|
|
348
|
-
|
349
|
-
class CommandDispatcher
|
350
|
-
def initialize(*args)
|
351
|
-
@io, @command_queue, @queue_lock, @node_connections = args
|
352
|
-
end
|
228
|
+
def on_close; end
|
353
229
|
|
354
230
|
def connection_id
|
355
231
|
-1
|
356
232
|
end
|
357
233
|
|
358
|
-
def to_io
|
359
|
-
@io
|
360
|
-
end
|
361
|
-
|
362
234
|
def connected?
|
363
235
|
true
|
364
236
|
end
|
365
237
|
|
366
|
-
def
|
367
|
-
false
|
368
|
-
end
|
369
|
-
|
370
|
-
def has_capacity?
|
238
|
+
def connecting?
|
371
239
|
false
|
372
240
|
end
|
373
241
|
|
@@ -375,99 +243,17 @@ module Cql
|
|
375
243
|
false
|
376
244
|
end
|
377
245
|
|
378
|
-
def
|
379
|
-
|
380
|
-
def on_close; end
|
381
|
-
|
382
|
-
def ping
|
383
|
-
if can_deliver_command?
|
384
|
-
deliver_commands
|
385
|
-
else
|
386
|
-
prune_directed_requests!
|
387
|
-
end
|
246
|
+
def has_capacity?
|
247
|
+
false
|
388
248
|
end
|
389
249
|
|
390
250
|
def handle_read
|
391
|
-
|
392
|
-
deliver_commands
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
|
-
def handle_write
|
397
|
-
end
|
398
|
-
|
399
|
-
def close
|
400
|
-
@io.close
|
401
|
-
end
|
402
|
-
|
403
|
-
def to_s
|
404
|
-
%(CommandDispatcher)
|
251
|
+
@out.read_nonblock(2**16)
|
405
252
|
end
|
406
253
|
|
407
254
|
private
|
408
255
|
|
409
|
-
|
410
|
-
@node_connections.any?(&:has_capacity?) && @command_queue.size > 0
|
411
|
-
end
|
412
|
-
|
413
|
-
def next_command
|
414
|
-
@queue_lock.synchronize do
|
415
|
-
if can_deliver_command?
|
416
|
-
return @command_queue.shift
|
417
|
-
end
|
418
|
-
end
|
419
|
-
nil
|
420
|
-
end
|
421
|
-
|
422
|
-
def deliver_commands
|
423
|
-
while (command = next_command)
|
424
|
-
case command.shift
|
425
|
-
when :connection_added
|
426
|
-
when :connection_established
|
427
|
-
connection = command.shift
|
428
|
-
connection.on_close(&method(:connection_closed))
|
429
|
-
when :event_listener
|
430
|
-
listener = command.shift
|
431
|
-
@node_connections.each { |c| c.on_event(&listener) }
|
432
|
-
when :request
|
433
|
-
request, future, connection_id = command
|
434
|
-
if connection_id
|
435
|
-
connection = @node_connections.find { |c| c.connection_id == connection_id }
|
436
|
-
if connection && connection.connected? && connection.has_capacity?
|
437
|
-
connection.perform_request(request, future)
|
438
|
-
elsif connection && connection.connected?
|
439
|
-
future.fail!(ConnectionBusyError.new("Connection ##{connection_id} is busy"))
|
440
|
-
else
|
441
|
-
future.fail!(ConnectionNotFoundError.new("Connection ##{connection_id} does not exist"))
|
442
|
-
end
|
443
|
-
else
|
444
|
-
@node_connections.select(&:has_capacity?).sample.perform_request(request, future)
|
445
|
-
end
|
446
|
-
end
|
447
|
-
end
|
448
|
-
end
|
449
|
-
|
450
|
-
def prune_directed_requests!
|
451
|
-
failing_commands = []
|
452
|
-
@queue_lock.synchronize do
|
453
|
-
@command_queue.reject! do |command|
|
454
|
-
if command.first == :request && command.last && @node_connections.none? { |c| c.connection_id == command.last }
|
455
|
-
failing_commands << command
|
456
|
-
true
|
457
|
-
else
|
458
|
-
false
|
459
|
-
end
|
460
|
-
end
|
461
|
-
end
|
462
|
-
failing_commands.each do |command|
|
463
|
-
_, _, future, id = command
|
464
|
-
future.fail!(ConnectionNotFoundError.new("Connection ##{id} no longer exists"))
|
465
|
-
end
|
466
|
-
end
|
467
|
-
|
468
|
-
def connection_closed(connection)
|
469
|
-
prune_directed_requests!
|
470
|
-
end
|
256
|
+
PING_BYTE = "\0".freeze
|
471
257
|
end
|
472
258
|
end
|
473
259
|
end
|