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