cql-rb 1.0.0.pre0

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/lib/cql/io.rb ADDED
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Io
5
+ IoError = Class.new(CqlError)
6
+ ConnectionError = Class.new(IoError)
7
+ NotRunningError = Class.new(CqlError)
8
+ ConnectionNotFoundError = Class.new(CqlError)
9
+ ConnectionBusyError = Class.new(CqlError)
10
+ end
11
+ end
12
+
13
+ require 'cql/io/io_reactor'
@@ -0,0 +1,351 @@
1
+ # encoding: utf-8
2
+
3
+ require 'socket'
4
+ require 'resolv-replace'
5
+
6
+
7
+ module Cql
8
+ module Io
9
+ class IoReactor
10
+ def initialize(options={})
11
+ @connection_timeout = options[:connection_timeout] || 5
12
+ @lock = Mutex.new
13
+ @streams = []
14
+ @command_queue = []
15
+ @queue_signal_receiver, @queue_signal_sender = IO.pipe
16
+ @started_future = Future.new
17
+ @stopped_future = Future.new
18
+ @running = false
19
+ end
20
+
21
+ def running?
22
+ @running
23
+ end
24
+
25
+ def start
26
+ @lock.synchronize do
27
+ unless @running
28
+ @running = true
29
+ @streams << CommandDispatcher.new(@queue_signal_receiver, @command_queue, @lock, @streams)
30
+ @reactor_thread = Thread.start do
31
+ begin
32
+ @started_future.complete!
33
+ io_loop
34
+ @stopped_future.complete!
35
+ rescue => e
36
+ @stopped_future.fail!(e)
37
+ raise
38
+ end
39
+ end
40
+ end
41
+ end
42
+ @started_future
43
+ end
44
+
45
+ def stop
46
+ @running = false
47
+ @stopped_future
48
+ end
49
+
50
+ def add_connection(host, port)
51
+ connection = NodeConnection.new(host, port, @connection_timeout)
52
+ future = connection.open
53
+ future.on_failure do
54
+ @lock.synchronize do
55
+ @streams.delete(connection)
56
+ end
57
+ end
58
+ future.on_complete do
59
+ command_queue_push
60
+ end
61
+ @lock.synchronize do
62
+ @streams << connection
63
+ end
64
+ command_queue_push
65
+ future
66
+ end
67
+
68
+ def queue_request(request, connection_id=nil)
69
+ future = Future.new
70
+ command_queue_push(:request, request, future, connection_id)
71
+ future
72
+ end
73
+
74
+ def add_event_listener(&listener)
75
+ command_queue_push(:event_listener, listener)
76
+ end
77
+
78
+ private
79
+
80
+ PING_BYTE = "\0".freeze
81
+
82
+ def io_loop
83
+ while running?
84
+ read_ready_streams = @streams.select(&:connected?)
85
+ write_ready_streams = @streams.select(&:can_write?)
86
+ readables, writables, _ = IO.select(read_ready_streams, write_ready_streams, nil, 1)
87
+ readables && readables.each(&:handle_read)
88
+ writables && writables.each(&:handle_write)
89
+ @streams.each(&:ping)
90
+ end
91
+ ensure
92
+ stop
93
+ @streams.each do |stream|
94
+ begin
95
+ stream.close
96
+ rescue IOError
97
+ end
98
+ end
99
+ end
100
+
101
+ def command_queue_push(*item)
102
+ if item && item.any?
103
+ @lock.synchronize do
104
+ @command_queue << item
105
+ end
106
+ end
107
+ @queue_signal_sender.write(PING_BYTE)
108
+ end
109
+ end
110
+
111
+ class NodeConnection
112
+ def initialize(*args)
113
+ @host, @port, @connection_timeout = args
114
+ @connected_future = Future.new
115
+ @io = nil
116
+ @addrinfo = nil
117
+ @write_buffer = ''
118
+ @read_buffer = ''
119
+ @current_frame = Protocol::ResponseFrame.new(@read_buffer)
120
+ @response_tasks = [nil] * 128
121
+ @event_listeners = []
122
+ end
123
+
124
+ def open
125
+ @connection_started_at = Time.now
126
+ begin
127
+ addrinfo = Socket.getaddrinfo(@host, @port, Socket::AF_INET, Socket::SOCK_STREAM)
128
+ _, port, _, ip, address_family, socket_type = addrinfo.first
129
+ @sockaddr = Socket.sockaddr_in(port, ip)
130
+ @io = Socket.new(address_family, socket_type, 0)
131
+ @io.connect_nonblock(@sockaddr)
132
+ rescue Errno::EINPROGRESS
133
+ # ok
134
+ rescue SystemCallError, SocketError => e
135
+ fail_connection!(e)
136
+ end
137
+ @connected_future
138
+ end
139
+
140
+ def connection_id
141
+ self.object_id
142
+ end
143
+
144
+ def to_io
145
+ @io
146
+ end
147
+
148
+ def on_event(&listener)
149
+ @event_listeners << listener
150
+ end
151
+
152
+ def ping
153
+ if @io && connecting? && (Time.now - @connection_started_at > @connection_timeout)
154
+ fail_connection!
155
+ end
156
+ end
157
+
158
+ def connected?
159
+ @io && !connecting?
160
+ end
161
+
162
+ def has_capacity?
163
+ !!next_stream_id && connected?
164
+ end
165
+
166
+ def can_write?
167
+ @io && (!@write_buffer.empty? || connecting?)
168
+ end
169
+
170
+ def perform_request(request, future)
171
+ stream_id = next_stream_id
172
+ Protocol::RequestFrame.new(request, stream_id).write(@write_buffer)
173
+ @response_tasks[stream_id] = future
174
+ end
175
+
176
+ def handle_read
177
+ new_bytes = @io.read_nonblock(2**16)
178
+ @current_frame << new_bytes
179
+ while @current_frame.complete?
180
+ stream_id = @current_frame.stream_id
181
+ if stream_id == EVENT_STREAM_ID
182
+ @event_listeners.each { |listener| listener.call(@current_frame.body) }
183
+ elsif @response_tasks[stream_id]
184
+ @response_tasks[stream_id].complete!([@current_frame.body, connection_id])
185
+ @response_tasks[stream_id] = nil
186
+ else
187
+ # TODO dropping the request on the floor here, but we didn't send it
188
+ end
189
+ @current_frame = Protocol::ResponseFrame.new(@read_buffer)
190
+ end
191
+ end
192
+
193
+ def handle_write
194
+ if connecting?
195
+ handle_connected
196
+ else
197
+ bytes_written = @io.write_nonblock(@write_buffer)
198
+ @write_buffer.slice!(0, bytes_written)
199
+ end
200
+ end
201
+
202
+ def close
203
+ if @io
204
+ @io.close
205
+ if connecting?
206
+ succeed_connection!
207
+ end
208
+ end
209
+ end
210
+
211
+ def to_s
212
+ state = begin
213
+ if connected? then 'connected'
214
+ elsif connecting? then 'connecting'
215
+ else 'not connected'
216
+ end
217
+ end
218
+ %<NodeConnection(#{@host}:#{@port}, #{state})>
219
+ end
220
+
221
+ private
222
+
223
+ EVENT_STREAM_ID = -1
224
+
225
+ def connecting?
226
+ !@connected_future.complete?
227
+ end
228
+
229
+ def handle_connected
230
+ @io.connect_nonblock(@sockaddr)
231
+ succeed_connection!
232
+ rescue Errno::EISCONN
233
+ # ok
234
+ succeed_connection!
235
+ rescue SystemCallError, SocketError => e
236
+ fail_connection!(e)
237
+ end
238
+
239
+ def succeed_connection!
240
+ @connected_future.complete!(connection_id)
241
+ end
242
+
243
+ def fail_connection!(e=nil)
244
+ message = "Could not connect to #{@host}:#{@port}"
245
+ message << ": #{e.message} (#{e.class.name})" if e
246
+ error = ConnectionError.new(message)
247
+ error.set_backtrace(e.backtrace) if e
248
+ @connected_future.fail!(error)
249
+ @io.close if @io
250
+ @io = nil
251
+ end
252
+
253
+ def next_stream_id
254
+ @response_tasks.each_with_index do |task, index|
255
+ return index if task.nil?
256
+ end
257
+ nil
258
+ end
259
+ end
260
+
261
+ class CommandDispatcher
262
+ def initialize(*args)
263
+ @io, @command_queue, @queue_lock, @node_connections = args
264
+ end
265
+
266
+ def connection_id
267
+ -1
268
+ end
269
+
270
+ def to_io
271
+ @io
272
+ end
273
+
274
+ def connected?
275
+ true
276
+ end
277
+
278
+ def has_capacity?
279
+ false
280
+ end
281
+
282
+ def can_write?
283
+ false
284
+ end
285
+
286
+ def on_event; end
287
+
288
+ def ping
289
+ if can_deliver_command?
290
+ deliver_commands
291
+ end
292
+ end
293
+
294
+ def handle_read
295
+ if @io.read_nonblock(1)
296
+ deliver_commands
297
+ end
298
+ end
299
+
300
+ def handle_write
301
+ end
302
+
303
+ def close
304
+ @io.close
305
+ end
306
+
307
+ def to_s
308
+ %(CommandDispatcher)
309
+ end
310
+
311
+ private
312
+
313
+ def can_deliver_command?
314
+ @node_connections.any?(&:has_capacity?) && @command_queue.size > 0
315
+ end
316
+
317
+ def next_command
318
+ @queue_lock.synchronize do
319
+ if can_deliver_command?
320
+ return @command_queue.shift
321
+ end
322
+ end
323
+ nil
324
+ end
325
+
326
+ def deliver_commands
327
+ while (command = next_command)
328
+ case command.shift
329
+ when :event_listener
330
+ listener = command.shift
331
+ @node_connections.each { |c| c.on_event(&listener) }
332
+ else
333
+ request, future, connection_id = command
334
+ if connection_id
335
+ connection = @node_connections.find { |c| c.connection_id == connection_id }
336
+ if connection && connection.has_capacity?
337
+ connection.perform_request(request, future)
338
+ elsif connection
339
+ future.fail!(ConnectionBusyError.new("Connection ##{connection_id} is busy"))
340
+ else
341
+ future.fail!(ConnectionNotFoundError.new("Connection ##{connection_id} does not exist"))
342
+ end
343
+ else
344
+ @node_connections.select(&:has_capacity?).sample.perform_request(request, future)
345
+ end
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Protocol
5
+ ProtocolError = Class.new(CqlError)
6
+ DecodingError = Class.new(ProtocolError)
7
+ EncodingError = Class.new(ProtocolError)
8
+ InvalidStreamIdError = Class.new(ProtocolError)
9
+ UnsupportedOperationError = Class.new(ProtocolError)
10
+ UnsupportedFrameTypeError = Class.new(ProtocolError)
11
+ UnsupportedResultKindError = Class.new(ProtocolError)
12
+ UnsupportedColumnTypeError = Class.new(ProtocolError)
13
+ UnsupportedEventTypeError = Class.new(ProtocolError)
14
+
15
+ CONSISTENCIES = [:any, :one, :two, :three, :quorum, :all, :local_quorum, :each_quorum].freeze
16
+
17
+ module Formats
18
+ CHAR_FORMAT = 'c'.freeze
19
+ DOUBLE_FORMAT = 'G'.freeze
20
+ FLOAT_FORMAT = 'g'.freeze
21
+ INT_FORMAT = 'N'.freeze
22
+ SHORT_FORMAT = 'n'.freeze
23
+
24
+ BYTES_FORMAT = 'C*'.freeze
25
+ TWO_INTS_FORMAT = 'NN'.freeze
26
+ HEADER_FORMAT = 'c4N'.freeze
27
+ end
28
+
29
+ module Constants
30
+ TRUE_BYTE = "\x01".freeze
31
+ FALSE_BYTE = "\x00".freeze
32
+ end
33
+ end
34
+ end
35
+
36
+ require 'cql/protocol/encoding'
37
+ require 'cql/protocol/decoding'
38
+ require 'cql/protocol/response_frame'
39
+ require 'cql/protocol/request_frame'
@@ -0,0 +1,156 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Protocol
5
+ module Decoding
6
+ extend self
7
+
8
+ def read_byte!(buffer)
9
+ raise DecodingError, 'No byte available to decode' if buffer.empty?
10
+ b = buffer.slice!(0, 1)
11
+ b.getbyte(0)
12
+ end
13
+
14
+ def read_varint!(buffer, length=buffer.length, signed=true)
15
+ raise DecodingError, "Length #{length} specifed but only #{buffer.size} bytes given" if buffer.size < length
16
+ bytes = buffer.slice!(0, length)
17
+ n = 0
18
+ bytes.each_byte do |b|
19
+ n = (n << 8) | b
20
+ end
21
+ if signed && bytes.getbyte(0) & 0x80 == 0x80
22
+ n -= 2**(bytes.length * 8)
23
+ end
24
+ n
25
+ end
26
+
27
+ def read_decimal!(buffer, length=buffer.length)
28
+ raise DecodingError, "Length #{length} specifed but only #{buffer.size} bytes given" if buffer.size < length
29
+ size = read_int!(buffer)
30
+ number_bytes = buffer.slice!(0, length - 4)
31
+ number_string = read_varint!(number_bytes).to_s
32
+ fraction_string = number_string[0, number_string.length - size] << DECIMAL_POINT << number_string[number_string.length - size, number_string.length]
33
+ BigDecimal.new(fraction_string)
34
+ end
35
+
36
+ def read_long!(buffer)
37
+ raise DecodingError, "Need eight bytes to decode long, only #{buffer.size} bytes given" if buffer.size < 8
38
+ top, bottom = buffer.slice!(0, 8).unpack(Formats::TWO_INTS_FORMAT)
39
+ (top << 32) | bottom
40
+ end
41
+
42
+ def read_double!(buffer)
43
+ raise DecodingError, "Need eight bytes to decode double, only #{buffer.size} bytes given" if buffer.size < 8
44
+ buffer.slice!(0, 8).unpack(Formats::DOUBLE_FORMAT).first
45
+ end
46
+
47
+ def read_float!(buffer)
48
+ raise DecodingError, "Need four bytes to decode float, only #{buffer.size} bytes given" if buffer.size < 4
49
+ buffer.slice!(0, 4).unpack(Formats::FLOAT_FORMAT).first
50
+ end
51
+
52
+ def read_int!(buffer)
53
+ raise DecodingError, "Need four bytes to decode an int, only #{buffer.size} bytes given" if buffer.size < 4
54
+ buffer.slice!(0, 4).unpack(Formats::INT_FORMAT).first
55
+ end
56
+
57
+ def read_short!(buffer)
58
+ raise DecodingError, "Need two bytes to decode a short, only #{buffer.size} bytes given" if buffer.size < 2
59
+ buffer.slice!(0, 2).unpack(Formats::SHORT_FORMAT).first
60
+ end
61
+
62
+ def read_string!(buffer)
63
+ length = read_short!(buffer)
64
+ raise DecodingError, "String length is #{length}, but only #{buffer.size} bytes given" if buffer.size < length
65
+ string = buffer.slice!(0, length)
66
+ string.force_encoding(::Encoding::UTF_8)
67
+ string
68
+ end
69
+
70
+ def read_long_string!(buffer)
71
+ length = read_int!(buffer)
72
+ raise DecodingError, "String length is #{length}, but only #{buffer.size} bytes given" if buffer.size < length
73
+ string = buffer.slice!(0, length)
74
+ string.force_encoding(::Encoding::UTF_8)
75
+ string
76
+ end
77
+
78
+ def read_uuid!(buffer)
79
+ raise DecodingError, "UUID requires 16 bytes, but only #{buffer.size} bytes given" if buffer.size < 16
80
+ Uuid.new(read_varint!(buffer, 16, false))
81
+ end
82
+
83
+ def read_string_list!(buffer)
84
+ size = read_short!(buffer)
85
+ size.times.map do
86
+ read_string!(buffer)
87
+ end
88
+ end
89
+
90
+ def read_bytes!(buffer)
91
+ size = read_int!(buffer)
92
+ return nil if size & 0x80000000 == 0x80000000
93
+ raise DecodingError, "Byte array length is #{size}, but only #{buffer.size} bytes given" if buffer.size < size
94
+ bytes = buffer.slice!(0, size)
95
+ bytes.force_encoding(::Encoding::BINARY)
96
+ bytes
97
+ end
98
+
99
+ def read_short_bytes!(buffer)
100
+ size = read_short!(buffer)
101
+ return nil if size & 0x8000 == 0x8000
102
+ raise DecodingError, "Byte array length is #{size}, but only #{buffer.size} bytes given" if buffer.size < size
103
+ bytes = buffer.slice!(0, size)
104
+ bytes.force_encoding(::Encoding::BINARY)
105
+ bytes
106
+ end
107
+
108
+ def read_option!(buffer)
109
+ id = read_short!(buffer)
110
+ value = nil
111
+ if block_given?
112
+ value = yield id, buffer
113
+ end
114
+ [id, value]
115
+ end
116
+
117
+ def read_inet!(buffer)
118
+ size = read_byte!(buffer)
119
+ raise DecodingError, "Inet requires #{size} bytes, but only #{buffer.size} bytes given" if buffer.size < size
120
+ ip_addr = IPAddr.new_ntoh(buffer.slice!(0, size))
121
+ port = read_int!(buffer)
122
+ [ip_addr, port]
123
+ end
124
+
125
+ def read_consistency!(buffer)
126
+ index = read_short!(buffer)
127
+ raise DecodingError, "Unknown consistency index #{index}" unless index < CONSISTENCIES.size
128
+ CONSISTENCIES[index]
129
+ end
130
+
131
+ def read_string_map!(buffer)
132
+ map = {}
133
+ map_size = read_short!(buffer)
134
+ map_size.times do
135
+ key = read_string!(buffer)
136
+ map[key] = read_string!(buffer)
137
+ end
138
+ map
139
+ end
140
+
141
+ def read_string_multimap!(buffer)
142
+ map = {}
143
+ map_size = read_short!(buffer)
144
+ map_size.times do
145
+ key = read_string!(buffer)
146
+ map[key] = read_string_list!(buffer)
147
+ end
148
+ map
149
+ end
150
+
151
+ private
152
+
153
+ DECIMAL_POINT = '.'.freeze
154
+ end
155
+ end
156
+ end