gorgon 0.5.0.rc1 → 0.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/Gemfile.lock +2 -4
  2. data/gorgon.gemspec +0 -1
  3. data/lib/gorgon/amqp_service.rb +5 -5
  4. data/lib/gorgon/gem_command_handler.rb +2 -1
  5. data/lib/gorgon/listener.rb +7 -5
  6. data/lib/gorgon/originator_protocol.rb +1 -0
  7. data/lib/gorgon/version.rb +1 -1
  8. data/lib/gorgon/worker_manager.rb +5 -2
  9. data/lib/gorgon_amq-protocol/.gitignore +15 -0
  10. data/lib/gorgon_amq-protocol/.gitmodules +3 -0
  11. data/lib/gorgon_amq-protocol/.rspec +3 -0
  12. data/lib/gorgon_amq-protocol/.travis.yml +19 -0
  13. data/lib/gorgon_amq-protocol/lib/gorgon_amq/bit_set.rb +82 -0
  14. data/lib/gorgon_amq-protocol/lib/gorgon_amq/endianness.rb +15 -0
  15. data/lib/gorgon_amq-protocol/lib/gorgon_amq/int_allocator.rb +96 -0
  16. data/lib/gorgon_amq-protocol/lib/gorgon_amq/pack.rb +53 -0
  17. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol.rb +4 -0
  18. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/client.rb +2322 -0
  19. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/constants.rb +22 -0
  20. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/exceptions.rb +60 -0
  21. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/float_32bit.rb +14 -0
  22. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/frame.rb +210 -0
  23. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table.rb +142 -0
  24. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_decoder.rb +190 -0
  25. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_encoder.rb +123 -0
  26. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/type_constants.rb +26 -0
  27. data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/version.rb +5 -0
  28. data/lib/gorgon_amq-protocol/lib/gorgon_amq/settings.rb +114 -0
  29. data/lib/gorgon_amq-protocol/lib/gorgon_amq/uri.rb +37 -0
  30. data/lib/gorgon_bunny/lib/gorgon_amq/protocol/extensions.rb +16 -0
  31. data/lib/gorgon_bunny/lib/gorgon_bunny.rb +89 -0
  32. data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/credentials_encoder.rb +55 -0
  33. data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/external_mechanism_encoder.rb +27 -0
  34. data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/plain_mechanism_encoder.rb +19 -0
  35. data/lib/gorgon_bunny/lib/gorgon_bunny/channel.rb +1875 -0
  36. data/lib/gorgon_bunny/lib/gorgon_bunny/channel_id_allocator.rb +80 -0
  37. data/lib/gorgon_bunny/lib/gorgon_bunny/compatibility.rb +24 -0
  38. data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/atomic_fixnum.rb +74 -0
  39. data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/condition.rb +66 -0
  40. data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/continuation_queue.rb +41 -0
  41. data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/linked_continuation_queue.rb +61 -0
  42. data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/synchronized_sorted_set.rb +56 -0
  43. data/lib/gorgon_bunny/lib/gorgon_bunny/consumer.rb +123 -0
  44. data/lib/gorgon_bunny/lib/gorgon_bunny/consumer_tag_generator.rb +23 -0
  45. data/lib/gorgon_bunny/lib/gorgon_bunny/consumer_work_pool.rb +94 -0
  46. data/lib/gorgon_bunny/lib/gorgon_bunny/delivery_info.rb +93 -0
  47. data/lib/gorgon_bunny/lib/gorgon_bunny/exceptions.rb +236 -0
  48. data/lib/gorgon_bunny/lib/gorgon_bunny/exchange.rb +271 -0
  49. data/lib/gorgon_bunny/lib/gorgon_bunny/framing.rb +56 -0
  50. data/lib/gorgon_bunny/lib/gorgon_bunny/heartbeat_sender.rb +70 -0
  51. data/lib/gorgon_bunny/lib/gorgon_bunny/message_properties.rb +119 -0
  52. data/lib/gorgon_bunny/lib/gorgon_bunny/queue.rb +387 -0
  53. data/lib/gorgon_bunny/lib/gorgon_bunny/reader_loop.rb +116 -0
  54. data/lib/gorgon_bunny/lib/gorgon_bunny/return_info.rb +74 -0
  55. data/lib/gorgon_bunny/lib/gorgon_bunny/session.rb +1044 -0
  56. data/lib/gorgon_bunny/lib/gorgon_bunny/socket.rb +83 -0
  57. data/lib/gorgon_bunny/lib/gorgon_bunny/ssl_socket.rb +57 -0
  58. data/lib/gorgon_bunny/lib/gorgon_bunny/system_timer.rb +20 -0
  59. data/lib/gorgon_bunny/lib/gorgon_bunny/test_kit.rb +27 -0
  60. data/lib/gorgon_bunny/lib/gorgon_bunny/timeout.rb +18 -0
  61. data/lib/gorgon_bunny/lib/gorgon_bunny/transport.rb +398 -0
  62. data/lib/gorgon_bunny/lib/gorgon_bunny/version.rb +6 -0
  63. data/lib/gorgon_bunny/lib/gorgon_bunny/versioned_delivery_tag.rb +28 -0
  64. data/spec/crash_reporter_spec.rb +1 -1
  65. data/spec/gem_command_handler_spec.rb +2 -2
  66. data/spec/listener_spec.rb +5 -5
  67. data/spec/worker_manager_spec.rb +3 -3
  68. metadata +56 -17
@@ -0,0 +1,22 @@
1
+ module GorgonAMQ
2
+ module Protocol
3
+ TLS_PORT = 5671
4
+ SSL_PORT = 5671
5
+
6
+ # caching
7
+ EMPTY_STRING = "".freeze
8
+
9
+ PACK_INT8 = 'c'.freeze
10
+ PACK_CHAR = 'C'.freeze
11
+ PACK_UINT16 = 'n'.freeze
12
+ PACK_UINT16_X2 = 'n2'.freeze
13
+ PACK_UINT32 = 'N'.freeze
14
+ PACK_UINT32_X2 = 'N2'.freeze
15
+ PACK_INT64 = 'q'.freeze
16
+ PACK_UCHAR_UINT32 = 'CN'.freeze
17
+ PACK_CHAR_UINT16_UINT32 = 'cnN'.freeze
18
+
19
+ PACK_32BIT_FLOAT = 'f'.freeze
20
+ PACK_64BIT_FLOAT = 'G'.freeze
21
+ end
22
+ end
@@ -0,0 +1,60 @@
1
+ module GorgonAMQ
2
+ module Protocol
3
+ class Error < StandardError
4
+ DEFAULT_MESSAGE = "AMQP error".freeze
5
+
6
+ def self.inherited(subclass)
7
+ @_subclasses ||= []
8
+ @_subclasses << subclass
9
+ end # self.inherited(subclazz)
10
+
11
+ def self.subclasses_with_values
12
+ @_subclasses.select{ |k| defined?(k::VALUE) }
13
+ end # self.subclasses_with_values
14
+
15
+ def self.[](code)
16
+ if result = subclasses_with_values.detect { |klass| klass::VALUE == code }
17
+ result
18
+ else
19
+ raise "No such exception class for code #{code}" unless result
20
+ end # if
21
+ end # self.[]
22
+
23
+ def initialize(message = self.class::DEFAULT_MESSAGE)
24
+ super(message)
25
+ end
26
+ end
27
+
28
+ class FrameTypeError < Protocol::Error
29
+ def initialize(types)
30
+ super("Must be one of #{types.inspect}")
31
+ end
32
+ end
33
+
34
+ class EmptyResponseError < Protocol::Error
35
+ DEFAULT_MESSAGE = "Empty response received from the server."
36
+
37
+ def initialize(message = self.class::DEFAULT_MESSAGE)
38
+ super(message)
39
+ end
40
+ end
41
+
42
+ class BadResponseError < Protocol::Error
43
+ def initialize(argument, expected, actual)
44
+ super("Argument #{argument} has to be #{expected.inspect}, was #{data.inspect}")
45
+ end
46
+ end
47
+
48
+ class SoftError < Protocol::Error
49
+ def self.inherited(subclass)
50
+ Error.inherited(subclass)
51
+ end # self.inherited(subclass)
52
+ end
53
+
54
+ class HardError < Protocol::Error
55
+ def self.inherited(subclass)
56
+ Error.inherited(subclass)
57
+ end # self.inherited(subclass)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+ module GorgonAMQ
2
+ module Protocol
3
+ # Allows distinguishing between 32-bit and 64-bit floats in Ruby.
4
+ # Useful in cases when RabbitMQ plugins encode
5
+ # values as 32 bit numbers.
6
+ class Float32Bit
7
+ attr_reader :value
8
+
9
+ def initialize(value)
10
+ @value = value
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,210 @@
1
+ # encoding: binary
2
+
3
+ module GorgonAMQ
4
+ module Protocol
5
+ class Frame
6
+ TYPES = {:method => 1, :headers => 2, :body => 3, :heartbeat => 8}.freeze
7
+ TYPES_REVERSE = TYPES.invert.freeze
8
+ TYPES_OPTIONS = TYPES.keys.freeze
9
+ CHANNEL_RANGE = (0..65535).freeze
10
+ FINAL_OCTET = "\xCE".freeze # 206
11
+
12
+ def self.encoded_payload(payload)
13
+ if payload.respond_to?(:force_encoding) && payload.encoding.name != 'BINARY'
14
+ # Only copy if we have to.
15
+ payload = payload.dup.force_encoding('BINARY')
16
+ end
17
+ payload
18
+ end
19
+
20
+ # The channel number is 0 for all frames which are global to the connection and 1-65535 for frames that refer to specific channels.
21
+ def self.encode_to_array(type, payload, channel)
22
+ raise RuntimeError.new("Channel has to be 0 or an integer in range 1..65535 but was #{channel.inspect}") unless CHANNEL_RANGE.include?(channel)
23
+ raise RuntimeError.new("Payload can't be nil") if payload.nil?
24
+ components = []
25
+ components << [find_type(type), channel, payload.bytesize].pack(PACK_CHAR_UINT16_UINT32)
26
+ components << encoded_payload(payload)
27
+ components << FINAL_OCTET
28
+ components
29
+ end
30
+
31
+ def self.encode(type, payload, channel)
32
+ encode_to_array(type, payload, channel).join
33
+ end
34
+
35
+ class << self
36
+ alias_method :__new__, :new unless method_defined?(:__new__) # because of reloading
37
+ end
38
+
39
+ def self.new(original_type, *args)
40
+ type_id = find_type(original_type)
41
+ klass = CLASSES[type_id]
42
+ klass.new(*args)
43
+ end
44
+
45
+ def self.find_type(type)
46
+ type_id = if Symbol === type then TYPES[type] else type end
47
+ raise FrameTypeError.new(TYPES_OPTIONS) if type == nil || !TYPES_REVERSE.has_key?(type_id)
48
+ type_id
49
+ end
50
+
51
+ def self.decode(*)
52
+ raise NotImplementedError.new <<-EOF
53
+ You are supposed to redefine this method, because it's dependent on used IO adapter.
54
+
55
+ This functionality is part of the https://github.com/ruby-amqp/amq-client library.
56
+ EOF
57
+ end
58
+
59
+ def self.decode_header(header)
60
+ raise EmptyResponseError if header == nil || header.empty?
61
+
62
+ type_id, channel, size = header.unpack(PACK_CHAR_UINT16_UINT32)
63
+ type = TYPES_REVERSE[type_id]
64
+ raise FrameTypeError.new(TYPES_OPTIONS) unless type
65
+ [type, channel, size]
66
+ end
67
+
68
+ def final?
69
+ true
70
+ end
71
+ end
72
+
73
+ class FrameSubclass < Frame
74
+ # Restore original new
75
+ class << self
76
+ alias_method :new, :__new__
77
+ undef_method :decode if method_defined?(:decode)
78
+ end
79
+
80
+ def self.id
81
+ @id
82
+ end
83
+
84
+ def self.encode(payload, channel)
85
+ super(@id, payload, channel)
86
+ end
87
+
88
+ attr_accessor :channel
89
+ attr_reader :payload
90
+ def initialize(payload, channel)
91
+ @payload, @channel = payload, channel
92
+ end
93
+
94
+ def size
95
+ @payload.bytesize
96
+ end
97
+
98
+ # TODO: remove once we are sure none of the clients
99
+ # uses this method directly
100
+ # @api private
101
+ def encode_to_array
102
+ components = []
103
+ components << [self.class.id, @channel, @payload.bytesize].pack(PACK_CHAR_UINT16_UINT32)
104
+ components << self.class.encoded_payload(@payload)
105
+ components << FINAL_OCTET
106
+ components
107
+ end
108
+
109
+ def encode
110
+ s = [self.class.id, @channel, @payload.bytesize].pack(PACK_CHAR_UINT16_UINT32)
111
+ s << self.class.encoded_payload(@payload)
112
+ s << FINAL_OCTET
113
+ s
114
+ end
115
+ end
116
+
117
+ class MethodFrame < FrameSubclass
118
+ @id = 1
119
+
120
+ def method_class
121
+ @method_class ||= begin
122
+ klass_id, method_id = self.payload.unpack(PACK_UINT16_X2)
123
+ index = klass_id << 16 | method_id
124
+ GorgonAMQ::Protocol::METHODS[index]
125
+ end
126
+ end
127
+
128
+ def final?
129
+ !self.method_class.has_content?
130
+ end # final?
131
+
132
+ def decode_payload
133
+ self.method_class.decode(@payload[4..-1])
134
+ end
135
+ end
136
+
137
+ class HeaderFrame < FrameSubclass
138
+ @id = 2
139
+
140
+ def final?
141
+ false
142
+ end
143
+
144
+ def body_size
145
+ decode_payload
146
+ @body_size
147
+ end
148
+
149
+ def weight
150
+ decode_payload
151
+ @weight
152
+ end
153
+
154
+ def klass_id
155
+ decode_payload
156
+ @klass_id
157
+ end
158
+
159
+ def properties
160
+ decode_payload
161
+ @properties
162
+ end
163
+
164
+ def decode_payload
165
+ @decoded_payload ||= begin
166
+ @klass_id, @weight = @payload.unpack(PACK_UINT16_X2)
167
+ # the total size of the content body, that is, the sum of the body sizes for the
168
+ # following content body frames. Zero indicates that there are no content body frames.
169
+ # So this is NOT related to this very header frame!
170
+ @body_size = GorgonAMQ::Hacks.unpack_uint64_big_endian(@payload[4..11]).first
171
+ @data = @payload[12..-1]
172
+ @properties = Basic.decode_properties(@data)
173
+ end
174
+ end
175
+ end
176
+
177
+ class BodyFrame < FrameSubclass
178
+ @id = 3
179
+
180
+ def decode_payload
181
+ @payload
182
+ end
183
+
184
+ def final?
185
+ # we cannot know whether it is final or not so framing code in amq-client
186
+ # checks this over the entire frameset. MK.
187
+ false
188
+ end
189
+ end
190
+
191
+ class HeartbeatFrame < FrameSubclass
192
+ @id = 8
193
+
194
+ def final?
195
+ true
196
+ end # final?
197
+
198
+ def self.encode
199
+ super(Protocol::EMPTY_STRING, 0)
200
+ end
201
+ end
202
+
203
+ Frame::CLASSES = {
204
+ Frame::TYPES[:method] => MethodFrame,
205
+ Frame::TYPES[:headers] => HeaderFrame,
206
+ Frame::TYPES[:body] => BodyFrame,
207
+ Frame::TYPES[:heartbeat] => HeartbeatFrame
208
+ }
209
+ end
210
+ end
@@ -0,0 +1,142 @@
1
+ # encoding: binary
2
+
3
+ require "gorgon_amq/protocol/client"
4
+ require "gorgon_amq/protocol/type_constants"
5
+ require "gorgon_amq/protocol/table_value_encoder"
6
+ require "gorgon_amq/protocol/table_value_decoder"
7
+
8
+ module GorgonAMQ
9
+ module Protocol
10
+ class Table
11
+
12
+ #
13
+ # Behaviors
14
+ #
15
+
16
+ include TypeConstants
17
+
18
+
19
+ #
20
+ # API
21
+ #
22
+
23
+ class InvalidTableError < StandardError
24
+ def initialize(key, value)
25
+ super("Invalid table value on key #{key}: #{value.inspect} (#{value.class})")
26
+ end
27
+ end
28
+
29
+
30
+ def self.encode(table)
31
+ buffer = String.new
32
+
33
+ table ||= {}
34
+
35
+ table.each do |key, value|
36
+ key = key.to_s # it can be a symbol as well
37
+ buffer << key.bytesize.chr + key
38
+
39
+ case value
40
+ when Hash then
41
+ buffer << TYPE_HASH
42
+ buffer << self.encode(value)
43
+ else
44
+ buffer << TableValueEncoder.encode(value)
45
+ end
46
+ end
47
+
48
+ [buffer.bytesize].pack(PACK_UINT32) + buffer
49
+ end
50
+
51
+
52
+
53
+
54
+ def self.decode(data)
55
+ table = Hash.new
56
+ table_length = data.unpack(PACK_UINT32).first
57
+
58
+ return table if table_length.zero?
59
+
60
+ offset = 4
61
+ while offset <= table_length
62
+ key, offset = decode_table_key(data, offset)
63
+ type, offset = TableValueDecoder.decode_value_type(data, offset)
64
+
65
+ table[key] = case type
66
+ when TYPE_STRING
67
+ v, offset = TableValueDecoder.decode_string(data, offset)
68
+ v
69
+ when TYPE_INTEGER
70
+ v, offset = TableValueDecoder.decode_integer(data, offset)
71
+ v
72
+ when TYPE_DECIMAL
73
+ v, offset = TableValueDecoder.decode_big_decimal(data, offset)
74
+ v
75
+ when TYPE_TIME
76
+ v, offset = TableValueDecoder.decode_time(data, offset)
77
+ v
78
+ when TYPE_HASH
79
+ v, offset = TableValueDecoder.decode_hash(data, offset)
80
+ v
81
+ when TYPE_BOOLEAN
82
+ v, offset = TableValueDecoder.decode_boolean(data, offset)
83
+ v
84
+ when TYPE_SIGNED_8BIT then
85
+ v, offset = TableValueDecoder.decode_short_short(data, offset)
86
+ v
87
+ when TYPE_SIGNED_16BIT then
88
+ v, offset = TableValueDecoder.decode_short(data, offset)
89
+ v
90
+ when TYPE_SIGNED_64BIT then
91
+ v, offset = TableValueDecoder.decode_long(data, offset)
92
+ v
93
+ when TYPE_32BIT_FLOAT then
94
+ v, offset = TableValueDecoder.decode_32bit_float(data, offset)
95
+ v
96
+ when TYPE_64BIT_FLOAT then
97
+ v, offset = TableValueDecoder.decode_64bit_float(data, offset)
98
+ v
99
+ when TYPE_VOID
100
+ nil
101
+ when TYPE_ARRAY
102
+ v, offset = TableValueDecoder.decode_array(data, offset)
103
+ v
104
+ else
105
+ raise ArgumentError, "Not a valid type: #{type.inspect}\nData: #{data.inspect}\nUnprocessed data: #{data[offset..-1].inspect}\nOffset: #{offset}\nTotal size: #{table_length}\nProcessed data: #{table.inspect}"
106
+ end
107
+ end
108
+
109
+ table
110
+ end # self.decode
111
+
112
+
113
+ def self.length(data)
114
+ data.unpack(PACK_UINT32).first
115
+ end
116
+
117
+
118
+ def self.hash_size(value)
119
+ acc = 0
120
+ value.each do |k, v|
121
+ acc += (1 + k.to_s.bytesize)
122
+ acc += TableValueEncoder.field_value_size(v)
123
+ end
124
+
125
+ acc
126
+ end # self.hash_size(value)
127
+
128
+
129
+ def self.decode_table_key(data, offset)
130
+ key_length = data.slice(offset, 1).unpack(PACK_CHAR).first
131
+ offset += 1
132
+ key = data.slice(offset, key_length)
133
+ offset += key_length
134
+
135
+ [key, offset]
136
+ end # self.decode_table_key(data, offset)
137
+
138
+
139
+
140
+ end # Table
141
+ end # Protocol
142
+ end # AMQ