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,32 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ class Node
5
+ class Registry
6
+
7
+ def initialize
8
+ @registry = {}
9
+ end
10
+
11
+ def flush(exception = Errors::ConnectionFailure.new("Socket closed"))
12
+ return true if @registry.empty?
13
+ @registry.each do |channel, futures|
14
+ futures.each do |id, future|
15
+ future.signal(FutureValue.new(exception)) rescue nil
16
+ end
17
+ end
18
+ @registry.clear
19
+ end
20
+
21
+ def get(channel, id)
22
+ (@registry[channel] ||= {}).delete(id)
23
+ end
24
+
25
+ def set(channel, id, future)
26
+ (@registry[channel] ||= {})[id] = future
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+ require 'bones/rpc/parser/buffer'
3
+
4
+ module Bones
5
+ module RPC
6
+ class Parser
7
+
8
+ EXT8 = [0xC7].pack('C').freeze
9
+ EXT16 = [0xC8].pack('C').freeze
10
+ EXT32 = [0xC9].pack('C').freeze
11
+
12
+ attr_reader :stream, :adapter
13
+
14
+ def buffer
15
+ @buffer ||= Bones::RPC::Parser::Buffer.new(@stream)
16
+ end
17
+
18
+ def initialize(stream, adapter)
19
+ @stream = stream.force_encoding('BINARY')
20
+ @adapter = Adapter.get(adapter)
21
+ end
22
+
23
+ def parser
24
+ @parser ||= @adapter.parser(@stream)
25
+ end
26
+
27
+ def read
28
+ sync do
29
+ b = buffer.getc
30
+ buffer.ungetc(b)
31
+ if b.nil?
32
+ raise EOFError
33
+ elsif b.start_with?(EXT8)
34
+ buffer.skip(1)
35
+ len, = buffer.read(1).unpack('C')
36
+ type, = buffer.read(1).unpack('C')
37
+ check_ext!(type)
38
+ head, = buffer.read(1).unpack('C')
39
+ data = buffer.read(len-1)
40
+ parse_ext!(head, data)
41
+ elsif b.start_with?(EXT16)
42
+ buffer.skip(1)
43
+ len, = buffer.read(2).unpack('n')
44
+ type, = buffer.read(1).unpack('C')
45
+ check_ext!(type)
46
+ head, = buffer.read(1).unpack('C')
47
+ data = buffer.read(len-1)
48
+ parse_ext!(head, data)
49
+ elsif b.start_with?(EXT32)
50
+ buffer.skip(1)
51
+ len, = buffer.read(4).unpack('N')
52
+ type, = buffer.read(1).unpack('C')
53
+ check_ext!(type)
54
+ head, = buffer.read(1).unpack('C')
55
+ data = buffer.read(len-1)
56
+ parse_ext!(head, data)
57
+ else
58
+ object = parser.read
59
+ buffer.seek(parser.unpacker_pos)
60
+ map_from!(object)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def check_ext!(type)
68
+ raise(Errors::InvalidExtMessage, "bad ext message received of type #{type.inspect} (should be #{0x0D.inspect})") unless valid_ext?(type)
69
+ end
70
+
71
+ def map_from!(object)
72
+ case object
73
+ when Array
74
+ if (3..4).include?(object.size)
75
+ case object.first
76
+ when 0
77
+ Protocol::Request.map_from(object)
78
+ when 1
79
+ Protocol::Response.map_from(object)
80
+ when 2
81
+ Protocol::Notify.map_from(object)
82
+ else
83
+ object
84
+ end
85
+ end
86
+ else
87
+ object
88
+ end
89
+ end
90
+
91
+ def parse_ext!(head, data)
92
+ message = Protocol.get_by_ext_head(head)
93
+ if message
94
+ message.unpack(data)
95
+ else
96
+ map_from!(Adapter.get_by_ext_head(head).unpack(data))
97
+ end
98
+ end
99
+
100
+ def sync
101
+ buffer.transaction do
102
+ yield
103
+ end
104
+ ensure
105
+ parser.unpacker_seek(buffer.pos) if parser.unpacker_pos != buffer.pos
106
+ end
107
+
108
+ def valid_ext?(type)
109
+ type == 0x0D
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ class Parser
5
+ class Buffer
6
+
7
+ attr_reader :io
8
+
9
+ def initialize(data)
10
+ @io = StringIO.new(data)
11
+ end
12
+
13
+ def getc
14
+ io.getc
15
+ end
16
+
17
+ def pos
18
+ io.pos
19
+ end
20
+
21
+ def read(n)
22
+ i = pos
23
+ data = io.read(n)
24
+ if data.bytesize < n
25
+ seek(i)
26
+ raise EOFError
27
+ else
28
+ data
29
+ end
30
+ end
31
+
32
+ def rewind
33
+ io.rewind
34
+ end
35
+
36
+ def seek(pos)
37
+ io.seek(pos)
38
+ end
39
+
40
+ def size
41
+ io.size
42
+ end
43
+
44
+ def skip(n)
45
+ seek(pos + n)
46
+ end
47
+
48
+ def sync(*others)
49
+ yield
50
+ ensure
51
+ others.each { |other| other.seek(pos) }
52
+ end
53
+
54
+ def to_str
55
+ i = pos
56
+ begin
57
+ io.read || ""
58
+ ensure
59
+ seek(i)
60
+ end
61
+ end
62
+
63
+ def transaction
64
+ i = pos
65
+ begin
66
+ yield
67
+ rescue EOFError => e
68
+ seek(i)
69
+ raise e
70
+ end
71
+ end
72
+
73
+ def ungetc(c)
74
+ io.ungetc(c)
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,106 @@
1
+ module Bones #:nodoc:
2
+ module RPC
3
+
4
+ # The +Bones::RPC::Protocol+ namespace contains convenience classes for
5
+ # building all of the possible messages defined in the Bones RPC Protocol.
6
+ module Protocol
7
+ extend self
8
+
9
+ def get_by_ext_head(head)
10
+ ext_heads[head]
11
+ end
12
+
13
+ def register_ext_head(message, head)
14
+ ext_heads[head] ||= message
15
+ return message
16
+ end
17
+
18
+ private
19
+
20
+ def ext_heads
21
+ @ext_heads ||= {}
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ require 'bones/rpc/protocol/adapter_helper'
28
+ require 'bones/rpc/protocol/binary_helper'
29
+
30
+ require 'bones/rpc/protocol/ext_message'
31
+ require 'bones/rpc/protocol/acknowledge'
32
+ require 'bones/rpc/protocol/synchronize'
33
+
34
+ require 'bones/rpc/protocol/notify'
35
+ require 'bones/rpc/protocol/request'
36
+ require 'bones/rpc/protocol/response'
37
+
38
+ module Bones
39
+ module RPC
40
+ module Protocol
41
+
42
+ def deserialize(buffer, adapter = nil)
43
+ char = buffer.getc
44
+ buffer.ungetc(char)
45
+ if sub = MAP[char]
46
+ sub.deserialize(buffer, adapter)
47
+ elsif adapter
48
+ Adapter.get(adapter).deserialize(buffer)
49
+ else
50
+ raise NotImplementedError, "Unknown data received: #{char.inspect}"
51
+ end
52
+ end
53
+
54
+ module MessagePackExtended
55
+ extend self
56
+
57
+ def deserialize(buffer, adapter = nil)
58
+ ext8 = buffer.getc
59
+ len = buffer.getc
60
+ type = buffer.getc
61
+ buffer.ungetc(type)
62
+ buffer.ungetc(len)
63
+ buffer.ungetc(ext8)
64
+ if sub = MAP[type]
65
+ sub.deserialize(buffer, adapter)
66
+ else
67
+ raise NotImplementedError, "Unknown MessagePackExtended data received: {ext8: #{ext8.inspect}, len: #{len.inspect}, type: #{type.inspect}}"
68
+ end
69
+ end
70
+
71
+ module BonesRPC
72
+ extend self
73
+
74
+ def deserialize(buffer, adapter = nil)
75
+ ext8 = buffer.getc
76
+ len = buffer.getc
77
+ type = buffer.getc
78
+ head = buffer.getc
79
+ buffer.ungetc(head)
80
+ buffer.ungetc(type)
81
+ buffer.ungetc(len)
82
+ buffer.ungetc(ext8)
83
+ if sub = MAP[head]
84
+ sub.deserialize(buffer, adapter)
85
+ else
86
+ raise NotImplementedError, "Unknown BonesRPC data received: {ext8: #{ext8.inspect}, len: #{len.inspect}, type: #{type.inspect}, head: #{head.inspect}}"
87
+ end
88
+ end
89
+
90
+ MAP = {
91
+ [0].pack('C').freeze => Synchronize,
92
+ [1].pack('C').freeze => Acknowledge
93
+ }.freeze
94
+ end
95
+
96
+ MAP = {
97
+ [0x0d].pack('C').freeze => BonesRPC
98
+ }.freeze
99
+ end
100
+
101
+ MAP = {
102
+ [0xc7].pack('C').freeze => MessagePackExtended
103
+ }.freeze
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ module Protocol
5
+ class Acknowledge < ExtMessage
6
+
7
+ uint32 :id
8
+ uint8 :ready
9
+
10
+ finalize
11
+
12
+ def initialize(id, ready)
13
+ self.id = id
14
+ self.ready = ready
15
+ end
16
+
17
+ undef ext_head
18
+ undef ready
19
+ undef :ready=
20
+ undef serialize_ready
21
+
22
+ def ext_head
23
+ 1
24
+ end
25
+
26
+ def ready
27
+ @ready
28
+ end
29
+
30
+ def ready=(val)
31
+ @ready = case val
32
+ when 0xC2
33
+ false
34
+ when 0xC3
35
+ true
36
+ else
37
+ !!val
38
+ end
39
+ end
40
+
41
+ def serialize_ready(buffer)
42
+ buffer << [ready ? 0xC3 : 0xC2].pack('C')
43
+ end
44
+
45
+ def log_inspect
46
+ type = "ACKNOWLEDGE"
47
+ fields = []
48
+ fields << ["%-12s", type]
49
+ fields << ["id=%s", id]
50
+ fields << ["ready=%s", ready]
51
+ f, v = fields.transpose
52
+ f.join(" ") % v
53
+ end
54
+
55
+ def self.deserialize(buffer, adapter = nil)
56
+ message = super
57
+ message.deserialize_id(buffer)
58
+ message.deserialize_ready(buffer)
59
+ message
60
+ end
61
+
62
+ def self.unpack(data)
63
+ buffer = StringIO.new(data)
64
+ id, = buffer.read(4).unpack('N')
65
+ ready = buffer.read(1)
66
+ new(id, ready)
67
+ end
68
+
69
+ def get(node)
70
+ node.detach(:synack, id)
71
+ end
72
+
73
+ def signal(future)
74
+ future.signal(FutureValue.new(self))
75
+ end
76
+
77
+ Protocol.register_ext_head self, 1
78
+
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,164 @@
1
+ # encoding: utf-8
2
+ module Bones
3
+ module RPC
4
+ module Protocol
5
+
6
+ module AdapterHelper
7
+
8
+ # Default implementation for a message is to do nothing when receiving
9
+ # replies.
10
+ #
11
+ # @example Receive replies.
12
+ # message.receive_replies(connection)
13
+ #
14
+ # @param [ Connection ] connection The connection.
15
+ #
16
+ # @return [ nil ] nil.
17
+ #
18
+ # @since 1.0.0
19
+ def receive_replies(connection); end
20
+
21
+ # Serializes the message and all of its fields to a new buffer or to the
22
+ # provided buffer.
23
+ #
24
+ # @example Serliaze the message.
25
+ # message.serialize
26
+ #
27
+ # @param [ String ] buffer A buffer to serialize to.
28
+ #
29
+ # @return [ String ] The result of serliazing this message
30
+ #
31
+ # @since 1.0.0
32
+ def serialize(buffer, adapter)
33
+ Adapter.get(adapter).serialize(process, buffer)
34
+ end
35
+
36
+ class << self
37
+
38
+ # Extends the including class with +ClassMethods+.
39
+ #
40
+ # @param [Class] subclass the inheriting class
41
+ def included(base)
42
+ super
43
+ base.extend(ClassMethods)
44
+ end
45
+ private :included
46
+ end
47
+
48
+ # Provides a DSL for defining struct-like fields for building messages
49
+ # for the Mongo Wire.
50
+ #
51
+ # @example
52
+ # class Command
53
+ # extend Message::ClassMethods
54
+ #
55
+ # int32 :length
56
+ # end
57
+ #
58
+ # Command.fields # => [:length]
59
+ # command = Command.new
60
+ # command.length = 12
61
+ # command.serialize_length("") # => "\f\x00\x00\x00"
62
+ module ClassMethods
63
+
64
+ def deserialize(adapter, buffer="")
65
+ adapter = Adapter.get(adapter)
66
+ message = adapter.deserialize(buffer)
67
+ message.shift
68
+ new(adapter, *message)
69
+ end
70
+
71
+ # @return [Array] the fields defined for this message
72
+ def fields
73
+ @fields ||= []
74
+ end
75
+
76
+ def any(name)
77
+ attr_accessor name
78
+
79
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
80
+ def process_#{name}
81
+ #{name}
82
+ end
83
+ RUBY
84
+
85
+ fields << name
86
+ end
87
+
88
+ # Declare a binary field.
89
+ #
90
+ # @example
91
+ # class Query < Message
92
+ # binary :collection
93
+ # end
94
+ #
95
+ # @param [String] name the name of this field
96
+ def binary(name)
97
+ attr_accessor name
98
+
99
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
100
+ def process_#{name}
101
+ #{name}
102
+ end
103
+ RUBY
104
+
105
+ fields << name
106
+ end
107
+
108
+ def integer(name)
109
+ attr_accessor name
110
+
111
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
112
+ def #{name}
113
+ @#{name} ||= 0
114
+ end
115
+
116
+ def process_#{name}
117
+ #{name}
118
+ end
119
+ RUBY
120
+
121
+ fields << name
122
+ end
123
+
124
+ def list(name)
125
+ attr_accessor name
126
+
127
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
128
+ def #{name}
129
+ @#{name} ||= []
130
+ end
131
+
132
+ def process_#{name}
133
+ #{name}
134
+ end
135
+ RUBY
136
+
137
+ fields << name
138
+ end
139
+
140
+ # Declares the message class as complete, and defines its serialization
141
+ # method from the declared fields.
142
+ def finalize
143
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
144
+ def process
145
+ list = []
146
+ #{fields.map { |f| "list << process_#{f}" }.join("\n")}
147
+ list
148
+ end
149
+ EOS
150
+ end
151
+
152
+ private
153
+
154
+ # This ensures that subclasses of the primary wire message classes have
155
+ # identical fields.
156
+ def inherited(subclass)
157
+ super
158
+ subclass.fields.replace(fields)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end