resque-integration 3.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.drone.yml +28 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/Appraisals +27 -0
- data/CHANGELOG.md +311 -0
- data/Gemfile +4 -0
- data/README.md +281 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/standalone/progress_bar.js +47 -0
- data/app/controllers/resque/jobs_controller.rb +42 -0
- data/app/controllers/resque/queues/info_controller.rb +9 -0
- data/app/controllers/resque/queues/status_controller.rb +38 -0
- data/app/views/shared/job_progress_bar.html.haml +17 -0
- data/bin/resque-status +59 -0
- data/config/routes.rb +13 -0
- data/config.ru +9 -0
- data/dip.yml +44 -0
- data/docker-compose.development.yml +12 -0
- data/docker-compose.drone.yml +6 -0
- data/docker-compose.yml +10 -0
- data/lib/generators/resque/integration/install/install_generator.rb +38 -0
- data/lib/resque/integration/backtrace.rb +29 -0
- data/lib/resque/integration/configuration.rb +238 -0
- data/lib/resque/integration/continuous.rb +75 -0
- data/lib/resque/integration/engine.rb +103 -0
- data/lib/resque/integration/extensions/job.rb +17 -0
- data/lib/resque/integration/extensions/worker.rb +17 -0
- data/lib/resque/integration/extensions.rb +8 -0
- data/lib/resque/integration/failure_backends/queues_totals.rb +37 -0
- data/lib/resque/integration/failure_backends.rb +7 -0
- data/lib/resque/integration/god.erb +99 -0
- data/lib/resque/integration/hooks.rb +72 -0
- data/lib/resque/integration/logs_rotator.rb +95 -0
- data/lib/resque/integration/monkey_patch/verbose_formatter.rb +10 -0
- data/lib/resque/integration/ordered.rb +142 -0
- data/lib/resque/integration/priority.rb +89 -0
- data/lib/resque/integration/queues_info/age.rb +53 -0
- data/lib/resque/integration/queues_info/config.rb +96 -0
- data/lib/resque/integration/queues_info/size.rb +33 -0
- data/lib/resque/integration/queues_info.rb +55 -0
- data/lib/resque/integration/tasks/hooks.rake +49 -0
- data/lib/resque/integration/tasks/lock.rake +37 -0
- data/lib/resque/integration/tasks/resque.rake +101 -0
- data/lib/resque/integration/unique.rb +218 -0
- data/lib/resque/integration/version.rb +5 -0
- data/lib/resque/integration.rb +146 -0
- data/lib/resque-integration.rb +1 -0
- data/resque-integration.gemspec +40 -0
- data/spec/fixtures/resque_queues.yml +45 -0
- data/spec/resque/controllers/jobs_controller_spec.rb +65 -0
- data/spec/resque/integration/configuration_spec.rb +147 -0
- data/spec/resque/integration/continuous_spec.rb +122 -0
- data/spec/resque/integration/failure_backends/queues_totals_spec.rb +105 -0
- data/spec/resque/integration/ordered_spec.rb +87 -0
- data/spec/resque/integration/priority_spec.rb +94 -0
- data/spec/resque/integration/queues_info_spec.rb +402 -0
- data/spec/resque/integration/unique_spec.rb +184 -0
- data/spec/resque/integration_spec.rb +105 -0
- data/spec/shared/resque_inline.rb +10 -0
- data/spec/spec_helper.rb +28 -0
- data/vendor/assets/images/progressbar/white.gif +0 -0
- data/vendor/assets/javascripts/jquery.progressbar.js +177 -0
- data/vendor/assets/stylesheets/jquery.progressbar.css.erb +33 -0
- data/vendor/assets/stylesheets/jquery.progressbar.no_pipeline.css +33 -0
- 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,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
|