bones-rpc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +29 -0
  8. data/Rakefile +1 -0
  9. data/bones-rpc.gemspec +29 -0
  10. data/lib/bones-rpc.rb +2 -0
  11. data/lib/bones/rpc.rb +23 -0
  12. data/lib/bones/rpc/adapter.rb +49 -0
  13. data/lib/bones/rpc/adapter/base.rb +41 -0
  14. data/lib/bones/rpc/adapter/erlang.rb +28 -0
  15. data/lib/bones/rpc/adapter/json.rb +23 -0
  16. data/lib/bones/rpc/adapter/msgpack.rb +52 -0
  17. data/lib/bones/rpc/adapter/parser.rb +37 -0
  18. data/lib/bones/rpc/address.rb +167 -0
  19. data/lib/bones/rpc/cluster.rb +266 -0
  20. data/lib/bones/rpc/connection.rb +146 -0
  21. data/lib/bones/rpc/connection/reader.rb +49 -0
  22. data/lib/bones/rpc/connection/socket.rb +4 -0
  23. data/lib/bones/rpc/connection/socket/connectable.rb +196 -0
  24. data/lib/bones/rpc/connection/socket/ssl.rb +35 -0
  25. data/lib/bones/rpc/connection/socket/tcp.rb +28 -0
  26. data/lib/bones/rpc/connection/writer.rb +51 -0
  27. data/lib/bones/rpc/context.rb +48 -0
  28. data/lib/bones/rpc/errors.rb +33 -0
  29. data/lib/bones/rpc/failover.rb +38 -0
  30. data/lib/bones/rpc/failover/disconnect.rb +33 -0
  31. data/lib/bones/rpc/failover/ignore.rb +31 -0
  32. data/lib/bones/rpc/failover/retry.rb +39 -0
  33. data/lib/bones/rpc/future.rb +26 -0
  34. data/lib/bones/rpc/instrumentable.rb +41 -0
  35. data/lib/bones/rpc/instrumentable/log.rb +45 -0
  36. data/lib/bones/rpc/instrumentable/noop.rb +33 -0
  37. data/lib/bones/rpc/loggable.rb +112 -0
  38. data/lib/bones/rpc/node.rb +317 -0
  39. data/lib/bones/rpc/node/registry.rb +32 -0
  40. data/lib/bones/rpc/parser.rb +114 -0
  41. data/lib/bones/rpc/parser/buffer.rb +80 -0
  42. data/lib/bones/rpc/protocol.rb +106 -0
  43. data/lib/bones/rpc/protocol/acknowledge.rb +82 -0
  44. data/lib/bones/rpc/protocol/adapter_helper.rb +164 -0
  45. data/lib/bones/rpc/protocol/binary_helper.rb +431 -0
  46. data/lib/bones/rpc/protocol/ext_message.rb +86 -0
  47. data/lib/bones/rpc/protocol/notify.rb +38 -0
  48. data/lib/bones/rpc/protocol/request.rb +45 -0
  49. data/lib/bones/rpc/protocol/response.rb +58 -0
  50. data/lib/bones/rpc/protocol/synchronize.rb +70 -0
  51. data/lib/bones/rpc/read_preference.rb +43 -0
  52. data/lib/bones/rpc/read_preference/nearest.rb +57 -0
  53. data/lib/bones/rpc/read_preference/selectable.rb +81 -0
  54. data/lib/bones/rpc/readable.rb +57 -0
  55. data/lib/bones/rpc/session.rb +195 -0
  56. data/lib/bones/rpc/uri.rb +222 -0
  57. data/lib/bones/rpc/version.rb +6 -0
  58. 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