sidekiq 4.1.1 → 4.2.0

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.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.github/issue_template.md +4 -0
  3. data/.travis.yml +3 -6
  4. data/4.0-Upgrade.md +3 -0
  5. data/COMM-LICENSE +1 -1
  6. data/Changes.md +69 -0
  7. data/Ent-Changes.md +40 -0
  8. data/Gemfile +6 -5
  9. data/Pro-3.0-Upgrade.md +5 -7
  10. data/Pro-Changes.md +66 -0
  11. data/README.md +2 -1
  12. data/bin/sidekiqload +2 -2
  13. data/lib/sidekiq.rb +5 -1
  14. data/lib/sidekiq/api.rb +3 -1
  15. data/lib/sidekiq/cli.rb +15 -2
  16. data/lib/sidekiq/client.rb +11 -3
  17. data/lib/sidekiq/core_ext.rb +1 -0
  18. data/lib/sidekiq/exception_handler.rb +2 -1
  19. data/lib/sidekiq/extensions/action_mailer.rb +1 -0
  20. data/lib/sidekiq/extensions/active_record.rb +1 -0
  21. data/lib/sidekiq/extensions/class_methods.rb +1 -0
  22. data/lib/sidekiq/extensions/generic_proxy.rb +1 -0
  23. data/lib/sidekiq/fetch.rb +1 -0
  24. data/lib/sidekiq/launcher.rb +9 -3
  25. data/lib/sidekiq/logging.rb +2 -1
  26. data/lib/sidekiq/manager.rb +2 -0
  27. data/lib/sidekiq/middleware/chain.rb +1 -0
  28. data/lib/sidekiq/middleware/i18n.rb +1 -0
  29. data/lib/sidekiq/middleware/server/retry_jobs.rb +7 -7
  30. data/lib/sidekiq/paginator.rb +1 -0
  31. data/lib/sidekiq/processor.rb +29 -25
  32. data/lib/sidekiq/rails.rb +18 -0
  33. data/lib/sidekiq/redis_connection.rb +6 -3
  34. data/lib/sidekiq/scheduled.rb +2 -0
  35. data/lib/sidekiq/testing.rb +1 -0
  36. data/lib/sidekiq/testing/inline.rb +1 -0
  37. data/lib/sidekiq/util.rb +1 -0
  38. data/lib/sidekiq/version.rb +2 -1
  39. data/lib/sidekiq/web.rb +80 -202
  40. data/lib/sidekiq/web/action.rb +99 -0
  41. data/lib/sidekiq/web/application.rb +335 -0
  42. data/lib/sidekiq/{web_helpers.rb → web/helpers.rb} +31 -11
  43. data/lib/sidekiq/web/router.rb +96 -0
  44. data/lib/sidekiq/worker.rb +1 -0
  45. data/sidekiq.gemspec +2 -2
  46. data/test/fake_env.rb +1 -0
  47. data/test/helper.rb +1 -0
  48. data/test/test_actors.rb +1 -0
  49. data/test/test_api.rb +1 -0
  50. data/test/test_cli.rb +15 -2
  51. data/test/test_client.rb +34 -0
  52. data/test/test_exception_handler.rb +2 -1
  53. data/test/test_extensions.rb +1 -0
  54. data/test/test_fetch.rb +1 -0
  55. data/test/test_launcher.rb +17 -2
  56. data/test/test_logging.rb +1 -0
  57. data/test/test_manager.rb +1 -0
  58. data/test/test_middleware.rb +1 -0
  59. data/test/test_processor.rb +1 -0
  60. data/test/test_rails.rb +1 -0
  61. data/test/test_redis_connection.rb +7 -1
  62. data/test/test_retry.rb +1 -0
  63. data/test/test_scheduled.rb +1 -0
  64. data/test/test_scheduling.rb +1 -0
  65. data/test/test_sidekiq.rb +1 -0
  66. data/test/test_testing.rb +1 -0
  67. data/test/test_testing_fake.rb +1 -0
  68. data/test/test_testing_inline.rb +2 -1
  69. data/test/test_util.rb +1 -0
  70. data/test/test_web.rb +53 -5
  71. data/test/test_web_helpers.rb +1 -0
  72. data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
  73. data/web/assets/javascripts/application.js +5 -0
  74. data/web/assets/javascripts/locales/{jquery.timeago.no.js → jquery.timeago.nb.js} +1 -1
  75. data/web/assets/stylesheets/application.css +26 -1
  76. data/web/assets/stylesheets/bootstrap.css +4 -8
  77. data/web/locales/de.yml +1 -1
  78. data/web/locales/en.yml +1 -0
  79. data/web/locales/ru.yml +3 -0
  80. data/web/views/_footer.erb +1 -1
  81. data/web/views/_nav.erb +1 -1
  82. data/web/views/busy.erb +4 -4
  83. data/web/views/dashboard.erb +2 -2
  84. data/web/views/dead.erb +1 -1
  85. data/web/views/layout.erb +3 -3
  86. data/web/views/morgue.erb +2 -2
  87. data/web/views/queue.erb +3 -3
  88. data/web/views/queues.erb +1 -1
  89. data/web/views/retries.erb +2 -2
  90. data/web/views/retry.erb +1 -1
  91. data/web/views/scheduled.erb +2 -2
  92. data/web/views/scheduled_job_info.erb +1 -1
  93. metadata +17 -29
  94. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  95. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  96. data/web/assets/images/status/active.png +0 -0
  97. data/web/assets/images/status/idle.png +0 -0
  98. data/web/views/_poll_js.erb +0 -5
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  begin
2
3
  require 'active_support/core_ext/class/attribute'
3
4
  rescue LoadError
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sidekiq'
2
3
 
3
4
  module Sidekiq
@@ -5,7 +6,7 @@ module Sidekiq
5
6
 
6
7
  class Logger
7
8
  def call(ex, ctxHash)
8
- Sidekiq.logger.warn(ctxHash) if !ctxHash.empty?
9
+ Sidekiq.logger.warn(Sidekiq.dump_json(ctxHash)) if !ctxHash.empty?
9
10
  Sidekiq.logger.warn "#{ex.class.name}: #{ex.message}"
10
11
  Sidekiq.logger.warn ex.backtrace.join("\n") unless ex.backtrace.nil?
11
12
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sidekiq/extensions/generic_proxy'
2
3
 
3
4
  module Sidekiq
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sidekiq/extensions/generic_proxy'
2
3
 
3
4
  module Sidekiq
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sidekiq/extensions/generic_proxy'
2
3
 
3
4
  module Sidekiq
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'yaml'
2
3
 
3
4
  module Sidekiq
data/lib/sidekiq/fetch.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sidekiq'
2
3
 
3
4
  module Sidekiq
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: utf-8
2
3
  require 'sidekiq/manager'
3
4
  require 'sidekiq/fetch'
@@ -79,7 +80,7 @@ module Sidekiq
79
80
  workers_key = "#{key}:workers".freeze
80
81
  nowdate = Time.now.utc.strftime("%Y-%m-%d".freeze)
81
82
  Sidekiq.redis do |conn|
82
- conn.pipelined do
83
+ conn.multi do
83
84
  conn.incrby("stat:processed".freeze, procd)
84
85
  conn.incrby("stat:processed:#{nowdate}", procd)
85
86
  conn.incrby("stat:failed".freeze, fails)
@@ -88,19 +89,24 @@ module Sidekiq
88
89
  Processor::WORKER_STATE.each_pair do |tid, hash|
89
90
  conn.hset(workers_key, tid, Sidekiq.dump_json(hash))
90
91
  end
92
+ conn.expire(workers_key, 60)
91
93
  end
92
94
  end
93
95
  fails = procd = 0
94
96
 
95
- _, _, _, msg = Sidekiq.redis do |conn|
96
- conn.pipelined do
97
+ _, exists, _, _, msg = Sidekiq.redis do |conn|
98
+ conn.multi do
97
99
  conn.sadd('processes', key)
100
+ conn.exists(key)
98
101
  conn.hmset(key, 'info', json, 'busy', Processor::WORKER_STATE.size, 'beat', Time.now.to_f, 'quiet', @done)
99
102
  conn.expire(key, 60)
100
103
  conn.rpop("#{key}-signals")
101
104
  end
102
105
  end
103
106
 
107
+ # first heartbeat or recovering from an outage and need to reestablish our heartbeat
108
+ fire_event(:heartbeat) if !exists
109
+
104
110
  return unless msg
105
111
 
106
112
  if JVM_RESERVED_SIGNALS.include?(msg)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'time'
2
3
  require 'logger'
3
4
  require 'fcntl'
@@ -47,7 +48,7 @@ module Sidekiq
47
48
  end
48
49
 
49
50
  def self.logger=(log)
50
- @logger = (log ? log : Logger.new('/dev/null'))
51
+ @logger = (log ? log : Logger.new(File::NULL))
51
52
  end
52
53
 
53
54
  # This reopens ALL logfiles in the process that have been rotated
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: utf-8
2
3
  require 'sidekiq/util'
3
4
  require 'sidekiq/processor'
4
5
  require 'sidekiq/fetch'
5
6
  require 'thread'
7
+ require 'set'
6
8
 
7
9
  module Sidekiq
8
10
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Sidekiq
2
3
  # Middleware is code configured to run before/after
3
4
  # a message is processed. It is patterned after Rack
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Simple middleware to save the current locale and restore it when the job executes.
3
4
  # Use it by requiring it in your initializer:
@@ -8,14 +8,14 @@ module Sidekiq
8
8
  # Automatically retry jobs that fail in Sidekiq.
9
9
  # Sidekiq's retry support assumes a typical development lifecycle:
10
10
  #
11
- # 0. push some code changes with a bug in it
12
- # 1. bug causes job processing to fail, sidekiq's middleware captures
13
- # the job and pushes it onto a retry queue
14
- # 2. sidekiq retries jobs in the retry queue multiple times with
15
- # an exponential delay, the job continues to fail
16
- # 3. after a few days, a developer deploys a fix. the job is
11
+ # 0. Push some code changes with a bug in it.
12
+ # 1. Bug causes job processing to fail, Sidekiq's middleware captures
13
+ # the job and pushes it onto a retry queue.
14
+ # 2. Sidekiq retries jobs in the retry queue multiple times with
15
+ # an exponential delay, the job continues to fail.
16
+ # 3. After a few days, a developer deploys a fix. The job is
17
17
  # reprocessed successfully.
18
- # 4. once retries are exhausted, sidekiq will give up and move the
18
+ # 4. Once retries are exhausted, Sidekiq will give up and move the
19
19
  # job to the Dead Job Queue (aka morgue) where it must be dealt with
20
20
  # manually in the Web UI.
21
21
  # 5. After 6 months on the DJQ, Sidekiq will discard the job.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Sidekiq
2
3
  module Paginator
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sidekiq/util'
2
3
  require 'sidekiq/fetch'
3
4
  require 'thread'
@@ -35,6 +36,7 @@ module Sidekiq
35
36
  @job = nil
36
37
  @thread = nil
37
38
  @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
39
+ @reloader = Sidekiq.options[:reloader]
38
40
  end
39
41
 
40
42
  def terminate(wait=false)
@@ -117,33 +119,35 @@ module Sidekiq
117
119
  jobstr = work.job
118
120
  queue = work.queue_name
119
121
 
120
- ack = false
121
- begin
122
- job = Sidekiq.load_json(jobstr)
123
- klass = job['class'.freeze].constantize
124
- worker = klass.new
125
- worker.jid = job['jid'.freeze]
126
-
127
- stats(worker, job, queue) do
128
- Sidekiq.server_middleware.invoke(worker, job, queue) do
129
- # Only ack if we either attempted to start this job or
130
- # successfully completed it. This prevents us from
131
- # losing jobs if a middleware raises an exception before yielding
132
- ack = true
133
- execute_job(worker, cloned(job['args'.freeze]))
122
+ @reloader.call do
123
+ ack = false
124
+ begin
125
+ job = Sidekiq.load_json(jobstr)
126
+ klass = job['class'.freeze].constantize
127
+ worker = klass.new
128
+ worker.jid = job['jid'.freeze]
129
+
130
+ stats(worker, job, queue) do
131
+ Sidekiq.server_middleware.invoke(worker, job, queue) do
132
+ # Only ack if we either attempted to start this job or
133
+ # successfully completed it. This prevents us from
134
+ # losing jobs if a middleware raises an exception before yielding
135
+ ack = true
136
+ execute_job(worker, cloned(job['args'.freeze]))
137
+ end
134
138
  end
139
+ ack = true
140
+ rescue Sidekiq::Shutdown
141
+ # Had to force kill this job because it didn't finish
142
+ # within the timeout. Don't acknowledge the work since
143
+ # we didn't properly finish it.
144
+ ack = false
145
+ rescue Exception => ex
146
+ handle_exception(ex, job || { :job => jobstr })
147
+ raise
148
+ ensure
149
+ work.acknowledge if ack
135
150
  end
136
- ack = true
137
- rescue Sidekiq::Shutdown
138
- # Had to force kill this job because it didn't finish
139
- # within the timeout. Don't acknowledge the work since
140
- # we didn't properly finish it.
141
- ack = false
142
- rescue Exception => ex
143
- handle_exception(ex, job || { :job => jobstr })
144
- raise
145
- ensure
146
- work.acknowledge if ack
147
151
  end
148
152
  end
149
153
 
data/lib/sidekiq/rails.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Sidekiq
2
3
  def self.hook_rails!
3
4
  return if defined?(@delay_removed)
@@ -34,5 +35,22 @@ module Sidekiq
34
35
  initializer 'sidekiq' do
35
36
  Sidekiq.hook_rails!
36
37
  end
38
+
39
+ class Reloader
40
+ def initialize(app = ::Rails.application)
41
+ Sidekiq.logger.debug "Enabling Rails 5+ live code reloading, so hot!" unless app.config.cache_classes
42
+ @app = app
43
+ end
44
+
45
+ def call
46
+ @app.reloader.wrap do
47
+ yield
48
+ end
49
+ end
50
+
51
+ def inspect
52
+ "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
53
+ end
54
+ end
37
55
  end if defined?(::Rails)
38
56
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'connection_pool'
2
3
  require 'redis'
3
4
  require 'uri'
@@ -7,6 +8,8 @@ module Sidekiq
7
8
  class << self
8
9
 
9
10
  def create(options={})
11
+ options = options.symbolize_keys
12
+
10
13
  options[:url] ||= determine_redis_provider
11
14
 
12
15
  size = options[:size] || (Sidekiq.server? ? (Sidekiq.options[:concurrency] + 5) : 5)
@@ -32,7 +35,7 @@ module Sidekiq
32
35
  # - enterprise's leader election
33
36
  # - enterprise's cron support
34
37
  def verify_sizing(size, concurrency)
35
- raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work, your pool has #{size} connections but really needs to have at least #{concurrency + 2}" if size <= concurrency
38
+ raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but really needs to have at least #{concurrency + 2}" if size <= concurrency
36
39
  end
37
40
 
38
41
  def build_client(options)
@@ -44,8 +47,8 @@ module Sidekiq
44
47
  require 'redis/namespace'
45
48
  Redis::Namespace.new(namespace, :redis => client)
46
49
  rescue LoadError
47
- Sidekiq.logger.error("Your Redis configuration use the namespace '#{namespace}' but the redis-namespace gem not included in Gemfile." \
48
- "Add the gem to your Gemfile in case you would like to keep using a namespace, otherwise remove the namespace parameter.")
50
+ Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
51
+ "Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
49
52
  exit(-127)
50
53
  end
51
54
  else
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sidekiq'
2
3
  require 'sidekiq/util'
3
4
  require 'sidekiq/api'
@@ -45,6 +46,7 @@ module Sidekiq
45
46
  @enq = (Sidekiq.options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new
46
47
  @sleeper = ConnectionPool::TimedStack.new
47
48
  @done = false
49
+ @thread = nil
48
50
  end
49
51
 
50
52
  # Shut down this instance, will pause until the thread is dead.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'securerandom'
2
3
  require 'sidekiq'
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sidekiq/testing'
2
3
 
3
4
  ##
data/lib/sidekiq/util.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'socket'
2
3
  require 'securerandom'
3
4
  require 'sidekiq/exception_handler'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Sidekiq
2
- VERSION = "4.1.1"
3
+ VERSION = "4.2.0"
3
4
  end
data/lib/sidekiq/web.rb CHANGED
@@ -1,25 +1,28 @@
1
+ # frozen_string_literal: true
1
2
  require 'erb'
2
- require 'yaml'
3
- require 'sinatra/base'
4
3
 
5
4
  require 'sidekiq'
6
5
  require 'sidekiq/api'
7
6
  require 'sidekiq/paginator'
8
- require 'sidekiq/web_helpers'
7
+ require 'sidekiq/web/helpers'
9
8
 
10
- module Sidekiq
11
- class Web < Sinatra::Base
12
- include Sidekiq::Paginator
9
+ require 'sidekiq/web/router'
10
+ require 'sidekiq/web/action'
11
+ require 'sidekiq/web/application'
13
12
 
14
- enable :sessions
15
- use ::Rack::Protection, :use => :authenticity_token unless ENV['RACK_ENV'] == 'test'
13
+ require 'rack/protection'
16
14
 
17
- set :root, File.expand_path(File.dirname(__FILE__) + "/../../web")
18
- set :public_folder, proc { "#{root}/assets" }
19
- set :views, proc { "#{root}/views" }
20
- set :locales, ["#{root}/locales"]
15
+ require 'rack/builder'
16
+ require 'rack/file'
17
+ require 'rack/session/cookie'
21
18
 
22
- helpers WebHelpers
19
+ module Sidekiq
20
+ class Web
21
+ ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
22
+ VIEWS = "#{ROOT}/views".freeze
23
+ LOCALES = ["#{ROOT}/locales".freeze]
24
+ LAYOUT = "#{VIEWS}/layout.erb".freeze
25
+ ASSETS = "#{ROOT}/assets".freeze
23
26
 
24
27
  DEFAULT_TABS = {
25
28
  "Dashboard" => '',
@@ -31,6 +34,18 @@ module Sidekiq
31
34
  }
32
35
 
33
36
  class << self
37
+ def settings
38
+ self
39
+ end
40
+
41
+ def middlewares
42
+ @middlewares ||= []
43
+ end
44
+
45
+ def use(*middleware_args, &block)
46
+ middlewares << [middleware_args, block]
47
+ end
48
+
34
49
  def default_tabs
35
50
  DEFAULT_TABS
36
51
  end
@@ -40,234 +55,97 @@ module Sidekiq
40
55
  end
41
56
  alias_method :tabs, :custom_tabs
42
57
 
43
- attr_accessor :app_url
44
- end
45
-
46
- get "/busy" do
47
- erb :busy
48
- end
49
-
50
- post "/busy" do
51
- if params['identity']
52
- p = Sidekiq::Process.new('identity' => params['identity'])
53
- p.quiet! if params[:quiet]
54
- p.stop! if params[:stop]
55
- else
56
- processes.each do |pro|
57
- pro.quiet! if params[:quiet]
58
- pro.stop! if params[:stop]
59
- end
58
+ def locales
59
+ @locales ||= LOCALES
60
60
  end
61
- redirect "#{root_path}busy"
62
- end
63
61
 
64
- get "/queues" do
65
- @queues = Sidekiq::Queue.all
66
- erb :queues
67
- end
68
-
69
- get "/queues/:name" do
70
- halt 404 unless params[:name]
71
- @count = (params[:count] || 25).to_i
72
- @name = params[:name]
73
- @queue = Sidekiq::Queue.new(@name)
74
- (@current_page, @total_size, @messages) = page("queue:#{@name}", params[:page], @count)
75
- @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
76
- erb :queue
77
- end
78
-
79
- post "/queues/:name" do
80
- Sidekiq::Queue.new(params[:name]).clear
81
- redirect "#{root_path}queues"
82
- end
83
-
84
- post "/queues/:name/delete" do
85
- Sidekiq::Job.new(params[:key_val], params[:name]).delete
86
- redirect_with_query("#{root_path}queues/#{params[:name]}")
87
- end
88
-
89
- get '/morgue' do
90
- @count = (params[:count] || 25).to_i
91
- (@current_page, @total_size, @dead) = page("dead", params[:page], @count, reverse: true)
92
- @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
93
- erb :morgue
94
- end
95
-
96
- get "/morgue/:key" do
97
- halt 404 unless params['key']
98
- @dead = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first
99
- redirect "#{root_path}morgue" if @dead.nil?
100
- erb :dead
101
- end
102
-
103
- post '/morgue' do
104
- redirect request.path unless params['key']
105
-
106
- params['key'].each do |key|
107
- job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
108
- retry_or_delete_or_kill job, params if job
62
+ def views
63
+ @views ||= VIEWS
109
64
  end
110
- redirect_with_query("#{root_path}morgue")
111
- end
112
65
 
113
- post "/morgue/all/delete" do
114
- Sidekiq::DeadSet.new.clear
115
- redirect "#{root_path}morgue"
66
+ attr_accessor :app_url, :session_secret, :redis_pool
67
+ attr_writer :locales, :views
116
68
  end
117
69
 
118
- post "/morgue/all/retry" do
119
- Sidekiq::DeadSet.new.retry_all
120
- redirect "#{root_path}morgue"
70
+ def settings
71
+ self.class.settings
121
72
  end
122
73
 
123
- post "/morgue/:key" do
124
- halt 404 unless params['key']
125
- job = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first
126
- retry_or_delete_or_kill job, params if job
127
- redirect_with_query("#{root_path}morgue")
74
+ def use(*middleware_args, &block)
75
+ middlewares << [middleware_args, block]
128
76
  end
129
77
 
130
-
131
- get '/retries' do
132
- @count = (params[:count] || 25).to_i
133
- (@current_page, @total_size, @retries) = page("retry", params[:page], @count)
134
- @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
135
- erb :retries
136
- end
137
-
138
- get "/retries/:key" do
139
- @retry = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first
140
- redirect "#{root_path}retries" if @retry.nil?
141
- erb :retry
142
- end
143
-
144
- post '/retries' do
145
- redirect request.path unless params['key']
146
-
147
- params['key'].each do |key|
148
- job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
149
- retry_or_delete_or_kill job, params if job
150
- end
151
- redirect_with_query("#{root_path}retries")
78
+ def middlewares
79
+ @middlewares ||= Web.middlewares.dup
152
80
  end
153
81
 
154
- post "/retries/all/delete" do
155
- Sidekiq::RetrySet.new.clear
156
- redirect "#{root_path}retries"
82
+ def call(env)
83
+ app.call(env)
157
84
  end
158
85
 
159
- post "/retries/all/retry" do
160
- Sidekiq::RetrySet.new.retry_all
161
- redirect "#{root_path}retries"
86
+ def self.call(env)
87
+ @app ||= new
88
+ @app.call(env)
162
89
  end
163
90
 
164
- post "/retries/:key" do
165
- job = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first
166
- retry_or_delete_or_kill job, params if job
167
- redirect_with_query("#{root_path}retries")
91
+ def app
92
+ @app ||= build
168
93
  end
169
94
 
170
- get '/scheduled' do
171
- @count = (params[:count] || 25).to_i
172
- (@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count)
173
- @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
174
- erb :scheduled
95
+ def self.register(extension)
96
+ extension.registered(WebApplication)
175
97
  end
176
98
 
177
- get "/scheduled/:key" do
178
- @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first
179
- redirect "#{root_path}scheduled" if @job.nil?
180
- erb :scheduled_job_info
181
- end
182
-
183
- post '/scheduled' do
184
- redirect request.path unless params['key']
99
+ private
185
100
 
186
- params['key'].each do |key|
187
- job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
188
- delete_or_add_queue job, params if job
101
+ def using?(middleware)
102
+ middlewares.any? do |(m,_)|
103
+ m.kind_of?(Array) && (m[0] == middleware || m[0].kind_of?(middleware))
189
104
  end
190
- redirect_with_query("#{root_path}scheduled")
191
- end
192
-
193
- post "/scheduled/:key" do
194
- halt 404 unless params['key']
195
- job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first
196
- delete_or_add_queue job, params if job
197
- redirect_with_query("#{root_path}scheduled")
198
- end
199
-
200
- get '/' do
201
- @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
202
- stats_history = Sidekiq::Stats::History.new((params[:days] || 30).to_i)
203
- @processed_history = stats_history.processed
204
- @failed_history = stats_history.failed
205
- erb :dashboard
206
105
  end
207
106
 
208
- REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
107
+ def build
108
+ middlewares = self.middlewares
109
+ klass = self.class
209
110
 
210
- get '/dashboard/stats' do
211
- redirect "#{root_path}stats"
212
- end
213
-
214
- get '/stats' do
215
- sidekiq_stats = Sidekiq::Stats.new
216
- redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
217
-
218
- content_type :json
219
- Sidekiq.dump_json(
220
- sidekiq: {
221
- processed: sidekiq_stats.processed,
222
- failed: sidekiq_stats.failed,
223
- busy: sidekiq_stats.workers_size,
224
- processes: sidekiq_stats.processes_size,
225
- enqueued: sidekiq_stats.enqueued,
226
- scheduled: sidekiq_stats.scheduled_size,
227
- retries: sidekiq_stats.retry_size,
228
- dead: sidekiq_stats.dead_size,
229
- default_latency: sidekiq_stats.default_queue_latency
230
- },
231
- redis: redis_stats
232
- )
233
- end
111
+ unless using?(::Rack::Protection) || ENV['RACK_ENV'] == 'test'
112
+ middlewares.unshift [[::Rack::Protection, { use: :authenticity_token }], nil]
113
+ end
234
114
 
235
- get '/stats/queues' do
236
- queue_stats = Sidekiq::Stats::Queues.new
115
+ unless using? ::Rack::Session::Cookie
116
+ unless secret = Web.session_secret
117
+ require 'securerandom'
118
+ secret = SecureRandom.hex(64)
119
+ end
237
120
 
238
- content_type :json
239
- Sidekiq.dump_json(
240
- queue_stats.lengths
241
- )
242
- end
121
+ middlewares.unshift [[::Rack::Session::Cookie, { secret: secret }], nil]
122
+ end
243
123
 
244
- private
124
+ ::Rack::Builder.new do
125
+ %w(stylesheets javascripts images).each do |asset_dir|
126
+ map "/#{asset_dir}" do
127
+ run ::Rack::File.new("#{ASSETS}/#{asset_dir}", { 'Cache-Control' => 'public, max-age=86400' })
128
+ end
129
+ end
245
130
 
246
- def retry_or_delete_or_kill job, params
247
- if params['retry']
248
- job.retry
249
- elsif params['delete']
250
- job.delete
251
- elsif params['kill']
252
- job.kill
253
- end
254
- end
131
+ middlewares.each {|middleware, block| use(*middleware, &block) }
255
132
 
256
- def delete_or_add_queue job, params
257
- if params['delete']
258
- job.delete
259
- elsif params['add_to_queue']
260
- job.add_to_queue
133
+ run WebApplication.new(klass)
261
134
  end
262
135
  end
263
136
  end
137
+
138
+ Sidekiq::WebApplication.helpers WebHelpers
139
+ Sidekiq::WebApplication.helpers Sidekiq::Paginator
140
+
141
+ Sidekiq::WebAction.class_eval "def _render\n#{ERB.new(File.read(Web::LAYOUT)).src}\nend"
264
142
  end
265
143
 
266
144
  if defined?(::ActionDispatch::Request::Session) &&
267
145
  !::ActionDispatch::Request::Session.respond_to?(:each)
268
146
  # mperham/sidekiq#2460
269
147
  # Rack apps can't reuse the Rails session store without
270
- # this monkeypatch
148
+ # this monkeypatch, fixed in Rails 5.
271
149
  class ActionDispatch::Request::Session
272
150
  def each(&block)
273
151
  hash = self.to_hash