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