cql-rb 1.0.0.pre4 → 1.0.0.pre5
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.
- 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
|