kicks 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +24 -0
  3. data/.gitignore +12 -0
  4. data/ChangeLog.md +142 -0
  5. data/Dockerfile +24 -0
  6. data/Dockerfile.slim +20 -0
  7. data/Gemfile +8 -0
  8. data/Guardfile +8 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +209 -0
  11. data/Rakefile +12 -0
  12. data/bin/sneakers +6 -0
  13. data/docker-compose.yml +24 -0
  14. data/examples/benchmark_worker.rb +22 -0
  15. data/examples/max_retry_handler.rb +68 -0
  16. data/examples/metrics_worker.rb +34 -0
  17. data/examples/middleware_worker.rb +36 -0
  18. data/examples/newrelic_metrics_worker.rb +40 -0
  19. data/examples/profiling_worker.rb +69 -0
  20. data/examples/sneakers.conf.rb.example +11 -0
  21. data/examples/title_scraper.rb +36 -0
  22. data/examples/workflow_worker.rb +23 -0
  23. data/kicks.gemspec +44 -0
  24. data/lib/sneakers/cli.rb +122 -0
  25. data/lib/sneakers/concerns/logging.rb +34 -0
  26. data/lib/sneakers/concerns/metrics.rb +34 -0
  27. data/lib/sneakers/configuration.rb +125 -0
  28. data/lib/sneakers/content_encoding.rb +47 -0
  29. data/lib/sneakers/content_type.rb +47 -0
  30. data/lib/sneakers/error_reporter.rb +33 -0
  31. data/lib/sneakers/errors.rb +2 -0
  32. data/lib/sneakers/handlers/maxretry.rb +219 -0
  33. data/lib/sneakers/handlers/oneshot.rb +26 -0
  34. data/lib/sneakers/metrics/logging_metrics.rb +16 -0
  35. data/lib/sneakers/metrics/newrelic_metrics.rb +32 -0
  36. data/lib/sneakers/metrics/null_metrics.rb +13 -0
  37. data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
  38. data/lib/sneakers/middleware/config.rb +23 -0
  39. data/lib/sneakers/publisher.rb +49 -0
  40. data/lib/sneakers/queue.rb +87 -0
  41. data/lib/sneakers/runner.rb +91 -0
  42. data/lib/sneakers/spawner.rb +30 -0
  43. data/lib/sneakers/support/production_formatter.rb +11 -0
  44. data/lib/sneakers/support/utils.rb +18 -0
  45. data/lib/sneakers/tasks.rb +66 -0
  46. data/lib/sneakers/version.rb +3 -0
  47. data/lib/sneakers/worker.rb +162 -0
  48. data/lib/sneakers/workergroup.rb +60 -0
  49. data/lib/sneakers.rb +125 -0
  50. data/log/.gitkeep +0 -0
  51. data/scripts/local_integration +2 -0
  52. data/scripts/local_worker +3 -0
  53. data/spec/fixtures/integration_worker.rb +18 -0
  54. data/spec/fixtures/require_worker.rb +23 -0
  55. data/spec/gzip_helper.rb +15 -0
  56. data/spec/sneakers/cli_spec.rb +75 -0
  57. data/spec/sneakers/concerns/logging_spec.rb +39 -0
  58. data/spec/sneakers/concerns/metrics_spec.rb +38 -0
  59. data/spec/sneakers/configuration_spec.rb +97 -0
  60. data/spec/sneakers/content_encoding_spec.rb +81 -0
  61. data/spec/sneakers/content_type_spec.rb +81 -0
  62. data/spec/sneakers/integration_spec.rb +158 -0
  63. data/spec/sneakers/publisher_spec.rb +179 -0
  64. data/spec/sneakers/queue_spec.rb +169 -0
  65. data/spec/sneakers/runner_spec.rb +70 -0
  66. data/spec/sneakers/sneakers_spec.rb +77 -0
  67. data/spec/sneakers/support/utils_spec.rb +44 -0
  68. data/spec/sneakers/tasks/sneakers_run_spec.rb +115 -0
  69. data/spec/sneakers/worker_handlers_spec.rb +469 -0
  70. data/spec/sneakers/worker_spec.rb +712 -0
  71. data/spec/sneakers/workergroup_spec.rb +83 -0
  72. data/spec/spec_helper.rb +21 -0
  73. 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,11 @@
1
+ workers 2
2
+ amqp "amqp://guest:guest@localhost:55672"
3
+
4
+ before_fork do
5
+ Sneakers::logger.info " ** im before-fork'en ** "
6
+ end
7
+
8
+
9
+ after_fork do
10
+ Sneakers::logger.info " !! im after forke'n !! "
11
+ end
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ module Sneakers
2
+ end