resque-integration 3.4.1

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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +28 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +3 -0
  5. data/Appraisals +27 -0
  6. data/CHANGELOG.md +311 -0
  7. data/Gemfile +4 -0
  8. data/README.md +281 -0
  9. data/Rakefile +1 -0
  10. data/app/assets/javascripts/standalone/progress_bar.js +47 -0
  11. data/app/controllers/resque/jobs_controller.rb +42 -0
  12. data/app/controllers/resque/queues/info_controller.rb +9 -0
  13. data/app/controllers/resque/queues/status_controller.rb +38 -0
  14. data/app/views/shared/job_progress_bar.html.haml +17 -0
  15. data/bin/resque-status +59 -0
  16. data/config/routes.rb +13 -0
  17. data/config.ru +9 -0
  18. data/dip.yml +44 -0
  19. data/docker-compose.development.yml +12 -0
  20. data/docker-compose.drone.yml +6 -0
  21. data/docker-compose.yml +10 -0
  22. data/lib/generators/resque/integration/install/install_generator.rb +38 -0
  23. data/lib/resque/integration/backtrace.rb +29 -0
  24. data/lib/resque/integration/configuration.rb +238 -0
  25. data/lib/resque/integration/continuous.rb +75 -0
  26. data/lib/resque/integration/engine.rb +103 -0
  27. data/lib/resque/integration/extensions/job.rb +17 -0
  28. data/lib/resque/integration/extensions/worker.rb +17 -0
  29. data/lib/resque/integration/extensions.rb +8 -0
  30. data/lib/resque/integration/failure_backends/queues_totals.rb +37 -0
  31. data/lib/resque/integration/failure_backends.rb +7 -0
  32. data/lib/resque/integration/god.erb +99 -0
  33. data/lib/resque/integration/hooks.rb +72 -0
  34. data/lib/resque/integration/logs_rotator.rb +95 -0
  35. data/lib/resque/integration/monkey_patch/verbose_formatter.rb +10 -0
  36. data/lib/resque/integration/ordered.rb +142 -0
  37. data/lib/resque/integration/priority.rb +89 -0
  38. data/lib/resque/integration/queues_info/age.rb +53 -0
  39. data/lib/resque/integration/queues_info/config.rb +96 -0
  40. data/lib/resque/integration/queues_info/size.rb +33 -0
  41. data/lib/resque/integration/queues_info.rb +55 -0
  42. data/lib/resque/integration/tasks/hooks.rake +49 -0
  43. data/lib/resque/integration/tasks/lock.rake +37 -0
  44. data/lib/resque/integration/tasks/resque.rake +101 -0
  45. data/lib/resque/integration/unique.rb +218 -0
  46. data/lib/resque/integration/version.rb +5 -0
  47. data/lib/resque/integration.rb +146 -0
  48. data/lib/resque-integration.rb +1 -0
  49. data/resque-integration.gemspec +40 -0
  50. data/spec/fixtures/resque_queues.yml +45 -0
  51. data/spec/resque/controllers/jobs_controller_spec.rb +65 -0
  52. data/spec/resque/integration/configuration_spec.rb +147 -0
  53. data/spec/resque/integration/continuous_spec.rb +122 -0
  54. data/spec/resque/integration/failure_backends/queues_totals_spec.rb +105 -0
  55. data/spec/resque/integration/ordered_spec.rb +87 -0
  56. data/spec/resque/integration/priority_spec.rb +94 -0
  57. data/spec/resque/integration/queues_info_spec.rb +402 -0
  58. data/spec/resque/integration/unique_spec.rb +184 -0
  59. data/spec/resque/integration_spec.rb +105 -0
  60. data/spec/shared/resque_inline.rb +10 -0
  61. data/spec/spec_helper.rb +28 -0
  62. data/vendor/assets/images/progressbar/white.gif +0 -0
  63. data/vendor/assets/javascripts/jquery.progressbar.js +177 -0
  64. data/vendor/assets/stylesheets/jquery.progressbar.css.erb +33 -0
  65. data/vendor/assets/stylesheets/jquery.progressbar.no_pipeline.css +33 -0
  66. metadata +402 -0
@@ -0,0 +1,95 @@
1
+ # coding: utf-8
2
+ module Resque
3
+ module Integration
4
+ class LogsRotator
5
+ class << self
6
+
7
+ # Перенаправить стандартный вывод в файл
8
+ def redirect_std
9
+ log_path = ::Resque.config.log_file
10
+ STDOUT.reopen File.open(log_path, 'a')
11
+ STDERR.reopen File.open(log_path, 'a')
12
+ STDOUT.sync = STDERR.sync = true
13
+ end
14
+
15
+ def register_hup_signal
16
+ Signal.trap('HUP') { ::Resque::Integration::LogsRotator.reopen_logs }
17
+ end
18
+
19
+ # Переоткрытие всех логов
20
+ #
21
+ # @see Unicorn::Utils
22
+ def reopen_logs
23
+ to_reopen = []
24
+ nr = 0
25
+ ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
26
+
27
+ to_reopen.each do |fp|
28
+ orig_st = begin
29
+ fp.stat
30
+ rescue IOError, Errno::EBADF # race
31
+ next
32
+ end
33
+
34
+ begin
35
+ b = File.stat(fp.path)
36
+ next if orig_st.ino == b.ino && orig_st.dev == b.dev
37
+ rescue Errno::ENOENT
38
+ end
39
+
40
+ begin
41
+ # stdin, stdout, stderr are special. The following dance should
42
+ # guarantee there is no window where `fp' is unwritable in MRI
43
+ # (or any correct Ruby implementation).
44
+ #
45
+ # Fwiw, GVL has zero bearing here. This is tricky because of
46
+ # the unavoidable existence of stdio FILE * pointers for
47
+ # std{in,out,err} in all programs which may use the standard C library
48
+ if fp.fileno <= 2
49
+ # We do not want to hit fclose(3)->dup(2) window for std{in,out,err}
50
+ # MRI will use freopen(3) here internally on std{in,out,err}
51
+ fp.reopen(fp.path, "a")
52
+ else
53
+ # We should not need this workaround, Ruby can be fixed:
54
+ # http://bugs.ruby-lang.org/issues/9036
55
+ # MRI will not call call fclose(3) or freopen(3) here
56
+ # since there's no associated std{in,out,err} FILE * pointer
57
+ # This should atomically use dup3(2) (or dup2(2)) syscall
58
+ File.open(fp.path, "a") { |tmpfp| fp.reopen(tmpfp) }
59
+ end
60
+
61
+ fp.sync = true
62
+ fp.flush # IO#sync=true may not implicitly flush
63
+ new_st = fp.stat
64
+
65
+ # this should only happen in the master:
66
+ if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
67
+ fp.chown(orig_st.uid, orig_st.gid)
68
+ end
69
+
70
+ nr += 1
71
+ rescue IOError, Errno::EBADF
72
+ # not much we can do...
73
+ end
74
+ end
75
+ nr
76
+ end
77
+
78
+ private
79
+
80
+ # @see Unicorn::Utils
81
+ def is_log?(fp)
82
+ append_flags = File::WRONLY | File::APPEND
83
+
84
+ ! fp.closed? &&
85
+ fp.stat.file? &&
86
+ fp.sync &&
87
+ (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
88
+ rescue IOError, Errno::EBADF
89
+ false
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,10 @@
1
+ require "resque/log_formatters/verbose_formatter"
2
+
3
+ module Resque
4
+ class VerboseFormatter
5
+ def call(serverity, datetime, progname, msg)
6
+ time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
7
+ "** [#{time}] #$$: #{msg}\n"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,142 @@
1
+ # Ordered Job
2
+ #
3
+ # Ensures that only one job for a given queue
4
+ # will be running on any worker at a given time
5
+ #
6
+ # Examples:
7
+ #
8
+ # class TestJob
9
+ # include Resque::Integration
10
+ #
11
+ # unique { |company_id, param1| [company_id] }
12
+ # ordered max_iterations: 10
13
+ #
14
+ # def self.execute(meta, company_id, param1)
15
+ # heavy_lifting_work
16
+ # end
17
+ # end
18
+ #
19
+ # class UniqueTestJob
20
+ # include Resque::Integration
21
+ #
22
+ # unique { |company_id, param1| [company_id] }
23
+ # ordered max_iterations: 10, unique: ->(_company_id, param1) { [param1] }
24
+ # ...
25
+ # end
26
+ #
27
+ module Resque
28
+ module Integration
29
+ module Ordered
30
+ ARGS_EXPIRATION = 1.week
31
+
32
+ def self.extended(base)
33
+ unless base.singleton_class.include?(::Resque::Integration::Unique)
34
+ base.extend ::Resque::Integration::Unique
35
+ end
36
+
37
+ unless base.singleton_class.include?(::Resque::Integration::Continuous)
38
+ base.extend ::Resque::Integration::Continuous
39
+ end
40
+
41
+ base.singleton_class.class_eval do
42
+ attr_accessor :max_iterations, :uniqueness
43
+
44
+ prepend Overrides
45
+ end
46
+ end
47
+
48
+ class Uniqueness
49
+ def initialize(&block)
50
+ @unique_block = block
51
+ end
52
+
53
+ def key(meta_id)
54
+ "ordered:unique:#{meta_id}"
55
+ end
56
+
57
+ def remove(meta_id, args)
58
+ Resque.redis.hdel(key(meta_id), encoded_unique_args(args))
59
+ end
60
+
61
+ def size(meta_id)
62
+ Resque.redis.hlen(key(meta_id)).to_i
63
+ end
64
+
65
+ def encoded_unique_args(args)
66
+ Resque.encode(@unique_block.call(*args))
67
+ end
68
+
69
+ def ordered_meta_id(meta_id, args)
70
+ Resque.redis.hget(key(meta_id), encoded_unique_args(args))
71
+ end
72
+
73
+ def set(meta_id, args, ordered_meta_id)
74
+ unique_key = key(meta_id)
75
+
76
+ if Resque.redis.hset(unique_key, encoded_unique_args(args), ordered_meta_id)
77
+ Resque.redis.expire(unique_key, ARGS_EXPIRATION)
78
+ end
79
+ end
80
+ end
81
+
82
+ module Overrides
83
+ def enqueue(*args)
84
+ meta = super
85
+
86
+ if uniqueness && ordered_meta_id = uniqueness.ordered_meta_id(meta.meta_id, args)
87
+ return get_meta(ordered_meta_id)
88
+ end
89
+
90
+ ordered_meta = ::Resque::Plugins::Meta::Metadata.new('meta_id' => ordered_meta_id(args), 'job_class' => self)
91
+ ordered_meta.save
92
+
93
+ uniqueness.set(meta.meta_id, args, ordered_meta.meta_id) if uniqueness
94
+ args.unshift(ordered_meta.meta_id)
95
+ encoded_args = ::Resque.encode(args)
96
+ args_key = ordered_queue_key(meta.meta_id)
97
+
98
+ ::Resque.redis.rpush(args_key, encoded_args)
99
+ ::Resque.redis.expire(args_key, ARGS_EXPIRATION)
100
+
101
+ ordered_meta
102
+ end
103
+
104
+ def perform(meta_id, *)
105
+ args_key = ordered_queue_key(meta_id)
106
+ i = 1
107
+ while job_args = ::Resque.redis.lpop(args_key)
108
+ job_args = ::Resque.decode(job_args)
109
+ ordered_meta = get_meta(job_args.shift)
110
+ ordered_meta.start!
111
+
112
+ begin
113
+ execute(ordered_meta, *job_args)
114
+ rescue Exception
115
+ ordered_meta.fail!
116
+ raise
117
+ ensure
118
+ uniqueness.remove(meta_id, job_args) if uniqueness
119
+ end
120
+
121
+ ordered_meta.finish!
122
+
123
+ i += 1
124
+ return continue if max_iterations && i > max_iterations && ordered_queue_size(meta_id) > 0
125
+ end
126
+ end
127
+ end
128
+
129
+ def ordered_queue_size(meta_id)
130
+ Resque.redis.llen(ordered_queue_key(meta_id)).to_i
131
+ end
132
+
133
+ def ordered_queue_key(meta_id)
134
+ "ordered:#{meta_id}"
135
+ end
136
+
137
+ def ordered_meta_id(args)
138
+ Digest::SHA1.hexdigest([Time.now.to_f, rand, self, args].join)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,89 @@
1
+ module Resque
2
+ module Integration
3
+ # Public: job with priority queues
4
+ #
5
+ # Examples:
6
+ # class MyJob
7
+ # include Resque::Integration
8
+ #
9
+ # queue :foo
10
+ # prioritized
11
+ #
12
+ # def self.perform(arg)
13
+ # heavy_lifting_work
14
+ # end
15
+ # end
16
+ #
17
+ # MyJob.enqueue_with_priority(:high, 1, another_param: 2) # enqueue job to :foo_high queue
18
+ # MyJob.enqueue_with_priority(:low, 1, another_param: 2) # enqueue job to :foo_low queue
19
+ #
20
+ # class MyUniqueJob
21
+ # include Resque::Integration
22
+ #
23
+ # queue :foo
24
+ # unique
25
+ # prioritized
26
+ #
27
+ # def self.execute(*args)
28
+ # meta = get_meta
29
+ #
30
+ # heavy_lifting_work do
31
+ # meta[:count] += 1
32
+ # end
33
+ # end
34
+ # end
35
+ module Priority
36
+ def self.extended(base)
37
+ base.singleton_class.prepend(Overrides)
38
+ end
39
+
40
+ module Overrides
41
+ # Public: enqueue job with normal priority
42
+ #
43
+ # Example:
44
+ # MyJob.enqueue(1)
45
+ def enqueue(*args)
46
+ enqueue_with_priority(:normal, *args)
47
+ end
48
+
49
+ # Public: dequeue job with priority
50
+ #
51
+ # Example:
52
+ # MyJob.dequeue(:high, 1)
53
+ def dequeue(priority, *args)
54
+ if unique?
55
+ super(*args, priority)
56
+ else
57
+ Resque.dequeue(self, *args, priority)
58
+ end
59
+ end
60
+
61
+ def perform(*args, _priority)
62
+ super(*args)
63
+ end
64
+ end
65
+
66
+ def priority?
67
+ true
68
+ end
69
+
70
+ # Public: enqueue job to priority queue
71
+ #
72
+ # Example:
73
+ # MyJob.enqueue_with_priority(:high, 1)
74
+ def enqueue_with_priority(priority, *args)
75
+ queue = priority_queue(priority)
76
+
77
+ if unique?
78
+ enqueue_to(queue, *args, priority)
79
+ else
80
+ Resque.enqueue_to(queue, self, *args, priority)
81
+ end
82
+ end
83
+
84
+ def priority_queue(priority)
85
+ priority.to_sym == :normal ? queue : "#{queue}_#{priority}".to_sym
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,53 @@
1
+ module Resque
2
+ module Integration
3
+ class QueuesInfo
4
+ class Age
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ def time(queue)
10
+ from_time = Time.now.utc
11
+ max_secs = 0
12
+
13
+ jobs.each do |job|
14
+ next unless job['queue'] == queue
15
+ job_secs = seconds_for(job, from_time)
16
+ max_secs = job_secs if job_secs > max_secs
17
+ end
18
+
19
+ max_secs
20
+ end
21
+
22
+ def overall
23
+ from_time = Time.now.utc
24
+
25
+ max_secs = 0
26
+
27
+ jobs.each do |job|
28
+ next unless job['queue']
29
+ job_secs = seconds_for(job, from_time)
30
+ next if job_secs < threshold(job['queue'])
31
+ max_secs = job_secs if job_secs > max_secs
32
+ end
33
+
34
+ max_secs
35
+ end
36
+
37
+ def threshold(queue)
38
+ @config.max_age(queue)
39
+ end
40
+
41
+ private
42
+
43
+ def jobs
44
+ Resque.workers.each_with_object([]) { |worker, memo| memo << worker.job unless worker.idle? }
45
+ end
46
+
47
+ def seconds_for(job, from_time)
48
+ (from_time - DateTime.strptime(job['run_at'], '%Y-%m-%dT%H:%M:%S').utc).to_i
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,96 @@
1
+ require 'yaml'
2
+
3
+ module Resque
4
+ module Integration
5
+ class QueuesInfo
6
+ class Config
7
+ def initialize(config_path)
8
+ config = load_config(config_path)
9
+ @defaults = config['defaults']
10
+ @queues = expand_config(config['queues'])
11
+ end
12
+
13
+ def max_age(queue)
14
+ threshold(queue, 'max_age')
15
+ end
16
+
17
+ def warn_age(queue)
18
+ threshold(queue, 'warn_age')
19
+ end
20
+
21
+ def max_size(queue)
22
+ threshold(queue, 'max_size')
23
+ end
24
+
25
+ def warn_size(queue)
26
+ threshold(queue, 'warn_size')
27
+ end
28
+
29
+ def max_failures_count(queue, period)
30
+ threshold(queue, "max_failures_count_per_#{period}")
31
+ end
32
+
33
+ def warn_failures_count(queue, period)
34
+ threshold(queue, "warn_failures_count_per_#{period}")
35
+ end
36
+
37
+ def channel(queue)
38
+ channel = threshold(queue, 'channel')
39
+ Array.wrap(channel).join(' ')
40
+ end
41
+
42
+ def warn_channel(queue)
43
+ channel = threshold(queue, 'warn_channel')
44
+ Array.wrap(channel).join(' ')
45
+ end
46
+
47
+ def data
48
+ @data ||= @queues.map do |queue_name, _queue_params|
49
+ {
50
+ '{#QUEUE}' => queue_name,
51
+
52
+ '{#CHANNEL}' => channel(queue_name),
53
+ '{#THRESHOLD_AGE}' => max_age(queue_name),
54
+ '{#THRESHOLD_SIZE}' => max_size(queue_name),
55
+ '{#THRESHOLD_FAILURES_PER_5M}' => max_failures_count(queue_name, '5m'),
56
+ '{#THRESHOLD_FAILURES_PER_1H}' => max_failures_count(queue_name, '1h'),
57
+
58
+ '{#WARNING_CHANNEL}' => warn_channel(queue_name),
59
+ '{#WARNING_AGE}' => warn_age(queue_name),
60
+ '{#WARNING_SIZE}' => warn_size(queue_name),
61
+ '{#WARNING_FAILURES_PER_5M}' => warn_failures_count(queue_name, '5m'),
62
+ '{#WARNING_FAILURES_PER_1H}' => warn_failures_count(queue_name, '1h')
63
+ }
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def threshold(queue, param)
70
+ @queues[queue].try(:[], param) || @defaults[param]
71
+ end
72
+
73
+ def load_config(path)
74
+ input = File.read(path)
75
+ input = ERB.new(input).result if defined?(ERB)
76
+ YAML.load(input)
77
+ end
78
+
79
+ def expand_config(config)
80
+ expanded_config = {}
81
+
82
+ config.keys.each do |key|
83
+ key.split(',').each do |queue|
84
+ queue.chomp!
85
+ queue.strip!
86
+
87
+ (expanded_config[queue] ||= {}).merge!(config[key])
88
+ end
89
+ end
90
+
91
+ expanded_config
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,33 @@
1
+ module Resque
2
+ module Integration
3
+ class QueuesInfo
4
+ class Size
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ def size(queue)
10
+ Resque.size(queue) || 0
11
+ end
12
+
13
+ def overall
14
+ max = 0
15
+
16
+ Resque.queues.each do |queue|
17
+ size = Resque.size(queue).to_i
18
+ next if size < threshold(queue)
19
+ max = size if size > max
20
+ end
21
+
22
+ max
23
+ end
24
+
25
+ private
26
+
27
+ def threshold(queue)
28
+ @config.max_size(queue)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,55 @@
1
+ module Resque
2
+ module Integration
3
+ class QueuesInfo
4
+ autoload :Age, "resque/integration/queues_info/age"
5
+ autoload :Size, "resque/integration/queues_info/size"
6
+ autoload :Config, "resque/integration/queues_info/config"
7
+
8
+ def initialize(options)
9
+ @config = Config.new(options.fetch(:config))
10
+ @size = Size.new(@config)
11
+ @age = Age.new(@config)
12
+ end
13
+
14
+ def age_for_queue(queue)
15
+ @age.time(queue)
16
+ end
17
+
18
+ def age_overall
19
+ @age.overall
20
+ end
21
+
22
+ def size_for_queue(queue)
23
+ @size.size(queue)
24
+ end
25
+
26
+ def size_overall
27
+ @size.overall
28
+ end
29
+
30
+ def failures_count_for_queue(queue)
31
+ Resque::Integration::FailureBackends::QueuesTotals.count(queue)
32
+ end
33
+
34
+ def threshold_size(queue)
35
+ @config.max_size(queue)
36
+ end
37
+
38
+ def threshold_age(queue)
39
+ @config.max_age(queue)
40
+ end
41
+
42
+ def threshold_failures_count(queue, period)
43
+ @config.max_failures_count(queue, period)
44
+ end
45
+
46
+ def channel(queue)
47
+ @config.channel(queue)
48
+ end
49
+
50
+ def data
51
+ @config.data
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,49 @@
1
+ # coding: utf-8
2
+ require 'erb'
3
+
4
+ namespace :resque do
5
+ # Здесь мы добавляем некоторые необходимые для корректной работы "хуки".
6
+ #
7
+ # @see https://github.com/resque/resque/tree/1-x-stable#workers
8
+ task :setup => :environment do
9
+ # принудительно инициализируем приложение
10
+ # (rails 3 не делают этого при запуске из rake-задачи)
11
+ Rails.application.eager_load! if Rails::VERSION::MAJOR < 4
12
+
13
+ # Включаем логирование в resque
14
+ ENV['VERBOSE'] = '1'
15
+
16
+ # перенаправление вывода в файл
17
+ Resque::Integration::LogsRotator.redirect_std
18
+ # слушать HUP сигнал для ротации логов
19
+ Resque::Integration::LogsRotator.register_hup_signal
20
+
21
+ # Нужно закрыть все соединения в **родительском** процессе,
22
+ # Нужно также закрыть соединение к memcache
23
+ Resque.before_first_fork do
24
+ ActiveRecord::Base.connection_handler.clear_all_connections!
25
+ Rails.cache.reset if Rails.cache.respond_to?(:reset)
26
+ end
27
+
28
+ Resque.before_fork do
29
+ Resque.redis.client.disconnect
30
+
31
+ ActiveRecord::Base.connection_handler.clear_all_connections!
32
+ end
33
+
34
+ Resque.after_fork do |job|
35
+ $0 = "resque-#{Resque::Version}: Processing #{job.queue}/#{job.payload['class']} since #{Time.now.to_s(:db)}"
36
+
37
+ ActiveRecord::Base.establish_connection
38
+
39
+ Resque.redis.client.connect
40
+ end
41
+
42
+ # Support for resque-multi-job-forks
43
+ require 'resque-multi-job-forks' if ENV['JOBS_PER_FORK'] || ENV['MINUTES_PER_FORK']
44
+
45
+ if Resque.config.run_at_exit_hooks? && ENV['RUN_AT_EXIT_HOOKS'].nil?
46
+ ENV['RUN_AT_EXIT_HOOKS'] = '1'
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ require 'resque/integration/unique'
2
+
3
+ namespace :resque do
4
+ namespace :lock do
5
+ task expire_all: :environment do
6
+ puts "Start expiring all resque locks"
7
+
8
+ redis = ::Resque.redis
9
+ cursor = 0
10
+ batch_size = 10_000
11
+ timeout = ::Resque::Integration::Unique::LOCK_TIMEOUT
12
+ count = 0
13
+ pattern = "lock:*"
14
+
15
+ loop do
16
+ cursor, keys = redis.scan(cursor, count: batch_size, match: pattern)
17
+ cursor = cursor.to_i
18
+
19
+ unless keys.empty?
20
+ redis.pipelined do
21
+ keys.each do |key|
22
+ redis.expire(key, timeout)
23
+ end
24
+ end
25
+
26
+ count += keys.size
27
+ puts "Expired #{count}..."
28
+ end
29
+
30
+ break if cursor.zero?
31
+ end
32
+
33
+ puts "Expired total #{count} keys."
34
+ puts "Done."
35
+ end
36
+ end
37
+ end