bones-rpc 0.0.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/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bones-rpc.gemspec +29 -0
- data/lib/bones-rpc.rb +2 -0
- data/lib/bones/rpc.rb +23 -0
- data/lib/bones/rpc/adapter.rb +49 -0
- data/lib/bones/rpc/adapter/base.rb +41 -0
- data/lib/bones/rpc/adapter/erlang.rb +28 -0
- data/lib/bones/rpc/adapter/json.rb +23 -0
- data/lib/bones/rpc/adapter/msgpack.rb +52 -0
- data/lib/bones/rpc/adapter/parser.rb +37 -0
- data/lib/bones/rpc/address.rb +167 -0
- data/lib/bones/rpc/cluster.rb +266 -0
- data/lib/bones/rpc/connection.rb +146 -0
- data/lib/bones/rpc/connection/reader.rb +49 -0
- data/lib/bones/rpc/connection/socket.rb +4 -0
- data/lib/bones/rpc/connection/socket/connectable.rb +196 -0
- data/lib/bones/rpc/connection/socket/ssl.rb +35 -0
- data/lib/bones/rpc/connection/socket/tcp.rb +28 -0
- data/lib/bones/rpc/connection/writer.rb +51 -0
- data/lib/bones/rpc/context.rb +48 -0
- data/lib/bones/rpc/errors.rb +33 -0
- data/lib/bones/rpc/failover.rb +38 -0
- data/lib/bones/rpc/failover/disconnect.rb +33 -0
- data/lib/bones/rpc/failover/ignore.rb +31 -0
- data/lib/bones/rpc/failover/retry.rb +39 -0
- data/lib/bones/rpc/future.rb +26 -0
- data/lib/bones/rpc/instrumentable.rb +41 -0
- data/lib/bones/rpc/instrumentable/log.rb +45 -0
- data/lib/bones/rpc/instrumentable/noop.rb +33 -0
- data/lib/bones/rpc/loggable.rb +112 -0
- data/lib/bones/rpc/node.rb +317 -0
- data/lib/bones/rpc/node/registry.rb +32 -0
- data/lib/bones/rpc/parser.rb +114 -0
- data/lib/bones/rpc/parser/buffer.rb +80 -0
- data/lib/bones/rpc/protocol.rb +106 -0
- data/lib/bones/rpc/protocol/acknowledge.rb +82 -0
- data/lib/bones/rpc/protocol/adapter_helper.rb +164 -0
- data/lib/bones/rpc/protocol/binary_helper.rb +431 -0
- data/lib/bones/rpc/protocol/ext_message.rb +86 -0
- data/lib/bones/rpc/protocol/notify.rb +38 -0
- data/lib/bones/rpc/protocol/request.rb +45 -0
- data/lib/bones/rpc/protocol/response.rb +58 -0
- data/lib/bones/rpc/protocol/synchronize.rb +70 -0
- data/lib/bones/rpc/read_preference.rb +43 -0
- data/lib/bones/rpc/read_preference/nearest.rb +57 -0
- data/lib/bones/rpc/read_preference/selectable.rb +81 -0
- data/lib/bones/rpc/readable.rb +57 -0
- data/lib/bones/rpc/session.rb +195 -0
- data/lib/bones/rpc/uri.rb +222 -0
- data/lib/bones/rpc/version.rb +6 -0
- metadata +198 -0
@@ -0,0 +1,266 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'bones/rpc/node'
|
3
|
+
|
4
|
+
module Bones
|
5
|
+
module RPC
|
6
|
+
|
7
|
+
# The cluster represents a cluster of MongoDB server nodes, either a single
|
8
|
+
# node, a replica set, or a mongos server.
|
9
|
+
#
|
10
|
+
# @since 1.0.0
|
11
|
+
class Cluster
|
12
|
+
|
13
|
+
# The default interval that a node would be flagged as "down".
|
14
|
+
#
|
15
|
+
# @since 2.0.0
|
16
|
+
DOWN_INTERVAL = 30
|
17
|
+
|
18
|
+
# The default interval that a node should be refreshed in.
|
19
|
+
#
|
20
|
+
# @since 2.0.0
|
21
|
+
REFRESH_INTERVAL = 300
|
22
|
+
|
23
|
+
# The default time to wait to retry an operation.
|
24
|
+
#
|
25
|
+
# @since 2.0.0
|
26
|
+
RETRY_INTERVAL = 0.25
|
27
|
+
|
28
|
+
# @!attribute options
|
29
|
+
# @return [ Hash ] The refresh options.
|
30
|
+
# @!attribute peers
|
31
|
+
# @return [ Array<Node> ] The node peers.
|
32
|
+
# @!attribute seeds
|
33
|
+
# @return [ Array<Node> ] The seed nodes.
|
34
|
+
attr_reader :session, :peers, :seeds
|
35
|
+
|
36
|
+
# Disconnects all nodes in the cluster. This should only be used in cases
|
37
|
+
# where you know you're not going to use the cluster on the thread anymore
|
38
|
+
# and need to force the connections to close.
|
39
|
+
#
|
40
|
+
# @return [ true ] True if the disconnect succeeded.
|
41
|
+
#
|
42
|
+
# @since 1.2.0
|
43
|
+
def disconnect
|
44
|
+
nodes.each { |node| node.disconnect } and true
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the interval at which a node should be flagged as down before
|
48
|
+
# retrying.
|
49
|
+
#
|
50
|
+
# @example Get the down interval, in seconds.
|
51
|
+
# cluster.down_interval
|
52
|
+
#
|
53
|
+
# @return [ Integer ] The down interval.
|
54
|
+
#
|
55
|
+
# @since 1.2.7
|
56
|
+
def down_interval
|
57
|
+
@down_interval ||= options[:down_interval] || DOWN_INTERVAL
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_refresh(node)
|
61
|
+
session.handle_refresh(node)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Initialize the new cluster.
|
65
|
+
#
|
66
|
+
# @example Initialize the cluster.
|
67
|
+
# Cluster.new([ "localhost:27017" ], down_interval: 20)
|
68
|
+
#
|
69
|
+
# @param [ Hash ] options The cluster options.
|
70
|
+
#
|
71
|
+
# @option options :down_interval number of seconds to wait before attempting
|
72
|
+
# to reconnect to a down node. (30)
|
73
|
+
# @option options :refresh_interval number of seconds to cache information
|
74
|
+
# about a node. (300)
|
75
|
+
# @option options [ Integer ] :timeout The time in seconds to wait for an
|
76
|
+
# operation to timeout. (5)
|
77
|
+
#
|
78
|
+
# @since 1.0.0
|
79
|
+
def initialize(session, hosts)
|
80
|
+
@session = session
|
81
|
+
@seeds = hosts.map { |host| Node.new(self, Address.new(host)) }
|
82
|
+
@peers = []
|
83
|
+
end
|
84
|
+
|
85
|
+
# Provide a pretty string for cluster inspection.
|
86
|
+
#
|
87
|
+
# @example Inspect the cluster.
|
88
|
+
# cluster.inspect
|
89
|
+
#
|
90
|
+
# @return [ String ] A nicely formatted string.
|
91
|
+
#
|
92
|
+
# @since 1.0.0
|
93
|
+
def inspect
|
94
|
+
"#<#{self.class.name}:#{object_id} @seeds=#{seeds.inspect}>"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get the number of times an operation should be retried before raising an
|
98
|
+
# error.
|
99
|
+
#
|
100
|
+
# @example Get the maximum retries.
|
101
|
+
# cluster.max_retries
|
102
|
+
#
|
103
|
+
# @return [ Integer ] The max retries.
|
104
|
+
#
|
105
|
+
# @since 1.2.7
|
106
|
+
def max_retries
|
107
|
+
@max_retries ||= options[:max_retries] || seeds.size
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns the list of available nodes, refreshing 1) any nodes which were
|
111
|
+
# down and ready to be checked again and 2) any nodes whose information is
|
112
|
+
# out of date. Arbiter nodes are not returned.
|
113
|
+
#
|
114
|
+
# @example Get the available nodes.
|
115
|
+
# cluster.nodes
|
116
|
+
#
|
117
|
+
# @return [ Array<Node> ] the list of available nodes.
|
118
|
+
#
|
119
|
+
# @since 1.0.0
|
120
|
+
def nodes
|
121
|
+
# Find the nodes that were down but are ready to be refreshed, or those
|
122
|
+
# with stale connection information.
|
123
|
+
needs_refresh, available = seeds.partition do |node|
|
124
|
+
refreshable?(node)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Refresh those nodes.
|
128
|
+
available.concat(refresh(needs_refresh))
|
129
|
+
|
130
|
+
# Now return all the nodes that are available and participating in the
|
131
|
+
# replica set.
|
132
|
+
available.reject{ |node| node.down? }
|
133
|
+
end
|
134
|
+
|
135
|
+
def options
|
136
|
+
session.options
|
137
|
+
end
|
138
|
+
|
139
|
+
def pool_size
|
140
|
+
options[:pool_size] || 5
|
141
|
+
end
|
142
|
+
|
143
|
+
# Refreshes information for each of the nodes provided. The node list
|
144
|
+
# defaults to the list of all known nodes.
|
145
|
+
#
|
146
|
+
# If a node is successfully refreshed, any newly discovered peers will also
|
147
|
+
# be refreshed.
|
148
|
+
#
|
149
|
+
# @example Refresh the nodes.
|
150
|
+
# cluster.refresh
|
151
|
+
#
|
152
|
+
# @param [ Array<Node> ] nodes_to_refresh The nodes to refresh.
|
153
|
+
#
|
154
|
+
# @return [ Array<Node> ] the available nodes
|
155
|
+
#
|
156
|
+
# @since 1.0.0
|
157
|
+
def refresh(nodes_to_refresh = seeds)
|
158
|
+
refreshed_nodes = []
|
159
|
+
seen = {}
|
160
|
+
# Set up a recursive lambda function for refreshing a node and it's peers.
|
161
|
+
refresh_node = ->(node) do
|
162
|
+
unless seen[node]
|
163
|
+
seen[node] = true
|
164
|
+
# Add the node to the global list of known nodes.
|
165
|
+
seeds.push(node) unless seeds.include?(node)
|
166
|
+
begin
|
167
|
+
node.refresh
|
168
|
+
# This node is good, so add it to the list of nodes to return.
|
169
|
+
refreshed_nodes.push(node) unless refreshed_nodes.include?(node)
|
170
|
+
rescue Errors::ConnectionFailure
|
171
|
+
# We couldn't connect to the node.
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
nodes_to_refresh.each(&refresh_node)
|
177
|
+
refreshed_nodes
|
178
|
+
end
|
179
|
+
|
180
|
+
# Get the interval in which the node list should be refreshed.
|
181
|
+
#
|
182
|
+
# @example Get the refresh interval, in seconds.
|
183
|
+
# cluster.refresh_interval
|
184
|
+
#
|
185
|
+
# @return [ Integer ] The refresh interval.
|
186
|
+
#
|
187
|
+
# @since 1.2.7
|
188
|
+
def refresh_interval
|
189
|
+
@refresh_interval ||= options[:refresh_interval] || REFRESH_INTERVAL
|
190
|
+
end
|
191
|
+
|
192
|
+
# Get the operation retry interval - the time to wait before retrying a
|
193
|
+
# single operation.
|
194
|
+
#
|
195
|
+
# @example Get the retry interval, in seconds.
|
196
|
+
# cluster.retry_interval
|
197
|
+
#
|
198
|
+
# @return [ Integer ] The retry interval.
|
199
|
+
#
|
200
|
+
# @since 1.2.7
|
201
|
+
def retry_interval
|
202
|
+
@retry_interval ||= options[:retry_interval] || RETRY_INTERVAL
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
# Get the boundary where a node that is down would need to be refreshed.
|
208
|
+
#
|
209
|
+
# @api private
|
210
|
+
#
|
211
|
+
# @example Get the down boundary.
|
212
|
+
# cluster.down_boundary
|
213
|
+
#
|
214
|
+
# @return [ Time ] The down boundary.
|
215
|
+
#
|
216
|
+
# @since 2.0.0
|
217
|
+
def down_boundary
|
218
|
+
Time.new - down_interval
|
219
|
+
end
|
220
|
+
|
221
|
+
# Get the standard refresh boundary to discover new nodes.
|
222
|
+
#
|
223
|
+
# @api private
|
224
|
+
#
|
225
|
+
# @example Get the refresh boundary.
|
226
|
+
# cluster.refresh_boundary
|
227
|
+
#
|
228
|
+
# @return [ Time ] The refresh boundary.
|
229
|
+
#
|
230
|
+
# @since 2.0.0
|
231
|
+
def refresh_boundary
|
232
|
+
Time.new - refresh_interval
|
233
|
+
end
|
234
|
+
|
235
|
+
# Is the provided node refreshable? This is in the case where the refresh
|
236
|
+
# boundary has passed, or the node has been down longer than the down
|
237
|
+
# boundary.
|
238
|
+
#
|
239
|
+
# @api private
|
240
|
+
#
|
241
|
+
# @example Is the node refreshable?
|
242
|
+
# cluster.refreshable?(node)
|
243
|
+
#
|
244
|
+
# @param [ Node ] node The Node to check.
|
245
|
+
#
|
246
|
+
# @since 2.0.0
|
247
|
+
def refreshable?(node)
|
248
|
+
node.down? ? node.down_at < down_boundary : node.needs_refresh?(refresh_boundary)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Creating a cloned cluster requires cloning all the seed nodes.
|
252
|
+
#
|
253
|
+
# @api prviate
|
254
|
+
#
|
255
|
+
# @example Clone the cluster.
|
256
|
+
# cluster.clone
|
257
|
+
#
|
258
|
+
# @return [ Cluster ] The cloned cluster.
|
259
|
+
#
|
260
|
+
# @since 1.0.0
|
261
|
+
def initialize_copy(_)
|
262
|
+
@seeds = seeds.map(&:dup)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'bones/rpc/connection/reader'
|
3
|
+
require 'bones/rpc/connection/socket'
|
4
|
+
require 'bones/rpc/connection/writer'
|
5
|
+
|
6
|
+
module Bones
|
7
|
+
module RPC
|
8
|
+
|
9
|
+
# This class contains behaviour of Bones::RPC socket connections.
|
10
|
+
#
|
11
|
+
# @since 2.0.0
|
12
|
+
class Connection
|
13
|
+
|
14
|
+
# The default connection timeout, in seconds.
|
15
|
+
#
|
16
|
+
# @since 2.0.0
|
17
|
+
TIMEOUT = 5
|
18
|
+
|
19
|
+
attr_reader :node, :socket
|
20
|
+
|
21
|
+
# Is the connection alive?
|
22
|
+
#
|
23
|
+
# @example Is the connection alive?
|
24
|
+
# connection.alive?
|
25
|
+
#
|
26
|
+
# @return [ true, false ] If the connection is alive.
|
27
|
+
#
|
28
|
+
# @since 1.0.0
|
29
|
+
def alive?
|
30
|
+
connected? ? @socket.alive? : false
|
31
|
+
end
|
32
|
+
|
33
|
+
def cleanup_socket(socket)
|
34
|
+
if @writer
|
35
|
+
@writer.async.terminate if @writer.alive?
|
36
|
+
@writer = nil
|
37
|
+
end
|
38
|
+
@node.cleanup_socket(socket)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Connect to the server defined by @host, @port without timeout @timeout.
|
42
|
+
#
|
43
|
+
# @example Open the connection
|
44
|
+
# connection.connect
|
45
|
+
#
|
46
|
+
# @return [ TCPSocket ] The socket.
|
47
|
+
#
|
48
|
+
# @since 1.0.0
|
49
|
+
def connect
|
50
|
+
if @writer
|
51
|
+
@writer.terminate
|
52
|
+
@writer = nil
|
53
|
+
end
|
54
|
+
@socket = if !!options[:ssl]
|
55
|
+
Socket::SSL.connect(host, port, timeout)
|
56
|
+
else
|
57
|
+
Socket::TCP.connect(host, port, timeout)
|
58
|
+
end
|
59
|
+
writer
|
60
|
+
return true
|
61
|
+
end
|
62
|
+
|
63
|
+
# Is the connection connected?
|
64
|
+
#
|
65
|
+
# @example Is the connection connected?
|
66
|
+
# connection.connected?
|
67
|
+
#
|
68
|
+
# @return [ true, false ] If the connection is connected.
|
69
|
+
#
|
70
|
+
# @since 1.0.0
|
71
|
+
def connected?
|
72
|
+
!!@socket
|
73
|
+
end
|
74
|
+
|
75
|
+
# Disconnect from the server.
|
76
|
+
#
|
77
|
+
# @example Disconnect from the server.
|
78
|
+
# connection.disconnect
|
79
|
+
#
|
80
|
+
# @return [ nil ] nil.
|
81
|
+
#
|
82
|
+
# @since 1.0.0
|
83
|
+
def disconnect
|
84
|
+
@socket.close
|
85
|
+
rescue
|
86
|
+
ensure
|
87
|
+
@socket = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def host
|
91
|
+
node.address.ip
|
92
|
+
end
|
93
|
+
|
94
|
+
def initialize(node)
|
95
|
+
@node = node
|
96
|
+
@socket = nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def inspect
|
100
|
+
"<#{self.class} \"#{node.address.resolved}\">"
|
101
|
+
end
|
102
|
+
|
103
|
+
def options
|
104
|
+
node.options
|
105
|
+
end
|
106
|
+
|
107
|
+
def port
|
108
|
+
node.address.port
|
109
|
+
end
|
110
|
+
|
111
|
+
def timeout
|
112
|
+
options[:timeout] || Connection::TIMEOUT
|
113
|
+
end
|
114
|
+
|
115
|
+
def write(operations)
|
116
|
+
with_connection do |socket|
|
117
|
+
writer.write(operations)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
# Yields a connected socket to the calling back. It will attempt to reconnect
|
124
|
+
# the socket if it is not connected.
|
125
|
+
#
|
126
|
+
# @api private
|
127
|
+
#
|
128
|
+
# @example Write to the connection.
|
129
|
+
# with_connection do |socket|
|
130
|
+
# socket.write(buf)
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# @return The yielded block
|
134
|
+
#
|
135
|
+
# @since 1.3.0
|
136
|
+
def with_connection
|
137
|
+
connect if @socket.nil? || !@socket.alive?
|
138
|
+
yield @socket
|
139
|
+
end
|
140
|
+
|
141
|
+
def writer
|
142
|
+
@writer ||= Writer.new(self, @socket, node.adapter)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Bones
|
3
|
+
module RPC
|
4
|
+
class Connection
|
5
|
+
class Reader
|
6
|
+
include Celluloid::IO
|
7
|
+
|
8
|
+
execute_block_on_receiver :initialize
|
9
|
+
|
10
|
+
def initialize(connection, socket, adapter)
|
11
|
+
@connection = connection
|
12
|
+
@socket = socket
|
13
|
+
@adapter = adapter
|
14
|
+
@buffer = ""
|
15
|
+
async.read
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse(data)
|
19
|
+
@buffer << data
|
20
|
+
if @buffer.empty?
|
21
|
+
async.read
|
22
|
+
else
|
23
|
+
parser = Bones::RPC::Parser.new(@buffer, @adapter)
|
24
|
+
begin
|
25
|
+
loop { async.send parser.read }
|
26
|
+
rescue EOFError
|
27
|
+
@buffer.replace(parser.buffer.to_str)
|
28
|
+
end
|
29
|
+
async.read
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def read
|
34
|
+
loop do
|
35
|
+
async.parse @socket.readpartial(4096)
|
36
|
+
end
|
37
|
+
rescue EOFError, Errors::ConnectionFailure => e
|
38
|
+
Loggable.warn(" BONES-RPC:", "#{@connection.node.address.resolved} Reader terminating: #{e.message}", "n/a")
|
39
|
+
terminate
|
40
|
+
end
|
41
|
+
|
42
|
+
def send(message)
|
43
|
+
@connection.node.handle_message(message)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|