kicks 3.0.0.pre

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 (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