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,4 @@
1
+ # encoding: utf-8
2
+ require 'bones/rpc/connection/socket/connectable'
3
+ require 'bones/rpc/connection/socket/ssl'
4
+ require 'bones/rpc/connection/socket/tcp'
@@ -0,0 +1,196 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ class Connection
5
+ module Socket
6
+ module Connectable
7
+
8
+ attr_reader :host, :port
9
+
10
+ # Is the socket connection alive?
11
+ #
12
+ # @example Is the socket alive?
13
+ # socket.alive?
14
+ #
15
+ # @return [ true, false ] If the socket is alive.
16
+ #
17
+ # @since 1.0.0
18
+ def alive?
19
+ io = to_io
20
+ if Kernel::select([ io ], nil, [ io ], 0)
21
+ !eof? rescue false
22
+ else
23
+ true
24
+ end
25
+ rescue IOError
26
+ false
27
+ end
28
+
29
+ # Bring in the class methods when included.
30
+ #
31
+ # @example Extend the class methods.
32
+ # Connectable.included(class)
33
+ #
34
+ # @param [ Class ] klass The class including the module.
35
+ #
36
+ # @since 1.3.0
37
+ def self.included(klass)
38
+ klass.send(:extend, ClassMethods)
39
+ end
40
+
41
+ # Read from the TCP socket.
42
+ #
43
+ # @param [ Integer ] size The length to read.
44
+ # @param [ String, NilClass ] buf The string which will receive the data.
45
+ #
46
+ # @return [ Object ] The data.
47
+ #
48
+ # @since 1.2.0
49
+ def read(size = nil, buf = nil)
50
+ check_if_alive!
51
+ handle_socket_errors { super }
52
+ end
53
+
54
+ # Read from the TCP socket.
55
+ #
56
+ # @param [ Integer ] size The maximum length to read.
57
+ # @param [ String, NilClass ] buf The string which will receive the data.
58
+ #
59
+ # @return [ Object ] The data.
60
+ #
61
+ # @since 1.2.0
62
+ def readpartial(maxlen, buf = nil)
63
+ check_if_alive!
64
+ handle_socket_errors { super }
65
+ end
66
+
67
+ # Set the encoding of the underlying socket.
68
+ #
69
+ # @param [ String ] string The encoding.
70
+ #
71
+ # @since 1.3.0
72
+ def set_encoding(string)
73
+ to_io.set_encoding(string)
74
+ end
75
+
76
+ # Write to the socket.
77
+ #
78
+ # @example Write to the socket.
79
+ # socket.write(data)
80
+ #
81
+ # @param [ Object ] args The data to write.
82
+ #
83
+ # @return [ Integer ] The number of bytes written.
84
+ #
85
+ # @since 1.0.0
86
+ def write(*args)
87
+ check_if_alive!
88
+ handle_socket_errors { super }
89
+ end
90
+
91
+ private
92
+
93
+ # Before performing a read or write operating, ping the server to check
94
+ # if it is alive.
95
+ #
96
+ # @api private
97
+ #
98
+ # @example Check if the connection is alive.
99
+ # connectable.check_if_alive!
100
+ #
101
+ # @raise [ ConnectionFailure ] If the connectable is not alive.
102
+ #
103
+ # @since 1.4.0
104
+ def check_if_alive!
105
+ unless alive?
106
+ raise Errors::ConnectionFailure, "Socket connection was closed by remote host"
107
+ end
108
+ end
109
+
110
+ # Generate the message for the connection failure based of the system
111
+ # call error, with some added information.
112
+ #
113
+ # @api private
114
+ #
115
+ # @example Generate the error message.
116
+ # connectable.generate_message(error)
117
+ #
118
+ # @param [ SystemCallError ] error The error.
119
+ #
120
+ # @return [ String ] The error message.
121
+ #
122
+ # @since 1.4.0
123
+ def generate_message(error)
124
+ "#{host}:#{port}: #{error.class.name} (#{error.errno}): #{error.message}"
125
+ end
126
+
127
+ # Handle the potential socket errors that can occur.
128
+ #
129
+ # @api private
130
+ #
131
+ # @example Handle the socket errors while executing the block.
132
+ # handle_socket_errors do
133
+ # socket.read(128)
134
+ # end
135
+ #
136
+ # @raise [ Bones::RPC::Errors::ConnectionFailure ] If a system call error or
137
+ # IOError occured which can be retried.
138
+ # @raise [ Bones::RPC::Errors::Unrecoverable ] If a system call error occured
139
+ # which cannot be retried and should be re-raised.
140
+ #
141
+ # @return [ Object ] The result of the yield.
142
+ #
143
+ # @since 1.0.0
144
+ def handle_socket_errors
145
+ yield
146
+ rescue Errno::ECONNREFUSED => e
147
+ raise Errors::ConnectionFailure, generate_message(e)
148
+ rescue Errno::EHOSTUNREACH => e
149
+ raise Errors::ConnectionFailure, generate_message(e)
150
+ rescue Errno::EPIPE => e
151
+ raise Errors::ConnectionFailure, generate_message(e)
152
+ rescue Errno::ECONNRESET => e
153
+ raise Errors::ConnectionFailure, generate_message(e)
154
+ rescue Errno::ETIMEDOUT => e
155
+ raise Errors::ConnectionFailure, generate_message(e)
156
+ rescue IOError
157
+ raise Errors::ConnectionFailure, "Connection timed out to Bones RPC on #{host}:#{port}"
158
+ rescue OpenSSL::SSL::SSLError => e
159
+ raise Errors::ConnectionFailure, "SSL Error '#{e.to_s}' for connection to Bones RPC on #{host}:#{port}"
160
+ end
161
+
162
+ module ClassMethods
163
+
164
+ # Connect to the tcp server.
165
+ #
166
+ # @example Connect to the server.
167
+ # TCPSocket.connect("127.0.0.1", 27017, 30)
168
+ #
169
+ # @param [ String ] host The host to connect to.
170
+ # @param [ Integer ] post The server port.
171
+ # @param [ Integer ] timeout The connection timeout.
172
+ #
173
+ # @return [ TCPSocket ] The socket.
174
+ #
175
+ # @since 1.0.0
176
+ def connect(host, port, timeout)
177
+ begin
178
+ Timeout::timeout(timeout) do
179
+ sock = new(host, port)
180
+ sock.set_encoding('binary')
181
+ timeout_val = [ timeout, 0 ].pack("l_2")
182
+ sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
183
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO, timeout_val)
184
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDTIMEO, timeout_val)
185
+ sock
186
+ end
187
+ rescue Timeout::Error
188
+ raise Errors::ConnectionFailure, "Timed out connection to Bones RPC on #{host}:#{port}"
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ require 'openssl'
3
+
4
+ module Bones
5
+ module RPC
6
+ class Connection
7
+ module Socket
8
+
9
+ # This is a wrapper around a tcp socket.
10
+ class SSL < ::Celluloid::IO::SSLSocket
11
+ include Connectable
12
+
13
+ # Initialize the new TCPSocket with SSL.
14
+ #
15
+ # @example Initialize the socket.
16
+ # SSL.new("127.0.0.1", 27017)
17
+ #
18
+ # @param [ String ] host The host.
19
+ # @param [ Integer ] port The port.
20
+ #
21
+ # @since 1.2.0
22
+ def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
23
+ @host, @port = remote_host, remote_port
24
+ handle_socket_errors do
25
+ socket = TCPSocket.new(remote_host, remote_port, local_host, local_port)
26
+ super(socket)
27
+ to_io.sync_close = true
28
+ connect
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ class Connection
5
+ module Socket
6
+
7
+ # This is a wrapper around a ssl socket.
8
+ class TCP < ::Celluloid::IO::TCPSocket
9
+ include Connectable
10
+
11
+ # Initialize the new TCPSocket.
12
+ #
13
+ # @example Initialize the socket.
14
+ # TCP.new("127.0.0.1", 27017)
15
+ #
16
+ # @param [ String ] remote_host The host.
17
+ # @param [ Integer ] remote_port The port.
18
+ #
19
+ # @since 1.2.0
20
+ def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
21
+ @host, @port = remote_host, remote_port
22
+ handle_socket_errors { super }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ class Connection
5
+ class Writer
6
+ include Celluloid::IO
7
+
8
+ execute_block_on_receiver :initialize
9
+ finalizer :shutdown
10
+ trap_exit :reader_died
11
+
12
+ def initialize(connection, socket, adapter)
13
+ @connection = connection
14
+ @socket = socket
15
+ @adapter = adapter
16
+ @resolved = @connection.node.address.resolved
17
+ @buffer = ""
18
+ @reader = Reader.new_link(@connection, @socket, @adapter)
19
+ end
20
+
21
+ def write(operations)
22
+ operations.each do |message, future|
23
+ message.serialize(@buffer, @adapter)
24
+ message.attach(@connection.node, future) if future
25
+ end
26
+ @socket.write(@buffer)
27
+ @buffer = ""
28
+ return true
29
+ rescue EOFError, Errors::ConnectionFailure => e
30
+ Loggable.warn(" BONES-RPC:", "#{@resolved} Writer terminating: #{e.message}", "n/a")
31
+ terminate
32
+ end
33
+
34
+ def shutdown
35
+ if @reader && @reader.alive?
36
+ @reader.unlink
37
+ @reader.async.terminate
38
+ end
39
+ @connection.cleanup_socket(@socket)
40
+ end
41
+
42
+ def reader_died(actor, reason)
43
+ Loggable.warn(" BONES-RPC:", "#{@resolved} Writer terminating: #{reason}", "n/a")
44
+ @reader = nil
45
+ terminate
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+
5
+ # The class for interacting with a MongoDB database. One only interacts with
6
+ # this class indirectly through a session.
7
+ #
8
+ # @since 1.0.0
9
+ class Context
10
+ include Readable
11
+
12
+ # @!attribute session
13
+ # @return [ Session ] The database session.
14
+ attr_reader :session
15
+
16
+ # Initialize the database.
17
+ #
18
+ # @example Initialize a database object.
19
+ # Database.new(session, :artists)
20
+ #
21
+ # @param [ Session ] session The session.
22
+ # @param [ String, Symbol ] name The name of the database.
23
+ #
24
+ # @since 1.0.0
25
+ def initialize(session)
26
+ @session = session
27
+ end
28
+
29
+ def notify(method, params)
30
+ read_preference.with_node(cluster) do |node|
31
+ node.notify(method, params)
32
+ end
33
+ end
34
+
35
+ def request(method, params)
36
+ read_preference.with_node(cluster) do |node|
37
+ node.request(method, params)
38
+ end
39
+ end
40
+
41
+ def synchronize
42
+ read_preference.with_node(cluster) do |node|
43
+ node.synchronize
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ module Errors
5
+
6
+ # Raised when the connection pool is saturated and no new connection is
7
+ # reaped during the wait time.
8
+ class PoolSaturated < RuntimeError; end
9
+
10
+ # Raised when attempting to checkout a connection on a thread that already
11
+ # has a connection checked out.
12
+ class ConnectionInUse < RuntimeError; end
13
+
14
+ # Raised when attempting to checkout a pinned connection from the pool but
15
+ # it is already in use by another object on the same thread.
16
+ class PoolTimeout < RuntimeError; end
17
+
18
+ # Generic error class for exceptions related to connection failures.
19
+ class ConnectionFailure < StandardError; end
20
+
21
+ # Raised when an Adapter is invalid.
22
+ class InvalidAdapter < StandardError; end
23
+
24
+ # Raised when a Bones::RPC URI is invalid.
25
+ class InvalidBonesRPCURI < StandardError; end
26
+
27
+ class InvalidExtMessage < StandardError; end
28
+
29
+ # Tag applied to unhandled exceptions on a node.
30
+ module SocketError; end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+ require 'bones/rpc/failover/disconnect'
3
+ require 'bones/rpc/failover/ignore'
4
+ require 'bones/rpc/failover/retry'
5
+
6
+ module Bones
7
+ module RPC
8
+
9
+ # Provides behaviour around failover scenarios for different types of
10
+ # exceptions that get raised on connection and execution of operations.
11
+ #
12
+ # @since 2.0.0
13
+ module Failover
14
+ extend self
15
+
16
+ # Hash lookup for the failover classes based off the exception type.
17
+ #
18
+ # @since 2.0.0
19
+ STRATEGIES = {
20
+ Errors::ConnectionFailure => Retry
21
+ }.freeze
22
+
23
+ # Get the appropriate failover handler given the provided exception.
24
+ #
25
+ # @example Get the failover handler for an IOError.
26
+ # Bones::RPC::Failover.get(IOError)
27
+ #
28
+ # @param [ Exception ] exception The raised exception.
29
+ #
30
+ # @return [ Object ] The failover handler.
31
+ #
32
+ # @since 2.0.0
33
+ def get(exception)
34
+ STRATEGIES.fetch(exception.class, Disconnect)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ module Failover
5
+
6
+ # Disconnect is for the case when we get exceptions we do not know about,
7
+ # and need to disconnect the node to cleanup the problem.
8
+ #
9
+ # @since 2.0.0
10
+ module Disconnect
11
+ extend self
12
+
13
+ # Executes the failover strategy. In the case of disconnect, we just re-raise
14
+ # the exception that was thrown previously extending a socket error and
15
+ # disconnect.
16
+ #
17
+ # @example Execute the disconnect strategy.
18
+ # Bones::RPC::Failover::Disconnect.execute(exception, node)
19
+ #
20
+ # @param [ Exception ] exception The raised exception.
21
+ # @param [ Node ] node The node the exception got raised on.
22
+ #
23
+ # @raise [ Errors::SocketError ] The extended exception that was thrown.
24
+ #
25
+ # @since 2.0.0
26
+ def execute(exception, node)
27
+ node.disconnect
28
+ raise(exception.extend(Errors::SocketError))
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end