ElmerFudd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 230878eebca4966cb714a69066b13ff94f1ec1ee
4
+ data.tar.gz: 949cc99eb76187efa5064e4b190a8c180d14ae9c
5
+ SHA512:
6
+ metadata.gz: 1966abdca7836ad2e4a1968fc24b871c3d2e93b40927ae988881f15f711c926037d08c4eddb72b1a32c90f4d0c15d88ec071a63e70d70e20151346e8547d9a7d
7
+ data.tar.gz: e174d9c40644de50ff1792f7e20f3af7825ac23eb376692013c992d85aca8560c4ea479e0c0d7dfab5963dab3c36726a746f98763403172012fc05ffb86b3e2e
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ ElmerFudd
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0
data/ElmerFudd.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ElmerFudd/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ElmerFudd"
8
+ spec.version = ElmerFudd::VERSION
9
+ spec.authors = ["Andrzej Sliwa"]
10
+ spec.email = ["andrzej.sliwa@i-tool.eu"]
11
+ spec.summary = %q{RabbitMQ in OTP way}
12
+ spec.description = %q{Be vewwy, vewwy quiet...I'm hunting wabbits!}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "bunny"
22
+ spec.add_development_dependency "bundler", "~> 1.5"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ end
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ElmerFudd.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrzej Sliwa
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Be vewwy, vewwy quiet...I'm hunting wabbits! [![Build Status](https://travis-ci.org/bonusboxme/ElmerFudd.svg)](https://travis-ci.org/bonusboxme/ElmerFudd)
2
+
3
+ ![Elmer Fudd](https://raw.githubusercontent.com/bonusboxme/ElmerFudd/master/elmer-fudd.jpg)
4
+
5
+ # ElmerFudd
6
+
7
+ TODO: Write a gem description
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'ElmerFudd'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install ElmerFudd
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( http://github.com/<my-github-username>/ElmerFudd/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/elmer-fudd.jpg ADDED
Binary file
@@ -0,0 +1,3 @@
1
+ module ElmerFudd
2
+ VERSION = "0.0.1"
3
+ end
data/lib/ElmerFudd.rb ADDED
@@ -0,0 +1,241 @@
1
+ require "ElmerFudd/version"
2
+
3
+ module ElmerFudd
4
+ class Publisher
5
+ def initialize(connection, uuid_service: -> { rand.to_s })
6
+ @connection = connection
7
+ @channel = @connection.create_channel
8
+ @x = @channel.default_exchange
9
+ @rpc_reply_queue = @channel.queue("", exclusive: true)
10
+ @uuid_service = uuid_service
11
+ @topic_x = {}
12
+ end
13
+
14
+ def notify(topic_exchange, routing_key, payload)
15
+ @topic_x[topic_exchange] ||= @channel.topic(topic_exchange)
16
+ @topic_x[topic_exchange].publish payload.to_s, routing_key: routing_key
17
+ nil
18
+ end
19
+
20
+ def cast(queue_name, payload)
21
+ @x.publish(payload.to_s, routing_key: queue_name)
22
+ nil
23
+ end
24
+
25
+ def call(queue_name, payload, timeout: 10)
26
+ @x.publish(payload.to_s, routing_key: queue_name, reply_to: @rpc_reply_queue.name,
27
+ correlation_id: correlation_id = @uuid_service.call)
28
+ response = nil
29
+ consumer_tag = @uuid_service.call
30
+ Timeout.timeout(timeout) do
31
+ @rpc_reply_queue.subscribe(block: true, consumer_tag: consumer_tag) do |delivery_info, properties, payload|
32
+ if properties[:correlation_id] == correlation_id
33
+ response = payload
34
+ delivery_info.consumer.cancel
35
+ end
36
+ end
37
+ end
38
+ response
39
+ rescue Timeout::Error
40
+ @channel.consumers[consumer_tag].cancel
41
+ raise
42
+ end
43
+ end
44
+
45
+ class JsonPublisher < Publisher
46
+ def notify(topic_exchange, routing_key, payload)
47
+ super(topic_exchange, routing_key, payload.to_json)
48
+ end
49
+
50
+ def cast(queue_name, payload)
51
+ super(queue_name, payload.to_json)
52
+ end
53
+
54
+ def call(queue_name, payload, **kwargs)
55
+ JSON.parse(super(queue_name, payload.to_json, **kwargs)).fetch("result")
56
+ end
57
+ end
58
+
59
+ class Worker
60
+ Message = Struct.new(:delivery_info, :properties, :payload, :route)
61
+ Env = Struct.new(:channel, :logger)
62
+ Route = Struct.new(:exchange_name, :routing_key, :queue_name)
63
+
64
+ def self.handlers
65
+ @handlers ||= []
66
+ end
67
+
68
+ def self.Route(queue_name, exchange_and_routing_key = {"" => queue_name})
69
+ exchange, routing_key = exchange_and_routing_key.first
70
+ Route.new(exchange, routing_key, queue_name)
71
+ end
72
+
73
+ def self.default_filters(*filters)
74
+ @filters = filters
75
+ end
76
+
77
+ def self.handle_event(route, filters: [], handler: nil, &block)
78
+ handlers << TopicHandler.new(route, handler || block, (@filters + filters).uniq)
79
+ end
80
+
81
+ def self.handle_cast(route, filters: [], handler: nil, &block)
82
+ handlers << DirectHandler.new(route, handler || block, (@filters + filters).uniq)
83
+ end
84
+
85
+ def self.handle_call(route, filters: [], handler: nil, &block)
86
+ handlers << RpcHandler.new(route, handler || block, (@filters + filters).uniq)
87
+ end
88
+
89
+ def initialize(connection, concurrency: 1, logger: Logger.new($stdout))
90
+ @connection = connection
91
+ channel = connection.create_channel.tap { |c| c.prefetch(concurrency) }
92
+ @env = Env.new(channel, logger)
93
+ end
94
+
95
+ def start
96
+ self.class.handlers.each do |handler|
97
+ handler.queue(@env).subscribe(ack: true, block: false) do |delivery_info, properties, payload|
98
+ message = Message.new(delivery_info, properties, payload, handler.route)
99
+ begin
100
+ handler.call(@env, message)
101
+ @env.channel.acknowledge(message.delivery_info.delivery_tag)
102
+ rescue Exception => e
103
+ @env.logger.fatal("Worker blocked: %s, %s:" % [e.class, e.message])
104
+ e.backtrace.each { |l| @env.logger.fatal(l) }
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ module Filter
112
+ def call_next(env, message, filters)
113
+ next_filter, *remainder = filters
114
+ if remainder.empty?
115
+ next_filter.call(env, message)
116
+ else
117
+ next_filter.call(env, message, remainder)
118
+ end
119
+ end
120
+ end
121
+
122
+ class DirectHandler
123
+ include Filter
124
+ attr_reader :route
125
+
126
+ def initialize(route, callback, filters)
127
+ @route = route
128
+ @callback = callback
129
+ @filters = filters
130
+ end
131
+
132
+ def queue(env)
133
+ env.channel.queue(@route.queue_name, durable: true).tap do |queue|
134
+ queue.bind(exchange(env), routing_key: @route.routing_key) unless @route.exchange_name == ""
135
+ end
136
+ end
137
+
138
+ def exchange(env)
139
+ env.channel.direct(@route.exchange_name)
140
+ end
141
+
142
+ def call(env, message)
143
+ call_next(env, message, @filters + [@callback])
144
+ end
145
+ end
146
+
147
+ class TopicHandler < DirectHandler
148
+ def exchange(env)
149
+ env.channel.topic(@route.exchange_name)
150
+ end
151
+ end
152
+
153
+ class RpcHandler < DirectHandler
154
+ def call(env, message)
155
+ reply(env, message, super)
156
+ end
157
+
158
+ def reply(env, original_message, response)
159
+ exchange(env).publish(response.to_s, routing_key: original_message.properties.reply_to,
160
+ correlation_id: original_message.properties.correlation_id)
161
+ end
162
+ end
163
+
164
+ class JsonFilter
165
+ extend Filter
166
+ def self.call(env, message, filters)
167
+ message.payload = JSON.parse(message.payload)
168
+ {result: call_next(env, message, filters)}.to_json
169
+ rescue JSON::ParserError
170
+ env.logger.error "Ignoring invalid JSON: #{message.payload}"
171
+ end
172
+ end
173
+
174
+ class DropFailedFilter
175
+ extend Filter
176
+ def self.call(env, message, filters)
177
+ call_next(env, message, filters)
178
+ rescue Exception => e
179
+ env.logger.info "Ignoring failed payload: #{message.payload}"
180
+ env.logger.debug "#{e.class}: #{e.message}"
181
+ e.backtrace.each { |l| env.logger.debug(l) }
182
+ end
183
+ end
184
+
185
+ class AirbrakeFilter
186
+ extend Filter
187
+ def self.call(env, message, filters)
188
+ call_next(env, message, filters)
189
+ rescue Exception => e
190
+ Airbrake.notify(e, parametets: {
191
+ payload: message.payload,
192
+ queue: message.route.queue_name,
193
+ exchange_name: message.route.exchange_name,
194
+ routing_key: message.delivery_info.routing_key,
195
+ matched_routing_key: message.route.routing_key
196
+ })
197
+ raise
198
+ end
199
+ end
200
+
201
+ class ActiveRecordConnectionPoolFilter
202
+ extend Filter
203
+ def self.call(env, message, filters)
204
+ retry_num = 0
205
+ ActiveRecord::Base.connection_pool.with_connection do
206
+ call_next(env, message, filters)
207
+ end
208
+ rescue ActiveRecord::ConnectionTimeoutError
209
+ retry_num += 1
210
+ if retry_num <= 5
211
+ retry
212
+ else
213
+ raise
214
+ end
215
+ end
216
+ end
217
+
218
+ class RetryFilter
219
+ include Filter
220
+
221
+ def initialize(times, exception: Exception,
222
+ exception_message_matches: /.*/)
223
+ @times = times
224
+ @exception = exception
225
+ @exception_message_matches = exception_message_matches
226
+ end
227
+
228
+ def call(env, message, filters)
229
+ retry_num = 0
230
+ call_next(env, message, filters)
231
+ rescue @exception => e
232
+ if e.message =~ @exception_message_matches && retry_num < @times
233
+ retry_num += 1
234
+ Math.log(retry_num, 2)
235
+ retry
236
+ else
237
+ raise
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ElmerFudd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrzej Sliwa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bunny
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Be vewwy, vewwy quiet...I'm hunting wabbits!
70
+ email:
71
+ - andrzej.sliwa@i-tool.eu
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - .ruby-gemset
79
+ - .ruby-version
80
+ - ElmerFudd.gemspec
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - elmer-fudd.jpg
86
+ - lib/ElmerFudd.rb
87
+ - lib/ElmerFudd/version.rb
88
+ - spec/spec_helper.rb
89
+ homepage: ''
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.2.2
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: RabbitMQ in OTP way
113
+ test_files:
114
+ - spec/spec_helper.rb