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.
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