cql-rb 2.0.0.pre0 → 2.0.0.pre1
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 +4 -4
- data/README.md +14 -2
- data/lib/cql.rb +8 -3
- data/lib/cql/client.rb +21 -356
- data/lib/cql/client/authenticators.rb +70 -0
- data/lib/cql/client/batch.rb +54 -0
- data/lib/cql/client/{asynchronous_client.rb → client.rb} +241 -6
- data/lib/cql/client/connector.rb +3 -2
- data/lib/cql/client/{asynchronous_prepared_statement.rb → prepared_statement.rb} +103 -0
- data/lib/cql/protocol.rb +1 -2
- data/lib/cql/protocol/cql_byte_buffer.rb +285 -0
- data/lib/cql/protocol/cql_protocol_handler.rb +3 -3
- data/lib/cql/protocol/frame_decoder.rb +3 -3
- data/lib/cql/protocol/frame_encoder.rb +2 -2
- data/lib/cql/protocol/request.rb +0 -2
- data/lib/cql/protocol/requests/auth_response_request.rb +2 -2
- data/lib/cql/protocol/requests/batch_request.rb +10 -10
- data/lib/cql/protocol/requests/credentials_request.rb +2 -2
- data/lib/cql/protocol/requests/execute_request.rb +13 -13
- data/lib/cql/protocol/requests/options_request.rb +2 -2
- data/lib/cql/protocol/requests/prepare_request.rb +2 -2
- data/lib/cql/protocol/requests/query_request.rb +13 -13
- data/lib/cql/protocol/requests/register_request.rb +2 -2
- data/lib/cql/protocol/requests/startup_request.rb +2 -2
- data/lib/cql/protocol/response.rb +2 -4
- data/lib/cql/protocol/responses/auth_challenge_response.rb +2 -2
- data/lib/cql/protocol/responses/auth_success_response.rb +2 -2
- data/lib/cql/protocol/responses/authenticate_response.rb +2 -2
- data/lib/cql/protocol/responses/detailed_error_response.rb +15 -15
- data/lib/cql/protocol/responses/error_response.rb +4 -4
- data/lib/cql/protocol/responses/event_response.rb +3 -3
- data/lib/cql/protocol/responses/prepared_result_response.rb +4 -4
- data/lib/cql/protocol/responses/raw_rows_result_response.rb +1 -1
- data/lib/cql/protocol/responses/ready_response.rb +1 -1
- data/lib/cql/protocol/responses/result_response.rb +3 -3
- data/lib/cql/protocol/responses/rows_result_response.rb +22 -22
- data/lib/cql/protocol/responses/schema_change_event_response.rb +2 -2
- data/lib/cql/protocol/responses/schema_change_result_response.rb +2 -2
- data/lib/cql/protocol/responses/set_keyspace_result_response.rb +2 -2
- data/lib/cql/protocol/responses/status_change_event_response.rb +2 -2
- data/lib/cql/protocol/responses/supported_response.rb +2 -2
- data/lib/cql/protocol/responses/void_result_response.rb +1 -1
- data/lib/cql/protocol/type_converter.rb +78 -81
- data/lib/cql/time_uuid.rb +6 -0
- data/lib/cql/uuid.rb +2 -1
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/batch_spec.rb +8 -8
- data/spec/cql/client/{asynchronous_client_spec.rb → client_spec.rb} +162 -0
- data/spec/cql/client/connector_spec.rb +13 -3
- data/spec/cql/client/{asynchronous_prepared_statement_spec.rb → prepared_statement_spec.rb} +148 -1
- data/spec/cql/client/request_runner_spec.rb +2 -2
- data/spec/cql/protocol/cql_byte_buffer_spec.rb +895 -0
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +1 -1
- data/spec/cql/protocol/frame_decoder_spec.rb +14 -14
- data/spec/cql/protocol/frame_encoder_spec.rb +7 -7
- data/spec/cql/protocol/requests/auth_response_request_spec.rb +4 -4
- data/spec/cql/protocol/requests/batch_request_spec.rb +21 -21
- data/spec/cql/protocol/requests/credentials_request_spec.rb +2 -2
- data/spec/cql/protocol/requests/execute_request_spec.rb +13 -13
- data/spec/cql/protocol/requests/options_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/prepare_request_spec.rb +2 -2
- data/spec/cql/protocol/requests/query_request_spec.rb +13 -13
- data/spec/cql/protocol/requests/register_request_spec.rb +2 -2
- data/spec/cql/protocol/requests/startup_request_spec.rb +4 -4
- data/spec/cql/protocol/responses/auth_challenge_response_spec.rb +5 -5
- data/spec/cql/protocol/responses/auth_success_response_spec.rb +5 -5
- data/spec/cql/protocol/responses/authenticate_response_spec.rb +3 -3
- data/spec/cql/protocol/responses/detailed_error_response_spec.rb +15 -15
- data/spec/cql/protocol/responses/error_response_spec.rb +5 -5
- data/spec/cql/protocol/responses/event_response_spec.rb +8 -8
- data/spec/cql/protocol/responses/prepared_result_response_spec.rb +7 -7
- data/spec/cql/protocol/responses/raw_rows_result_response_spec.rb +1 -1
- data/spec/cql/protocol/responses/ready_response_spec.rb +2 -2
- data/spec/cql/protocol/responses/result_response_spec.rb +16 -16
- data/spec/cql/protocol/responses/rows_result_response_spec.rb +21 -21
- data/spec/cql/protocol/responses/schema_change_event_response_spec.rb +3 -3
- data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +3 -3
- data/spec/cql/protocol/responses/set_keyspace_result_response_spec.rb +2 -2
- data/spec/cql/protocol/responses/status_change_event_response_spec.rb +3 -3
- data/spec/cql/protocol/responses/supported_response_spec.rb +3 -3
- data/spec/cql/protocol/responses/topology_change_event_response_spec.rb +3 -3
- data/spec/cql/protocol/responses/void_result_response_spec.rb +2 -2
- data/spec/cql/protocol/type_converter_spec.rb +25 -13
- data/spec/cql/time_uuid_spec.rb +17 -4
- data/spec/cql/uuid_spec.rb +5 -1
- data/spec/integration/protocol_spec.rb +48 -42
- data/spec/spec_helper.rb +0 -1
- metadata +27 -39
- data/lib/cql/byte_buffer.rb +0 -177
- data/lib/cql/client/synchronous_client.rb +0 -79
- data/lib/cql/client/synchronous_prepared_statement.rb +0 -63
- data/lib/cql/future.rb +0 -515
- data/lib/cql/io.rb +0 -15
- data/lib/cql/io/connection.rb +0 -220
- data/lib/cql/io/io_reactor.rb +0 -349
- data/lib/cql/protocol/decoding.rb +0 -187
- data/lib/cql/protocol/encoding.rb +0 -114
- data/spec/cql/byte_buffer_spec.rb +0 -337
- data/spec/cql/client/synchronous_client_spec.rb +0 -170
- data/spec/cql/client/synchronous_prepared_statement_spec.rb +0 -155
- data/spec/cql/future_spec.rb +0 -737
- data/spec/cql/io/connection_spec.rb +0 -484
- data/spec/cql/io/io_reactor_spec.rb +0 -402
- data/spec/cql/protocol/decoding_spec.rb +0 -547
- data/spec/cql/protocol/encoding_spec.rb +0 -386
- data/spec/integration/io_spec.rb +0 -283
- data/spec/support/fake_server.rb +0 -106
data/lib/cql/io.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module Cql
|
4
|
-
IoError = Class.new(CqlError)
|
5
|
-
CancelledError = Class.new(CqlError)
|
6
|
-
|
7
|
-
module Io
|
8
|
-
ConnectionError = Class.new(IoError)
|
9
|
-
ConnectionClosedError = Class.new(ConnectionError)
|
10
|
-
ConnectionTimeoutError = Class.new(ConnectionError)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
require 'cql/io/io_reactor'
|
15
|
-
require 'cql/io/connection'
|
data/lib/cql/io/connection.rb
DELETED
@@ -1,220 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'socket'
|
4
|
-
|
5
|
-
|
6
|
-
module Cql
|
7
|
-
module Io
|
8
|
-
# A wrapper around a socket. Handles connecting to the remote host, reading
|
9
|
-
# from and writing to the socket.
|
10
|
-
#
|
11
|
-
# @private
|
12
|
-
class Connection
|
13
|
-
attr_reader :host, :port, :connection_timeout
|
14
|
-
|
15
|
-
# @private
|
16
|
-
def initialize(host, port, connection_timeout, unblocker, clock, socket_impl=Socket)
|
17
|
-
@host = host
|
18
|
-
@port = port
|
19
|
-
@connection_timeout = connection_timeout
|
20
|
-
@unblocker = unblocker
|
21
|
-
@clock = clock
|
22
|
-
@socket_impl = socket_impl
|
23
|
-
@lock = Mutex.new
|
24
|
-
@connected = false
|
25
|
-
@write_buffer = ByteBuffer.new
|
26
|
-
@connected_promise = Promise.new
|
27
|
-
end
|
28
|
-
|
29
|
-
# @private
|
30
|
-
def connect
|
31
|
-
begin
|
32
|
-
unless @addrinfos
|
33
|
-
@connection_started_at = @clock.now
|
34
|
-
@addrinfos = @socket_impl.getaddrinfo(@host, @port, nil, Socket::SOCK_STREAM)
|
35
|
-
end
|
36
|
-
unless @io
|
37
|
-
_, port, _, ip, address_family, socket_type = @addrinfos.shift
|
38
|
-
@sockaddr = @socket_impl.sockaddr_in(port, ip)
|
39
|
-
@io = @socket_impl.new(address_family, socket_type, 0)
|
40
|
-
end
|
41
|
-
unless connected?
|
42
|
-
@io.connect_nonblock(@sockaddr)
|
43
|
-
@connected = true
|
44
|
-
@connected_promise.fulfill(self)
|
45
|
-
end
|
46
|
-
rescue Errno::EISCONN
|
47
|
-
@connected = true
|
48
|
-
@connected_promise.fulfill(self)
|
49
|
-
rescue Errno::EINPROGRESS, Errno::EALREADY
|
50
|
-
if @clock.now - @connection_started_at > @connection_timeout
|
51
|
-
close(ConnectionTimeoutError.new("Could not connect to #{@host}:#{@port} within #{@connection_timeout}s"))
|
52
|
-
end
|
53
|
-
rescue Errno::EINVAL, Errno::ECONNREFUSED => e
|
54
|
-
if @addrinfos.empty?
|
55
|
-
close(e)
|
56
|
-
else
|
57
|
-
@io = nil
|
58
|
-
retry
|
59
|
-
end
|
60
|
-
rescue SystemCallError => e
|
61
|
-
close(e)
|
62
|
-
rescue SocketError => e
|
63
|
-
close(e) || closed!(e)
|
64
|
-
end
|
65
|
-
@connected_promise.future
|
66
|
-
end
|
67
|
-
|
68
|
-
# Closes the connection
|
69
|
-
def close(cause=nil)
|
70
|
-
return false unless @io
|
71
|
-
begin
|
72
|
-
@io.close
|
73
|
-
rescue SystemCallError, IOError
|
74
|
-
# nothing to do, the socket was most likely already closed
|
75
|
-
end
|
76
|
-
closed!(cause)
|
77
|
-
true
|
78
|
-
end
|
79
|
-
|
80
|
-
# @private
|
81
|
-
def connecting?
|
82
|
-
!(closed? || connected?)
|
83
|
-
end
|
84
|
-
|
85
|
-
# Returns true if the connection is connected
|
86
|
-
def connected?
|
87
|
-
@connected
|
88
|
-
end
|
89
|
-
|
90
|
-
# Returns true if the connection is closed
|
91
|
-
def closed?
|
92
|
-
@io.nil?
|
93
|
-
end
|
94
|
-
|
95
|
-
# @private
|
96
|
-
def writable?
|
97
|
-
empty_buffer = @lock.synchronize do
|
98
|
-
@write_buffer.empty?
|
99
|
-
end
|
100
|
-
!(closed? || empty_buffer)
|
101
|
-
end
|
102
|
-
|
103
|
-
# Register to receive notifications when new data is read from the socket.
|
104
|
-
#
|
105
|
-
# You should only call this method in your protocol handler constructor.
|
106
|
-
#
|
107
|
-
# Only one callback can be registered, if you register multiple times only
|
108
|
-
# the last one will receive notifications. This is not meant as a general
|
109
|
-
# event system, it's just for protocol handlers to receive data from their
|
110
|
-
# connection. If you want multiple listeners you need to implement that
|
111
|
-
# yourself in your protocol handler.
|
112
|
-
#
|
113
|
-
# It is very important that you don't do any heavy lifting in the callback
|
114
|
-
# since it is called from the IO reactor thread, and as long as the
|
115
|
-
# callback is working the reactor can't handle any IO and no other
|
116
|
-
# callbacks can be called.
|
117
|
-
#
|
118
|
-
# Errors raised by the callback will be ignored.
|
119
|
-
#
|
120
|
-
# @yield [String] the new data
|
121
|
-
#
|
122
|
-
def on_data(&listener)
|
123
|
-
@data_listener = listener
|
124
|
-
end
|
125
|
-
|
126
|
-
# Register to receive a notification when the socket is closed, both for
|
127
|
-
# expected and unexpected reasons.
|
128
|
-
#
|
129
|
-
# You shoud only call this method in your protocol handler constructor.
|
130
|
-
#
|
131
|
-
# Only one callback can be registered, if you register multiple times only
|
132
|
-
# the last one will receive notifications. This is not meant as a general
|
133
|
-
# event system, it's just for protocol handlers to be notified of the
|
134
|
-
# connection closing. If you want multiple listeners you need to implement
|
135
|
-
# that yourself in your protocol handler.
|
136
|
-
#
|
137
|
-
# Errors raised by the callback will be ignored.
|
138
|
-
#
|
139
|
-
# @yield [error, nil] the error that caused the socket to close, or nil if
|
140
|
-
# the socket closed with #close
|
141
|
-
#
|
142
|
-
def on_closed(&listener)
|
143
|
-
@closed_listener = listener
|
144
|
-
end
|
145
|
-
|
146
|
-
# Write bytes to the socket.
|
147
|
-
#
|
148
|
-
# You can either pass in bytes (as a string or as a `ByteBuffer`), or you
|
149
|
-
# can use the block form of this method to get access to the connection's
|
150
|
-
# internal buffer.
|
151
|
-
#
|
152
|
-
# @yieldparam buffer [Cql::ByteBuffer] the connection's internal buffer
|
153
|
-
# @param bytes [String, Cql::ByteBuffer] the data to write to the socket
|
154
|
-
#
|
155
|
-
def write(bytes=nil)
|
156
|
-
@lock.synchronize do
|
157
|
-
if block_given?
|
158
|
-
yield @write_buffer
|
159
|
-
elsif bytes
|
160
|
-
@write_buffer.append(bytes)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
@unblocker.unblock!
|
164
|
-
end
|
165
|
-
|
166
|
-
# @private
|
167
|
-
def flush
|
168
|
-
if writable?
|
169
|
-
@lock.synchronize do
|
170
|
-
bytes_written = @io.write_nonblock(@write_buffer.cheap_peek)
|
171
|
-
@write_buffer.discard(bytes_written)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
rescue => e
|
175
|
-
close(e)
|
176
|
-
end
|
177
|
-
|
178
|
-
# @private
|
179
|
-
def read
|
180
|
-
new_data = @io.read_nonblock(2**16)
|
181
|
-
@data_listener.call(new_data) if @data_listener
|
182
|
-
rescue => e
|
183
|
-
close(e)
|
184
|
-
end
|
185
|
-
|
186
|
-
# @private
|
187
|
-
def to_io
|
188
|
-
@io
|
189
|
-
end
|
190
|
-
|
191
|
-
def to_s
|
192
|
-
state = 'inconsistent'
|
193
|
-
if connected?
|
194
|
-
state = 'connected'
|
195
|
-
elsif connecting?
|
196
|
-
state = 'connecting'
|
197
|
-
elsif closed?
|
198
|
-
state = 'closed'
|
199
|
-
end
|
200
|
-
%(#<#{self.class.name} #{state} #{@host}:#{@port}>)
|
201
|
-
end
|
202
|
-
|
203
|
-
private
|
204
|
-
|
205
|
-
def closed!(cause)
|
206
|
-
@io = nil
|
207
|
-
if cause && !cause.is_a?(IoError)
|
208
|
-
cause = ConnectionError.new(cause.message)
|
209
|
-
end
|
210
|
-
unless connected?
|
211
|
-
@connected_promise.fail(cause)
|
212
|
-
end
|
213
|
-
@connected = false
|
214
|
-
if @closed_listener
|
215
|
-
@closed_listener.call(cause)
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
data/lib/cql/io/io_reactor.rb
DELETED
@@ -1,349 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module Cql
|
4
|
-
module Io
|
5
|
-
ReactorError = Class.new(IoError)
|
6
|
-
|
7
|
-
# An IO reactor takes care of all the IO for a client. It handles opening
|
8
|
-
# new connections, and making sure that connections that have data to send
|
9
|
-
# flush to the network, and connections that have data coming in read that
|
10
|
-
# data and delegate it to their protocol handlers.
|
11
|
-
#
|
12
|
-
# All IO is done in a single background thread, regardless of how many
|
13
|
-
# connections you open. There shouldn't be any problems handling hundreds of
|
14
|
-
# connections if needed. All operations are thread safe, but you should take
|
15
|
-
# great care when in your protocol handlers to make sure that they don't
|
16
|
-
# do too much work in their data handling callbacks, since those will be
|
17
|
-
# run in the reactor thread, and every cycle you use there is a cycle which
|
18
|
-
# can't be used to handle IO.
|
19
|
-
#
|
20
|
-
# The IO reactor is completely protocol agnostic, and it's up to the
|
21
|
-
# specified protocol handler factory to create objects that can interpret
|
22
|
-
# the bytes received from remote hosts, and to send the correct commands
|
23
|
-
# back. The way this works is that when you create an IO reactor you provide
|
24
|
-
# a factory that can create protocol handler objects (this factory is most
|
25
|
-
# of the time just class, but it could potentially be any object that
|
26
|
-
# responds to #new). When you #connect a new protocol handler instance is
|
27
|
-
# created and passed a connection. The protocol handler can then register to
|
28
|
-
# receive data that arrives over the socket, and it can write data to the
|
29
|
-
# socket. It can also register to be notified when the socket is closed, or
|
30
|
-
# it can itself close the socket.
|
31
|
-
#
|
32
|
-
# @example A protocol handler that processes whole lines
|
33
|
-
#
|
34
|
-
# class LineProtocolHandler
|
35
|
-
# def initialize(connection, scheduler)
|
36
|
-
# @connection = connection
|
37
|
-
# # register a listener method for new data, this must be done in the
|
38
|
-
# # in the constructor, and only one listener can be registered
|
39
|
-
# @connection.on_data(&method(:process_data))
|
40
|
-
# @buffer = ''
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# def process_data(new_data)
|
44
|
-
# # in this fictional protocol we want to process whole lines, so we
|
45
|
-
# # append new data to our buffer and then loop as long as there is
|
46
|
-
# # a newline in the buffer, everything up until a newline is a
|
47
|
-
# # complete line
|
48
|
-
# @buffer << new_data
|
49
|
-
# while newline_index = @buffer.index("\n")
|
50
|
-
# line = @buffer.slice!(0, newline_index + 1)
|
51
|
-
# line.chomp!
|
52
|
-
# # Now do something interesting with the line, but remember that
|
53
|
-
# # while you're in the data listener method you're executing in the
|
54
|
-
# # IO reactor thread so you're blocking the reactor from doing
|
55
|
-
# # other IO work. You should not do any heavy lifting here, but
|
56
|
-
# # instead hand off the data to your application's other threads.
|
57
|
-
# # One way of doing that is to create a Cql::Future in the method
|
58
|
-
# # that sends the request, and then complete the future in this
|
59
|
-
# # method. How you keep track of which future belongs to which
|
60
|
-
# # reply is very protocol dependent so you'll have to figure that
|
61
|
-
# # out yourself.
|
62
|
-
# end
|
63
|
-
# end
|
64
|
-
#
|
65
|
-
# def send_request(command_string)
|
66
|
-
# # This example primarily shows how to implement a data listener
|
67
|
-
# # method, but this is how you write data to the connection. The
|
68
|
-
# # method can be called anything, it doesn't have to be #send_request
|
69
|
-
# @connection.write(command_string)
|
70
|
-
# # The connection object itself is threadsafe, but to create any
|
71
|
-
# # interesting protocol you probably need to set up some state for
|
72
|
-
# # each request so that you know which request to complete when you
|
73
|
-
# # get data back.
|
74
|
-
# end
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
# See {Cql::Protocol::CqlProtocolHandler} for an example of how the CQL
|
78
|
-
# protocol is implemented, and there is an integration tests that implements
|
79
|
-
# the Redis protocol that you can look at too.
|
80
|
-
#
|
81
|
-
# @private
|
82
|
-
#
|
83
|
-
class IoReactor
|
84
|
-
# Initializes a new IO reactor.
|
85
|
-
#
|
86
|
-
# @param protocol_handler_factory [Proc, Class] a Proc or class that
|
87
|
-
# will be used create the protocol handler objects returned by
|
88
|
-
# {#connect}
|
89
|
-
# @param options [Hash] only used to inject behaviour during tests
|
90
|
-
#
|
91
|
-
def initialize(protocol_handler_factory, options={})
|
92
|
-
if protocol_handler_factory.respond_to?(:new)
|
93
|
-
@protocol_handler_factory = protocol_handler_factory.method(:new)
|
94
|
-
else
|
95
|
-
@protocol_handler_factory = protocol_handler_factory
|
96
|
-
end
|
97
|
-
@clock = options[:clock] || Time
|
98
|
-
@unblocker = Unblocker.new
|
99
|
-
@io_loop = IoLoopBody.new(options)
|
100
|
-
@io_loop.add_socket(@unblocker)
|
101
|
-
@running = false
|
102
|
-
@stopped = false
|
103
|
-
@started_promise = Promise.new
|
104
|
-
@stopped_promise = Promise.new
|
105
|
-
@lock = Mutex.new
|
106
|
-
end
|
107
|
-
|
108
|
-
# Register to receive notifications when the reactor shuts down because
|
109
|
-
# on an irrecoverable error.
|
110
|
-
#
|
111
|
-
# The listener block will be called in the reactor thread. Any errors that
|
112
|
-
# it raises will be ignored.
|
113
|
-
#
|
114
|
-
# @yield [error] the error that cause the reactor to stop
|
115
|
-
#
|
116
|
-
def on_error(&listener)
|
117
|
-
@stopped_promise.future.on_failure(&listener)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Returns true as long as the reactor is running. It will be true even
|
121
|
-
# after #stop has been called, but false when the future returned by
|
122
|
-
# #stop completes.
|
123
|
-
#
|
124
|
-
def running?
|
125
|
-
@running
|
126
|
-
end
|
127
|
-
|
128
|
-
# Starts the reactor. This will spawn a background thread that will manage
|
129
|
-
# all connections.
|
130
|
-
#
|
131
|
-
# This method is asynchronous and returns a future which completes when
|
132
|
-
# the reactor has started.
|
133
|
-
#
|
134
|
-
# @return [Cql::Future] a future that will resolve to the reactor itself
|
135
|
-
def start
|
136
|
-
@lock.synchronize do
|
137
|
-
raise ReactorError, 'Cannot start a stopped IO reactor' if @stopped
|
138
|
-
return @started_promise.future if @running
|
139
|
-
@running = true
|
140
|
-
end
|
141
|
-
Thread.start do
|
142
|
-
@started_promise.fulfill(self)
|
143
|
-
begin
|
144
|
-
@io_loop.tick until @stopped
|
145
|
-
ensure
|
146
|
-
@io_loop.close_sockets
|
147
|
-
@io_loop.cancel_timers
|
148
|
-
@running = false
|
149
|
-
if $!
|
150
|
-
@stopped_promise.fail($!)
|
151
|
-
else
|
152
|
-
@stopped_promise.fulfill(self)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
@started_promise.future
|
157
|
-
end
|
158
|
-
|
159
|
-
# Stops the reactor.
|
160
|
-
#
|
161
|
-
# This method is asynchronous and returns a future which completes when
|
162
|
-
# the reactor has completely stopped, or fails with an error if the reactor
|
163
|
-
# stops or has already stopped because of a failure.
|
164
|
-
#
|
165
|
-
# @return [Cql::Future] a future that will resolve to the reactor itself
|
166
|
-
#
|
167
|
-
def stop
|
168
|
-
@stopped = true
|
169
|
-
@stopped_promise.future
|
170
|
-
end
|
171
|
-
|
172
|
-
# Opens a connection to the specified host and port.
|
173
|
-
#
|
174
|
-
# This method is asynchronous and returns a future which completes when
|
175
|
-
# the connection has been established, or fails if the connection cannot
|
176
|
-
# be established for some reason (the connection takes longer than the
|
177
|
-
# specified timeout, the remote host cannot be found, etc.).
|
178
|
-
#
|
179
|
-
# The object returned in the future will be an instance of the protocol
|
180
|
-
# handler class you passed to {#initialize}.
|
181
|
-
#
|
182
|
-
# @param host [String] the host to connect to
|
183
|
-
# @param port [Integer] the port to connect to
|
184
|
-
# @param timeout [Numeric] the number of seconds to wait for a connection
|
185
|
-
# before failing
|
186
|
-
# @return [Cql::Future] a future that will resolve to a protocol handler
|
187
|
-
# object that will be your interface to interact with the connection
|
188
|
-
#
|
189
|
-
def connect(host, port, timeout)
|
190
|
-
connection = Connection.new(host, port, timeout, @unblocker, @clock)
|
191
|
-
f = connection.connect
|
192
|
-
protocol_handler = @protocol_handler_factory.call(connection, self)
|
193
|
-
@io_loop.add_socket(connection)
|
194
|
-
@unblocker.unblock!
|
195
|
-
f.map(protocol_handler)
|
196
|
-
end
|
197
|
-
|
198
|
-
# Returns a future that completes after the specified number of seconds.
|
199
|
-
#
|
200
|
-
# @param timeout [Float] the number of seconds to wait until the returned
|
201
|
-
# future is completed
|
202
|
-
# @return [Cql::Future] a future that completes when the timer expires
|
203
|
-
#
|
204
|
-
def schedule_timer(timeout)
|
205
|
-
@io_loop.schedule_timer(timeout)
|
206
|
-
end
|
207
|
-
|
208
|
-
def to_s
|
209
|
-
@io_loop.to_s
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
# @private
|
214
|
-
class Unblocker
|
215
|
-
def initialize
|
216
|
-
@out, @in = IO.pipe
|
217
|
-
@lock = Mutex.new
|
218
|
-
end
|
219
|
-
|
220
|
-
def connected?
|
221
|
-
true
|
222
|
-
end
|
223
|
-
|
224
|
-
def connecting?
|
225
|
-
false
|
226
|
-
end
|
227
|
-
|
228
|
-
def writable?
|
229
|
-
false
|
230
|
-
end
|
231
|
-
|
232
|
-
def closed?
|
233
|
-
@in.nil?
|
234
|
-
end
|
235
|
-
|
236
|
-
def unblock!
|
237
|
-
@lock.synchronize do
|
238
|
-
@in.write(PING_BYTE)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
def read
|
243
|
-
@out.read_nonblock(2**16)
|
244
|
-
end
|
245
|
-
|
246
|
-
def close
|
247
|
-
@in.close
|
248
|
-
@out.close
|
249
|
-
@in = nil
|
250
|
-
@out = nil
|
251
|
-
end
|
252
|
-
|
253
|
-
def to_io
|
254
|
-
@out
|
255
|
-
end
|
256
|
-
|
257
|
-
def to_s
|
258
|
-
%(#<#{self.class.name}>)
|
259
|
-
end
|
260
|
-
|
261
|
-
private
|
262
|
-
|
263
|
-
PING_BYTE = "\0".freeze
|
264
|
-
end
|
265
|
-
|
266
|
-
# @private
|
267
|
-
class IoLoopBody
|
268
|
-
def initialize(options={})
|
269
|
-
@selector = options[:selector] || IO
|
270
|
-
@clock = options[:clock] || Time
|
271
|
-
@lock = Mutex.new
|
272
|
-
@sockets = []
|
273
|
-
@timers = []
|
274
|
-
end
|
275
|
-
|
276
|
-
def add_socket(socket)
|
277
|
-
@lock.synchronize do
|
278
|
-
sockets = @sockets.reject { |s| s.closed? }
|
279
|
-
sockets << socket
|
280
|
-
@sockets = sockets
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def schedule_timer(timeout, promise=Promise.new)
|
285
|
-
@lock.synchronize do
|
286
|
-
timers = @timers.reject { |pair| pair[1].nil? }
|
287
|
-
timers << [@clock.now + timeout, promise]
|
288
|
-
@timers = timers
|
289
|
-
end
|
290
|
-
promise.future
|
291
|
-
end
|
292
|
-
|
293
|
-
def close_sockets
|
294
|
-
@sockets.each do |s|
|
295
|
-
begin
|
296
|
-
s.close unless s.closed?
|
297
|
-
rescue
|
298
|
-
# the socket had most likely already closed due to an error
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
def cancel_timers
|
304
|
-
@timers.each do |pair|
|
305
|
-
if pair[1]
|
306
|
-
pair[1].fail(CancelledError.new)
|
307
|
-
pair[1] = nil
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
def tick(timeout=1)
|
313
|
-
check_sockets!(timeout)
|
314
|
-
check_timers!
|
315
|
-
end
|
316
|
-
|
317
|
-
def to_s
|
318
|
-
%(#<#{IoReactor.name} @connections=[#{@sockets.map(&:to_s).join(', ')}]>)
|
319
|
-
end
|
320
|
-
|
321
|
-
private
|
322
|
-
|
323
|
-
def check_sockets!(timeout)
|
324
|
-
readables, writables, connecting = [], [], []
|
325
|
-
sockets = @sockets
|
326
|
-
sockets.each do |s|
|
327
|
-
next if s.closed?
|
328
|
-
readables << s if s.connected?
|
329
|
-
writables << s if s.connecting? || s.writable?
|
330
|
-
connecting << s if s.connecting?
|
331
|
-
end
|
332
|
-
r, w, _ = @selector.select(readables, writables, nil, timeout)
|
333
|
-
connecting.each(&:connect)
|
334
|
-
r && r.each(&:read)
|
335
|
-
w && w.each(&:flush)
|
336
|
-
end
|
337
|
-
|
338
|
-
def check_timers!
|
339
|
-
timers = @timers
|
340
|
-
timers.each do |pair|
|
341
|
-
if pair[1] && pair[0] <= @clock.now
|
342
|
-
pair[1].fulfill
|
343
|
-
pair[1] = nil
|
344
|
-
end
|
345
|
-
end
|
346
|
-
end
|
347
|
-
end
|
348
|
-
end
|
349
|
-
end
|