resque-integration 3.4.1

Sign up to get free protection for your applications and to get access to all the features.
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