bones-rpc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|