kicks 3.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +24 -0
- data/.gitignore +12 -0
- data/ChangeLog.md +142 -0
- data/Dockerfile +24 -0
- data/Dockerfile.slim +20 -0
- data/Gemfile +8 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +209 -0
- data/Rakefile +12 -0
- data/bin/sneakers +6 -0
- data/docker-compose.yml +24 -0
- data/examples/benchmark_worker.rb +22 -0
- data/examples/max_retry_handler.rb +68 -0
- data/examples/metrics_worker.rb +34 -0
- data/examples/middleware_worker.rb +36 -0
- data/examples/newrelic_metrics_worker.rb +40 -0
- data/examples/profiling_worker.rb +69 -0
- data/examples/sneakers.conf.rb.example +11 -0
- data/examples/title_scraper.rb +36 -0
- data/examples/workflow_worker.rb +23 -0
- data/kicks.gemspec +44 -0
- data/lib/sneakers/cli.rb +122 -0
- data/lib/sneakers/concerns/logging.rb +34 -0
- data/lib/sneakers/concerns/metrics.rb +34 -0
- data/lib/sneakers/configuration.rb +125 -0
- data/lib/sneakers/content_encoding.rb +47 -0
- data/lib/sneakers/content_type.rb +47 -0
- data/lib/sneakers/error_reporter.rb +33 -0
- data/lib/sneakers/errors.rb +2 -0
- data/lib/sneakers/handlers/maxretry.rb +219 -0
- data/lib/sneakers/handlers/oneshot.rb +26 -0
- data/lib/sneakers/metrics/logging_metrics.rb +16 -0
- data/lib/sneakers/metrics/newrelic_metrics.rb +32 -0
- data/lib/sneakers/metrics/null_metrics.rb +13 -0
- data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
- data/lib/sneakers/middleware/config.rb +23 -0
- data/lib/sneakers/publisher.rb +49 -0
- data/lib/sneakers/queue.rb +87 -0
- data/lib/sneakers/runner.rb +91 -0
- data/lib/sneakers/spawner.rb +30 -0
- data/lib/sneakers/support/production_formatter.rb +11 -0
- data/lib/sneakers/support/utils.rb +18 -0
- data/lib/sneakers/tasks.rb +66 -0
- data/lib/sneakers/version.rb +3 -0
- data/lib/sneakers/worker.rb +162 -0
- data/lib/sneakers/workergroup.rb +60 -0
- data/lib/sneakers.rb +125 -0
- data/log/.gitkeep +0 -0
- data/scripts/local_integration +2 -0
- data/scripts/local_worker +3 -0
- data/spec/fixtures/integration_worker.rb +18 -0
- data/spec/fixtures/require_worker.rb +23 -0
- data/spec/gzip_helper.rb +15 -0
- data/spec/sneakers/cli_spec.rb +75 -0
- data/spec/sneakers/concerns/logging_spec.rb +39 -0
- data/spec/sneakers/concerns/metrics_spec.rb +38 -0
- data/spec/sneakers/configuration_spec.rb +97 -0
- data/spec/sneakers/content_encoding_spec.rb +81 -0
- data/spec/sneakers/content_type_spec.rb +81 -0
- data/spec/sneakers/integration_spec.rb +158 -0
- data/spec/sneakers/publisher_spec.rb +179 -0
- data/spec/sneakers/queue_spec.rb +169 -0
- data/spec/sneakers/runner_spec.rb +70 -0
- data/spec/sneakers/sneakers_spec.rb +77 -0
- data/spec/sneakers/support/utils_spec.rb +44 -0
- data/spec/sneakers/tasks/sneakers_run_spec.rb +115 -0
- data/spec/sneakers/worker_handlers_spec.rb +469 -0
- data/spec/sneakers/worker_spec.rb +712 -0
- data/spec/sneakers/workergroup_spec.rb +83 -0
- data/spec/spec_helper.rb +21 -0
- metadata +352 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'sneakers'
|
3
|
+
require 'sneakers/runner'
|
4
|
+
require 'sneakers/metrics/newrelic_metrics'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'newrelic_rpm'
|
7
|
+
|
8
|
+
# With this configuration will send two types of data to newrelic server:
|
9
|
+
# 1. Transaction data which you would see under 'Applications'
|
10
|
+
# 2. Metrics where you will be able to see by configuring a dashboardi, available for enterprise accounts
|
11
|
+
#
|
12
|
+
# You should have newrelic.yml in the 'config' folder with the proper account settings
|
13
|
+
|
14
|
+
Sneakers::Metrics::NewrelicMetrics.eagent ::NewRelic
|
15
|
+
Sneakers.configure metrics: Sneakers::Metrics::NewrelicMetrics.new
|
16
|
+
|
17
|
+
class MetricsWorker
|
18
|
+
include Sneakers::Worker
|
19
|
+
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
20
|
+
|
21
|
+
from_queue 'downloads'
|
22
|
+
|
23
|
+
def work(msg)
|
24
|
+
title = extract_title(open(msg))
|
25
|
+
logger.info "FOUND <#{title}>"
|
26
|
+
ack!
|
27
|
+
end
|
28
|
+
|
29
|
+
add_transaction_tracer :work, name: 'MetricsWorker', params: 'args[0]', category: :task
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def extract_title(html)
|
34
|
+
html =~ /<title>(.*?)<\/title>/
|
35
|
+
$1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
r = Sneakers::Runner.new([ MetricsWorker ])
|
40
|
+
r.run
|
@@ -0,0 +1,69 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'sneakers'
|
3
|
+
require 'sneakers/runner'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
|
7
|
+
profiling = ARGV[0]
|
8
|
+
messages = 100_000
|
9
|
+
|
10
|
+
|
11
|
+
if profiling
|
12
|
+
require 'ruby-prof'
|
13
|
+
messages /= 100 # profiling makes everything much slower (around 300req/s)
|
14
|
+
end
|
15
|
+
|
16
|
+
Sneakers.configure
|
17
|
+
Sneakers.logger.level = Logger::ERROR
|
18
|
+
|
19
|
+
Sneakers::Worker.configure_logger(Logger.new('/dev/null'))
|
20
|
+
|
21
|
+
puts "feeding messages in"
|
22
|
+
messages.times {
|
23
|
+
Sneakers.publish("{}", :to_queue => 'downloads')
|
24
|
+
}
|
25
|
+
puts "done"
|
26
|
+
|
27
|
+
|
28
|
+
class ProfilingWorker
|
29
|
+
include Sneakers::Worker
|
30
|
+
from_queue 'downloads',
|
31
|
+
:ack => true,
|
32
|
+
:threads => 50,
|
33
|
+
:prefetch => 50,
|
34
|
+
:timeout_job_after => 1,
|
35
|
+
:exchange => 'sneakers',
|
36
|
+
:heartbeat => 5,
|
37
|
+
:amqp_heartbeat => 10
|
38
|
+
def work(msg)
|
39
|
+
ack!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
r = Sneakers::Runner.new([ProfilingWorker])
|
46
|
+
|
47
|
+
# ctrl-c and Ruby 2.0 breaks signal handling
|
48
|
+
# Sidekiq has same issues
|
49
|
+
# https://github.com/mperham/sidekiq/issues/728
|
50
|
+
#
|
51
|
+
# so we use a timeout and a thread that kills profiling
|
52
|
+
if profiling
|
53
|
+
puts "profiling start"
|
54
|
+
RubyProf.start
|
55
|
+
|
56
|
+
|
57
|
+
Thread.new do
|
58
|
+
sleep 10
|
59
|
+
puts "stopping profiler"
|
60
|
+
result = RubyProf.stop
|
61
|
+
|
62
|
+
# Print a flat profile to text
|
63
|
+
printer = RubyProf::FlatPrinter.new(result)
|
64
|
+
printer.print(STDOUT)
|
65
|
+
exit(0)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
r.run
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "sneakers"
|
2
|
+
require 'open-uri'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
def compose_or_localhost(key)
|
6
|
+
Resolv::DNS.new.getaddress(key)
|
7
|
+
rescue
|
8
|
+
"localhost"
|
9
|
+
end
|
10
|
+
|
11
|
+
rmq_addr = compose_or_localhost("rabbitmq")
|
12
|
+
|
13
|
+
Sneakers.configure :log => STDOUT, :amqp => "amqp://guest:guest@#{rmq_addr}:5672"
|
14
|
+
Sneakers.logger.level = Logger::INFO
|
15
|
+
|
16
|
+
class TitleScraper
|
17
|
+
include Sneakers::Worker
|
18
|
+
|
19
|
+
from_queue 'downloads'
|
20
|
+
|
21
|
+
def work(msg)
|
22
|
+
title = extract_title(open(msg))
|
23
|
+
logger.info "FOUND <#{title}>"
|
24
|
+
ack!
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def extract_title(html)
|
30
|
+
html =~ /<title>(.*?)<\/title>/
|
31
|
+
$1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'sneakers'
|
3
|
+
|
4
|
+
class WorkflowWorker
|
5
|
+
include Sneakers::Worker
|
6
|
+
from_queue 'downloads',
|
7
|
+
exchange_options: { durable: false },
|
8
|
+
queue_options: { durable: false },
|
9
|
+
:ack => true,
|
10
|
+
:threads => 50,
|
11
|
+
:prefetch => 50,
|
12
|
+
:timeout_job_after => 1,
|
13
|
+
:exchange => 'dummy',
|
14
|
+
:heartbeat => 5,
|
15
|
+
:amqp_heartbeat => 10
|
16
|
+
|
17
|
+
def work(msg)
|
18
|
+
logger.info("Seriously, i'm DONE.")
|
19
|
+
publish "cleaned up", :to_queue => "foobar"
|
20
|
+
logger.info("Published to 'foobar'")
|
21
|
+
ack!
|
22
|
+
end
|
23
|
+
end
|
data/kicks.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env gem build
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
lib = File.expand_path('../lib', __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require 'sneakers/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |gem|
|
9
|
+
gem.name = 'kicks'
|
10
|
+
gem.version = Sneakers::VERSION
|
11
|
+
gem.authors = ['Dotan Nahum', 'Michael Klishin']
|
12
|
+
gem.email = ['michael@clojurewerkz.org']
|
13
|
+
gem.description = %q( Fast background processing framework for Ruby and RabbitMQ )
|
14
|
+
gem.summary = %q( Fast background processing framework for Ruby and RabbitMQ )
|
15
|
+
gem.homepage = 'https://github.com/ruby-amqp/kicks'
|
16
|
+
gem.license = 'MIT'
|
17
|
+
gem.required_ruby_version = Gem::Requirement.new(">= 2.5")
|
18
|
+
|
19
|
+
gem.files = `git ls-files`.split($/).reject { |f| f == 'Gemfile.lock' }
|
20
|
+
gem.executables = gem.files.grep(/^bin/).
|
21
|
+
reject { |f| f =~ /^bin\/ci/ }.
|
22
|
+
map { |f| File.basename(f) }
|
23
|
+
gem.test_files = gem.files.grep(/^(test|spec|features)\//)
|
24
|
+
gem.require_paths = ['lib']
|
25
|
+
|
26
|
+
gem.add_dependency 'serverengine', '~> 2.1'
|
27
|
+
gem.add_dependency 'bunny', '~> 2.19'
|
28
|
+
gem.add_dependency 'concurrent-ruby', '~> 1.0'
|
29
|
+
gem.add_dependency 'thor'
|
30
|
+
gem.add_dependency 'rake', '>= 12.3', '< 14.0'
|
31
|
+
|
32
|
+
# for integration environment (see .travis.yml and integration_spec)
|
33
|
+
gem.add_development_dependency 'rabbitmq_http_api_client'
|
34
|
+
gem.add_development_dependency 'redis'
|
35
|
+
|
36
|
+
gem.add_development_dependency 'minitest', '~> 5.15'
|
37
|
+
gem.add_development_dependency 'rr', '~> 3.0'
|
38
|
+
gem.add_development_dependency 'unparser', '~> 0.2'
|
39
|
+
gem.add_development_dependency 'simplecov', '~> 0.21'
|
40
|
+
gem.add_development_dependency 'simplecov-rcov-text'
|
41
|
+
gem.add_development_dependency 'guard', '~> 2.18'
|
42
|
+
gem.add_development_dependency 'guard-minitest', '~> 2.4'
|
43
|
+
gem.add_development_dependency 'pry-byebug', '~> 3.9'
|
44
|
+
end
|
data/lib/sneakers/cli.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'sneakers/runner'
|
3
|
+
|
4
|
+
|
5
|
+
#
|
6
|
+
# $ sneakers work TitleWorker,FooWorker
|
7
|
+
# $ sneakers stop
|
8
|
+
# $ sneakers recycle
|
9
|
+
# $ sneakers reload
|
10
|
+
# $ sneakers init
|
11
|
+
#
|
12
|
+
#
|
13
|
+
module Sneakers
|
14
|
+
class CLI < Thor
|
15
|
+
|
16
|
+
SNEAKERS=<<-EOF
|
17
|
+
|
18
|
+
__
|
19
|
+
,--' > Sneakers
|
20
|
+
`=====
|
21
|
+
|
22
|
+
EOF
|
23
|
+
|
24
|
+
BANNER = SNEAKERS
|
25
|
+
|
26
|
+
method_option :debug
|
27
|
+
method_option :daemonize
|
28
|
+
method_option :log
|
29
|
+
method_option :pid_path
|
30
|
+
method_option :require
|
31
|
+
|
32
|
+
desc "work FirstWorker,SecondWorker ... ,NthWorker", "Run workers"
|
33
|
+
def work(workers = "")
|
34
|
+
opts = {
|
35
|
+
:daemonize => !!options[:daemonize]
|
36
|
+
}
|
37
|
+
|
38
|
+
opts[:log] = options[:log] || (opts[:daemonize] ? 'sneakers.log' : STDOUT)
|
39
|
+
opts[:pid_path] = options[:pid_path] if options[:pid_path]
|
40
|
+
|
41
|
+
if opts[:daemonize]
|
42
|
+
puts "*** DEPRACATED: self-daemonization '--daemonize' is considered a bad practice, which is why this feature will be removed in future versions. Please run Sneakers in front, and use things like upstart, systemd, or supervisor to manage it as a daemon."
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
Sneakers.configure(opts)
|
47
|
+
|
48
|
+
require_boot File.expand_path(options[:require]) if options[:require]
|
49
|
+
|
50
|
+
if workers.empty?
|
51
|
+
workers = Sneakers::Worker::Classes
|
52
|
+
else
|
53
|
+
workers, missing_workers = Sneakers::Utils.parse_workers(workers)
|
54
|
+
end
|
55
|
+
|
56
|
+
unless missing_workers.nil? || missing_workers.empty?
|
57
|
+
say "Missing workers: #{missing_workers.join(', ')}" if missing_workers
|
58
|
+
say "Did you `require` properly?"
|
59
|
+
return
|
60
|
+
end
|
61
|
+
|
62
|
+
if workers.empty?
|
63
|
+
say <<-EOF
|
64
|
+
Error: No workers found.
|
65
|
+
Please require your worker classes before specifying in CLI
|
66
|
+
|
67
|
+
$ sneakers work FooWorker
|
68
|
+
^- require this in your code
|
69
|
+
|
70
|
+
EOF
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
r = Sneakers::Runner.new(workers)
|
75
|
+
|
76
|
+
pid = Sneakers::CONFIG[:pid_path]
|
77
|
+
|
78
|
+
say SNEAKERS
|
79
|
+
say "Workers ....: #{em workers.join(', ')}"
|
80
|
+
say "Log ........: #{em (Sneakers::CONFIG[:log] == STDOUT ? 'Console' : Sneakers::CONFIG[:log]) }"
|
81
|
+
say "PID ........: #{em pid}"
|
82
|
+
say ""
|
83
|
+
say (" "*31)+"Process control"
|
84
|
+
say "="*80
|
85
|
+
say "Stop (nicely) ..............: kill -SIGTERM `cat #{pid}`"
|
86
|
+
say "Stop (immediate) ...........: kill -SIGQUIT `cat #{pid}`"
|
87
|
+
say "Restart (nicely) ...........: kill -SIGUSR1 `cat #{pid}`"
|
88
|
+
say "Restart (immediate) ........: kill -SIGHUP `cat #{pid}`"
|
89
|
+
say "Reconfigure ................: kill -SIGUSR2 `cat #{pid}`"
|
90
|
+
say "Scale workers ..............: reconfigure, then restart"
|
91
|
+
say "="*80
|
92
|
+
say ""
|
93
|
+
|
94
|
+
if options[:debug]
|
95
|
+
say "==== configuration ==="
|
96
|
+
say Sneakers::CONFIG.inspect
|
97
|
+
say "======================"
|
98
|
+
end
|
99
|
+
|
100
|
+
r.run
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
private
|
105
|
+
def require_boot(file)
|
106
|
+
load file
|
107
|
+
end
|
108
|
+
|
109
|
+
def em(text)
|
110
|
+
shell.set_color(text, nil, true)
|
111
|
+
end
|
112
|
+
|
113
|
+
def ok(detail=nil)
|
114
|
+
text = detail ? "OK, #{detail}." : "OK."
|
115
|
+
say text, :green
|
116
|
+
end
|
117
|
+
|
118
|
+
def error(detail)
|
119
|
+
say detail, :red
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sneakers
|
2
|
+
module Concerns
|
3
|
+
module Logging
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.send :define_method, :logger do
|
7
|
+
base.logger
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def logger
|
13
|
+
@logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def logger=(logger)
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure_logger(log=nil)
|
21
|
+
if log
|
22
|
+
@logger = log
|
23
|
+
else
|
24
|
+
@logger = Logger.new(STDOUT)
|
25
|
+
@logger.level = Logger::INFO
|
26
|
+
@logger.formatter = Sneakers::Support::ProductionFormatter
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'sneakers/metrics/null_metrics'
|
2
|
+
|
3
|
+
module Sneakers
|
4
|
+
module Concerns
|
5
|
+
module Metrics
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.send :define_method, :metrics do
|
9
|
+
base.metrics
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def metrics
|
15
|
+
@metrics
|
16
|
+
end
|
17
|
+
|
18
|
+
def metrics=(metrics)
|
19
|
+
@metrics = metrics
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure_metrics(metrics=nil)
|
23
|
+
if metrics
|
24
|
+
@metrics = metrics
|
25
|
+
else
|
26
|
+
@metrics = Sneakers::Metrics::NullMetrics.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'sneakers/error_reporter'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Sneakers
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@hash, :to_hash, :[], :[]=, :==, :fetch, :delete, :has_key?
|
9
|
+
|
10
|
+
EXCHANGE_OPTION_DEFAULTS = {
|
11
|
+
:type => :direct,
|
12
|
+
:durable => true,
|
13
|
+
:auto_delete => false,
|
14
|
+
:arguments => {} # Passed as :arguments to Bunny::Channel#exchange
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
QUEUE_OPTION_DEFAULTS = {
|
18
|
+
:durable => true,
|
19
|
+
:auto_delete => false,
|
20
|
+
:exclusive => false,
|
21
|
+
:arguments => {}
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
DEFAULTS = {
|
25
|
+
# Set up default handler which just logs the error.
|
26
|
+
# Remove this in production if you don't want sensitive data logged.
|
27
|
+
:error_reporters => [Sneakers::ErrorReporter::DefaultLogger.new],
|
28
|
+
|
29
|
+
# runner
|
30
|
+
:runner_config_file => nil,
|
31
|
+
:metrics => nil,
|
32
|
+
:daemonize => false,
|
33
|
+
:start_worker_delay => 0.2,
|
34
|
+
:workers => 4,
|
35
|
+
:log => STDOUT,
|
36
|
+
:pid_path => 'sneakers.pid',
|
37
|
+
:amqp_heartbeat => 30,
|
38
|
+
|
39
|
+
# workers
|
40
|
+
:prefetch => 10,
|
41
|
+
:threads => 10,
|
42
|
+
:share_threads => false,
|
43
|
+
:ack => true,
|
44
|
+
:heartbeat => 30,
|
45
|
+
:hooks => {},
|
46
|
+
:exchange => 'sneakers',
|
47
|
+
:exchange_options => EXCHANGE_OPTION_DEFAULTS,
|
48
|
+
:queue_options => QUEUE_OPTION_DEFAULTS
|
49
|
+
}.freeze
|
50
|
+
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
clear
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear
|
57
|
+
@hash = DEFAULTS.dup
|
58
|
+
@hash[:amqp] = ENV.fetch('RABBITMQ_URL', 'amqp://guest:guest@localhost:5672')
|
59
|
+
@hash[:vhost] = AMQ::Settings.parse_amqp_url(@hash[:amqp]).fetch(:vhost, '/')
|
60
|
+
end
|
61
|
+
|
62
|
+
def merge!(hash)
|
63
|
+
hash = hash.dup
|
64
|
+
hash = map_all_deprecated_options(hash)
|
65
|
+
|
66
|
+
# parse vhost from amqp if vhost is not specified explicitly, only
|
67
|
+
# if we're not given a connection to use.
|
68
|
+
if hash[:connection].nil?
|
69
|
+
if hash[:vhost].nil? && !hash[:amqp].nil?
|
70
|
+
hash[:vhost] = AMQ::Settings.parse_amqp_url(hash[:amqp]).fetch(:vhost, '/')
|
71
|
+
end
|
72
|
+
else
|
73
|
+
# If we are given a Bunny object, ignore params we'd otherwise use to
|
74
|
+
# create one. This removes any question about where config params are
|
75
|
+
# coming from, and makes it more likely that downstream code that needs
|
76
|
+
# this info gets it from the right place.
|
77
|
+
[:vhost, :amqp, :heartbeat].each do |k|
|
78
|
+
hash.delete(k)
|
79
|
+
@hash.delete(k)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@hash = deep_merge(@hash, hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
def merge(hash)
|
87
|
+
instance = self.class.new
|
88
|
+
instance.merge! to_hash
|
89
|
+
instance.merge! hash
|
90
|
+
instance
|
91
|
+
end
|
92
|
+
|
93
|
+
def inspect_with_redaction
|
94
|
+
redacted = self.class.new
|
95
|
+
redacted.merge! to_hash
|
96
|
+
|
97
|
+
# redact passwords
|
98
|
+
redacted[:amqp] = redacted[:amqp].sub(/(?<=\Aamqp:\/)[^@]+(?=@)/, "<redacted>") if redacted.has_key?(:amqp)
|
99
|
+
return redacted.inspect_without_redaction
|
100
|
+
end
|
101
|
+
alias_method :inspect_without_redaction, :inspect
|
102
|
+
alias_method :inspect, :inspect_with_redaction
|
103
|
+
|
104
|
+
def map_all_deprecated_options(hash)
|
105
|
+
hash = map_deprecated_options_key(:exchange_options, :exchange_type, :type, true, hash)
|
106
|
+
hash = map_deprecated_options_key(:exchange_options, :exchange_arguments, :arguments, true, hash)
|
107
|
+
hash = map_deprecated_options_key(:exchange_options, :durable, :durable, false, hash)
|
108
|
+
hash = map_deprecated_options_key(:queue_options, :durable, :durable, true, hash)
|
109
|
+
hash = map_deprecated_options_key(:queue_options, :arguments, :arguments, true, hash)
|
110
|
+
hash
|
111
|
+
end
|
112
|
+
|
113
|
+
def map_deprecated_options_key(target_key, deprecated_key, key, delete_deprecated_key, hash = {})
|
114
|
+
return hash if hash[deprecated_key].nil?
|
115
|
+
hash = deep_merge({ target_key => { key => hash[deprecated_key] } }, hash)
|
116
|
+
hash.delete(deprecated_key) if delete_deprecated_key
|
117
|
+
hash
|
118
|
+
end
|
119
|
+
|
120
|
+
def deep_merge(first, second)
|
121
|
+
merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
122
|
+
first.merge(second, &merger)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sneakers
|
2
|
+
class ContentEncoding
|
3
|
+
def self.register(content_encoding: nil, encoder: nil, decoder: nil)
|
4
|
+
# This can be removed when support is dropped for ruby 2.0 and replaced
|
5
|
+
# by a keyword arg with no default value
|
6
|
+
fail ArgumentError, 'missing keyword: content_encoding' if content_encoding.nil?
|
7
|
+
fail ArgumentError, 'missing keyword: encoder' if encoder.nil?
|
8
|
+
fail ArgumentError, 'missing keyword: decoder' if decoder.nil?
|
9
|
+
|
10
|
+
fail ArgumentError, "#{content_encoding} encoder must be a proc" unless encoder.is_a? Proc
|
11
|
+
fail ArgumentError, "#{content_encoding} decoder must be a proc" unless decoder.is_a? Proc
|
12
|
+
|
13
|
+
fail ArgumentError, "#{content_encoding} encoder must accept one argument, the payload" unless encoder.arity == 1
|
14
|
+
fail ArgumentError, "#{content_encoding} decoder must accept one argument, the payload" unless decoder.arity == 1
|
15
|
+
@_encodings[content_encoding] = new(encoder, decoder)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.encode(payload, content_encoding)
|
19
|
+
return payload unless content_encoding
|
20
|
+
@_encodings[content_encoding].encoder.(payload)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.decode(payload, content_encoding)
|
24
|
+
return payload unless content_encoding
|
25
|
+
@_encodings[content_encoding].decoder.(payload)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.reset!
|
29
|
+
@_encodings = Hash.new(
|
30
|
+
new(passthrough, passthrough)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.passthrough
|
35
|
+
->(payload) { payload }
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(encoder, decoder)
|
39
|
+
@encoder = encoder
|
40
|
+
@decoder = decoder
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :encoder, :decoder
|
44
|
+
|
45
|
+
reset!
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sneakers
|
2
|
+
class ContentType
|
3
|
+
def self.register(content_type: nil, serializer: nil, deserializer: nil)
|
4
|
+
# This can be removed when support is dropped for ruby 2.0 and replaced
|
5
|
+
# by a keyword arg with no default value
|
6
|
+
fail ArgumentError, 'missing keyword: content_type' if content_type.nil?
|
7
|
+
fail ArgumentError, 'missing keyword: serializer' if serializer.nil?
|
8
|
+
fail ArgumentError, 'missing keyword: deserializer' if deserializer.nil?
|
9
|
+
|
10
|
+
fail ArgumentError, "#{content_type} serializer must be a proc" unless serializer.is_a? Proc
|
11
|
+
fail ArgumentError, "#{content_type} deserializer must be a proc" unless deserializer.is_a? Proc
|
12
|
+
|
13
|
+
fail ArgumentError, "#{content_type} serializer must accept one argument, the payload" unless serializer.arity == 1
|
14
|
+
fail ArgumentError, "#{content_type} deserializer must accept one argument, the payload" unless deserializer.arity == 1
|
15
|
+
@_types[content_type] = new(serializer, deserializer)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.serialize(payload, content_type)
|
19
|
+
return payload unless content_type
|
20
|
+
@_types[content_type].serializer.(payload)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.deserialize(payload, content_type)
|
24
|
+
return payload unless content_type
|
25
|
+
@_types[content_type].deserializer.(payload)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.reset!
|
29
|
+
@_types = Hash.new(
|
30
|
+
new(passthrough, passthrough)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.passthrough
|
35
|
+
->(payload) { payload }
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(serializer, deserializer)
|
39
|
+
@serializer = serializer
|
40
|
+
@deserializer = deserializer
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :serializer, :deserializer
|
44
|
+
|
45
|
+
reset!
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Ripped off from https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/exception_handler.rb
|
2
|
+
module Sneakers
|
3
|
+
module ErrorReporter
|
4
|
+
class DefaultLogger
|
5
|
+
def call(exception, worker, context_hash)
|
6
|
+
Sneakers.logger.warn(context_hash) unless context_hash.empty?
|
7
|
+
log_string = ''
|
8
|
+
log_string += "[Exception error=#{exception.message.inspect} error_class=#{exception.class} worker_class=#{worker.class}" unless exception.nil?
|
9
|
+
log_string += " backtrace=#{exception.backtrace.take(50).join(',')}" unless exception.nil? || exception.backtrace.nil?
|
10
|
+
log_string += ']'
|
11
|
+
Sneakers.logger.error log_string
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def worker_error(exception, context_hash = {})
|
16
|
+
Sneakers.error_reporters.each do |handler|
|
17
|
+
begin
|
18
|
+
handler.call(exception, self, context_hash)
|
19
|
+
rescue SignalException, SystemExit
|
20
|
+
# ServerEngine handles these exceptions, so they are not expected
|
21
|
+
# to be raised within the error reporter.
|
22
|
+
# Nevertheless, they are listed here to ensure that they are not
|
23
|
+
# caught by the rescue block below.
|
24
|
+
raise
|
25
|
+
rescue Exception => inner_exception
|
26
|
+
Sneakers.logger.error '!!! ERROR REPORTER THREW AN ERROR !!!'
|
27
|
+
Sneakers.logger.error inner_exception
|
28
|
+
Sneakers.logger.error inner_exception.backtrace.join("\n") unless inner_exception.backtrace.nil?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|