delayed 2.0.3 → 2.1.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.
- checksums.yaml +4 -4
- data/lib/delayed/monitor.rb +54 -7
- data/lib/delayed/tasks.rb +31 -11
- data/lib/delayed/version.rb +1 -1
- data/spec/delayed/__snapshots__/monitor_spec.rb.snap +24 -24
- data/spec/delayed/job_spec.rb +0 -6
- data/spec/delayed/monitor_spec.rb +292 -221
- data/spec/helper.rb +19 -11
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 984a910472fc4f1dedafcf4060cf076bcda408196bfa80f08039ed2527533a41
|
|
4
|
+
data.tar.gz: 98f7de42e2964ee1576ba6ae3fc2b39cbe76a841263054a5b9634f7e55ce1114
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dcb018c429ff591409796e410c570f49408eb0b8363759f6c8970ac4ee1773abccc117b5398c3596ecf8491c0e70db548bd4a2ea54ef39a77fa78da43b6cbfe4
|
|
7
|
+
data.tar.gz: 020a785c7bfc81456b8cc7b8ddace55fad1f9468edb3b9cef0ef066eb6aed64b580d30974b48ce97bc7b1602294c9901c7c26fa294d3e4b4ecebd177aef677c8
|
data/lib/delayed/monitor.rb
CHANGED
|
@@ -35,6 +35,33 @@ module Delayed
|
|
|
35
35
|
send(:"#{metric}_grouped")
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def self.sql_now_in_utc
|
|
39
|
+
case ActiveRecord::Base.connection.adapter_name
|
|
40
|
+
when 'PostgreSQL'
|
|
41
|
+
"TIMEZONE('UTC', NOW())"
|
|
42
|
+
when 'MySQL', 'Mysql2'
|
|
43
|
+
"UTC_TIMESTAMP()"
|
|
44
|
+
else
|
|
45
|
+
"CURRENT_TIMESTAMP"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.parse_utc_time(string)
|
|
50
|
+
# Depending on Rails version & DB adapter, this will be either a String or a DateTime.
|
|
51
|
+
# If it's a DateTime, and if connection is running with the `:local` time zone config,
|
|
52
|
+
# then by default Rails incorrectly assumes it's in local time instead of UTC.
|
|
53
|
+
# We use `strftime` to strip the encoded TZ info and re-parse it as UTC.
|
|
54
|
+
#
|
|
55
|
+
# Example:
|
|
56
|
+
# - "2026-02-05 10:01:23" -> DB-returned string
|
|
57
|
+
# - "2026-02-05 10:01:23 -0600" -> Rails-parsed DateTime with incorrect TZ
|
|
58
|
+
# - "2026-02-05 10:01:23" -> `strftime` output
|
|
59
|
+
# - "2026-02-05 04:01:23 -0600" -> Re-parsed as UTC and converted to local time
|
|
60
|
+
string = string.strftime('%Y-%m-%d %H:%M:%S') if string.respond_to?(:strftime)
|
|
61
|
+
|
|
62
|
+
ActiveSupport::TimeZone.new("UTC").parse(string)
|
|
63
|
+
end
|
|
64
|
+
|
|
38
65
|
private
|
|
39
66
|
|
|
40
67
|
attr_reader :jobs
|
|
@@ -75,7 +102,15 @@ module Delayed
|
|
|
75
102
|
|
|
76
103
|
def grouped_min(scope, column)
|
|
77
104
|
Delayed::Job.from(scope.select("priority, queue, MIN(#{column}) AS #{column}"))
|
|
78
|
-
.group(priority_case_statement, :queue)
|
|
105
|
+
.group(priority_case_statement, :queue)
|
|
106
|
+
.select(<<~SQL.squish)
|
|
107
|
+
(#{priority_case_statement}) AS priority,
|
|
108
|
+
queue,
|
|
109
|
+
MIN(#{column}) AS #{column},
|
|
110
|
+
#{self.class.sql_now_in_utc} AS db_now_utc
|
|
111
|
+
SQL
|
|
112
|
+
.group_by { |j| [j.priority.to_i, j.queue] }
|
|
113
|
+
.transform_values(&:first)
|
|
79
114
|
end
|
|
80
115
|
|
|
81
116
|
def count_grouped
|
|
@@ -107,16 +142,16 @@ module Delayed
|
|
|
107
142
|
end
|
|
108
143
|
|
|
109
144
|
def max_lock_age_grouped
|
|
110
|
-
|
|
145
|
+
oldest_locked_at_query.transform_values { |j| db_now(j) - j.locked_at }
|
|
111
146
|
end
|
|
112
147
|
|
|
113
148
|
def max_age_grouped
|
|
114
|
-
|
|
149
|
+
oldest_run_at_query.transform_values { |j| db_now(j) - j.run_at }
|
|
115
150
|
end
|
|
116
151
|
|
|
117
152
|
def alert_age_percent_grouped
|
|
118
|
-
|
|
119
|
-
max_age =
|
|
153
|
+
oldest_run_at_query.each_with_object({}) do |((priority, queue), j), metrics|
|
|
154
|
+
max_age = db_now(j) - j.run_at
|
|
120
155
|
alert_age = Priority.new(priority).alert_age
|
|
121
156
|
metrics[[priority, queue]] = [max_age / alert_age * 100, 100].min if alert_age
|
|
122
157
|
end
|
|
@@ -129,11 +164,23 @@ module Delayed
|
|
|
129
164
|
alias working_count_grouped locked_count_grouped
|
|
130
165
|
|
|
131
166
|
def oldest_locked_job_grouped
|
|
132
|
-
|
|
167
|
+
oldest_locked_at_query.transform_values(&:locked_at)
|
|
133
168
|
end
|
|
134
169
|
|
|
135
170
|
def oldest_workable_job_grouped
|
|
136
|
-
|
|
171
|
+
oldest_run_at_query.transform_values(&:run_at)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def oldest_locked_at_query
|
|
175
|
+
@memo[:oldest_locked_at_query] ||= grouped_min(jobs.claimed, :locked_at)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def oldest_run_at_query
|
|
179
|
+
@memo[:oldest_run_at_query] ||= grouped_min(jobs.claimable, :run_at)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def db_now(record)
|
|
183
|
+
self.class.parse_utc_time(record.db_now_utc)
|
|
137
184
|
end
|
|
138
185
|
|
|
139
186
|
def priority_case_statement
|
data/lib/delayed/tasks.rb
CHANGED
|
@@ -10,17 +10,37 @@ namespace :delayed do
|
|
|
10
10
|
|
|
11
11
|
next unless defined?(Rails.application.config)
|
|
12
12
|
|
|
13
|
-
# By default, Rails
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
# By default, Rails wants to disable eager loading inside of `rake`
|
|
14
|
+
# commands, even if `eager_load` is set to true. This is done to speed up
|
|
15
|
+
# the boot time of rake tasks that don't need the entire application loaded.
|
|
16
|
+
#
|
|
17
|
+
# The problem is that long-lived processes like `delayed` **do** want to
|
|
18
|
+
# eager load the application before spawning any threads or forks.
|
|
19
|
+
# (Especially if in a production environment where we want full load-order
|
|
20
|
+
# parity with the `rails server` processes!)
|
|
21
|
+
#
|
|
22
|
+
# When a Rails app boots, it chooses whether to eager load based on its
|
|
23
|
+
# `eager_load` config and whether or not it was initiated by a `rake`
|
|
24
|
+
# command. If it did eager load, we don't want to eager load again, but if
|
|
25
|
+
# it was initiated by a `rake` command, it sets `eager_load` to false before
|
|
26
|
+
# the point at which `delayed` starts setting up _its_ rake environment.
|
|
27
|
+
#
|
|
28
|
+
# So we cannot rely on that config to know whether or not to eager load --
|
|
29
|
+
# instead we must make an inference:
|
|
30
|
+
# - Newer rails versions (~7.0+) have a `config.rake_eager_load` option,
|
|
31
|
+
# which tells us whether the app has already eager loaded in a `rake`
|
|
32
|
+
# context.
|
|
33
|
+
# - If `rake_eager_loading` is not defined or `false`, we will then check
|
|
34
|
+
# `cache_classes` & explicitly eager load if true.
|
|
35
|
+
|
|
36
|
+
eager_loaded = Rails.application.config.respond_to?(:rake_eager_load) && Rails.application.config.rake_eager_load
|
|
37
|
+
next if eager_loaded || !Rails.application.config.cache_classes
|
|
38
|
+
|
|
39
|
+
Rails.application.config.eager_load = true
|
|
40
|
+
Rails::Application::Finisher.initializers
|
|
41
|
+
.find { |i| i.name == :eager_load! }
|
|
42
|
+
.bind(Rails.application)
|
|
43
|
+
.run
|
|
24
44
|
end
|
|
25
45
|
|
|
26
46
|
desc 'start a delayed worker'
|
data/lib/delayed/version.rb
CHANGED
|
@@ -318,10 +318,10 @@ GroupAggregate (cost=...)
|
|
|
318
318
|
SNAP
|
|
319
319
|
|
|
320
320
|
snapshots["runs the expected postgresql query for max_lock_age 1"] = <<-SNAP
|
|
321
|
-
SELECT
|
|
321
|
+
SELECT (CASE WHEN priority >= 0
|
|
322
322
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
323
323
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
324
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
324
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(locked_at) AS locked_at, TIMEZONE('UTC', NOW()) AS db_now_utc
|
|
325
325
|
FROM (SELECT priority, queue, MIN(locked_at) AS locked_at
|
|
326
326
|
FROM \"delayed_jobs\"
|
|
327
327
|
WHERE \"delayed_jobs\".\"locked_at\" >= '2025-11-10 16:59:43'
|
|
@@ -336,7 +336,7 @@ SNAP
|
|
|
336
336
|
|
|
337
337
|
snapshots["produces the expected postgresql query plan for max_lock_age 1"] = <<-SNAP
|
|
338
338
|
GroupAggregate (cost=...)
|
|
339
|
-
Output:
|
|
339
|
+
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, min(subquery.locked_at), timezone('UTC'::text, now())
|
|
340
340
|
Group Key: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue
|
|
341
341
|
-> Sort (cost=...)
|
|
342
342
|
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, subquery.locked_at
|
|
@@ -357,7 +357,7 @@ SNAP
|
|
|
357
357
|
|
|
358
358
|
snapshots["[legacy index] produces the expected postgresql query plan for max_lock_age 1"] = <<-SNAP
|
|
359
359
|
GroupAggregate (cost=...)
|
|
360
|
-
Output:
|
|
360
|
+
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, min(subquery.locked_at), timezone('UTC'::text, now())
|
|
361
361
|
Group Key: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue
|
|
362
362
|
-> Sort (cost=...)
|
|
363
363
|
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, subquery.locked_at
|
|
@@ -377,10 +377,10 @@ GroupAggregate (cost=...)
|
|
|
377
377
|
SNAP
|
|
378
378
|
|
|
379
379
|
snapshots["runs the expected postgresql query for max_age 1"] = <<-SNAP
|
|
380
|
-
SELECT
|
|
380
|
+
SELECT (CASE WHEN priority >= 0
|
|
381
381
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
382
382
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
383
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
383
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(run_at) AS run_at, TIMEZONE('UTC', NOW()) AS db_now_utc
|
|
384
384
|
FROM (SELECT priority, queue, MIN(run_at) AS run_at
|
|
385
385
|
FROM \"delayed_jobs\"
|
|
386
386
|
WHERE (\"delayed_jobs\".\"locked_at\" IS NULL
|
|
@@ -396,7 +396,7 @@ SNAP
|
|
|
396
396
|
|
|
397
397
|
snapshots["produces the expected postgresql query plan for max_age 1"] = <<-SNAP
|
|
398
398
|
GroupAggregate (cost=...)
|
|
399
|
-
Output:
|
|
399
|
+
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, min(subquery.run_at), timezone('UTC'::text, now())
|
|
400
400
|
Group Key: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue
|
|
401
401
|
-> Sort (cost=...)
|
|
402
402
|
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, subquery.run_at
|
|
@@ -417,7 +417,7 @@ SNAP
|
|
|
417
417
|
|
|
418
418
|
snapshots["[legacy index] produces the expected postgresql query plan for max_age 1"] = <<-SNAP
|
|
419
419
|
GroupAggregate (cost=...)
|
|
420
|
-
Output:
|
|
420
|
+
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, min(subquery.run_at), timezone('UTC'::text, now())
|
|
421
421
|
Group Key: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue
|
|
422
422
|
-> Sort (cost=...)
|
|
423
423
|
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, subquery.run_at
|
|
@@ -556,10 +556,10 @@ GroupAggregate (cost=...)
|
|
|
556
556
|
SNAP
|
|
557
557
|
|
|
558
558
|
snapshots["runs the expected postgresql query for alert_age_percent 1"] = <<-SNAP
|
|
559
|
-
SELECT
|
|
559
|
+
SELECT (CASE WHEN priority >= 0
|
|
560
560
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
561
561
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
562
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
562
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(run_at) AS run_at, TIMEZONE('UTC', NOW()) AS db_now_utc
|
|
563
563
|
FROM (SELECT priority, queue, MIN(run_at) AS run_at
|
|
564
564
|
FROM \"delayed_jobs\"
|
|
565
565
|
WHERE (\"delayed_jobs\".\"locked_at\" IS NULL
|
|
@@ -575,7 +575,7 @@ SNAP
|
|
|
575
575
|
|
|
576
576
|
snapshots["produces the expected postgresql query plan for alert_age_percent 1"] = <<-SNAP
|
|
577
577
|
GroupAggregate (cost=...)
|
|
578
|
-
Output:
|
|
578
|
+
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, min(subquery.run_at), timezone('UTC'::text, now())
|
|
579
579
|
Group Key: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue
|
|
580
580
|
-> Sort (cost=...)
|
|
581
581
|
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, subquery.run_at
|
|
@@ -596,7 +596,7 @@ SNAP
|
|
|
596
596
|
|
|
597
597
|
snapshots["[legacy index] produces the expected postgresql query plan for alert_age_percent 1"] = <<-SNAP
|
|
598
598
|
GroupAggregate (cost=...)
|
|
599
|
-
Output:
|
|
599
|
+
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, min(subquery.run_at), timezone('UTC'::text, now())
|
|
600
600
|
Group Key: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue
|
|
601
601
|
-> Sort (cost=...)
|
|
602
602
|
Output: (CASE WHEN ((subquery.priority >= 0) AND (subquery.priority < 10)) THEN 0 WHEN ((subquery.priority >= 10) AND (subquery.priority < 20)) THEN 10 WHEN ((subquery.priority >= 20) AND (subquery.priority < 30)) THEN 20 WHEN (subquery.priority >= 30) THEN 30 ELSE NULL::integer END), subquery.queue, subquery.run_at
|
|
@@ -795,10 +795,10 @@ USE TEMP B-TREE FOR GROUP BY
|
|
|
795
795
|
SNAP
|
|
796
796
|
|
|
797
797
|
snapshots["runs the expected sqlite3 query for max_lock_age 1"] = <<-SNAP
|
|
798
|
-
SELECT
|
|
798
|
+
SELECT (CASE WHEN priority >= 0
|
|
799
799
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
800
800
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
801
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
801
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(locked_at) AS locked_at, CURRENT_TIMESTAMP AS db_now_utc
|
|
802
802
|
FROM (SELECT priority, queue, MIN(locked_at) AS locked_at
|
|
803
803
|
FROM \"delayed_jobs\"
|
|
804
804
|
WHERE \"delayed_jobs\".\"locked_at\" >= '2025-11-10 16:59:43'
|
|
@@ -828,10 +828,10 @@ USE TEMP B-TREE FOR GROUP BY
|
|
|
828
828
|
SNAP
|
|
829
829
|
|
|
830
830
|
snapshots["runs the expected sqlite3 query for max_age 1"] = <<-SNAP
|
|
831
|
-
SELECT
|
|
831
|
+
SELECT (CASE WHEN priority >= 0
|
|
832
832
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
833
833
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
834
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
834
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(run_at) AS run_at, CURRENT_TIMESTAMP AS db_now_utc
|
|
835
835
|
FROM (SELECT priority, queue, MIN(run_at) AS run_at
|
|
836
836
|
FROM \"delayed_jobs\"
|
|
837
837
|
WHERE (\"delayed_jobs\".\"locked_at\" IS NULL
|
|
@@ -929,10 +929,10 @@ USE TEMP B-TREE FOR GROUP BY
|
|
|
929
929
|
SNAP
|
|
930
930
|
|
|
931
931
|
snapshots["runs the expected sqlite3 query for alert_age_percent 1"] = <<-SNAP
|
|
932
|
-
SELECT
|
|
932
|
+
SELECT (CASE WHEN priority >= 0
|
|
933
933
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
934
934
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
935
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
935
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(run_at) AS run_at, CURRENT_TIMESTAMP AS db_now_utc
|
|
936
936
|
FROM (SELECT priority, queue, MIN(run_at) AS run_at
|
|
937
937
|
FROM \"delayed_jobs\"
|
|
938
938
|
WHERE (\"delayed_jobs\".\"locked_at\" IS NULL
|
|
@@ -1149,10 +1149,10 @@ snapshots["[legacy index] produces the expected mysql2 query plan for failed_cou
|
|
|
1149
1149
|
SNAP
|
|
1150
1150
|
|
|
1151
1151
|
snapshots["runs the expected mysql2 query for max_lock_age 1"] = <<-SNAP
|
|
1152
|
-
SELECT
|
|
1152
|
+
SELECT (CASE WHEN priority >= 0
|
|
1153
1153
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
1154
1154
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
1155
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
1155
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(locked_at) AS locked_at, UTC_TIMESTAMP() AS db_now_utc
|
|
1156
1156
|
FROM (SELECT priority, queue, MIN(locked_at) AS locked_at
|
|
1157
1157
|
FROM `delayed_jobs`
|
|
1158
1158
|
WHERE `delayed_jobs`.`locked_at` >= '2025-11-10 16:59:43'
|
|
@@ -1188,10 +1188,10 @@ snapshots["[legacy index] produces the expected mysql2 query plan for max_lock_a
|
|
|
1188
1188
|
SNAP
|
|
1189
1189
|
|
|
1190
1190
|
snapshots["runs the expected mysql2 query for max_age 1"] = <<-SNAP
|
|
1191
|
-
SELECT
|
|
1191
|
+
SELECT (CASE WHEN priority >= 0
|
|
1192
1192
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
1193
1193
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
1194
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
1194
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(run_at) AS run_at, UTC_TIMESTAMP() AS db_now_utc
|
|
1195
1195
|
FROM (SELECT priority, queue, MIN(run_at) AS run_at
|
|
1196
1196
|
FROM `delayed_jobs`
|
|
1197
1197
|
WHERE (`delayed_jobs`.`locked_at` IS NULL
|
|
@@ -1307,10 +1307,10 @@ snapshots["[legacy index] produces the expected mysql2 query plan for workable_c
|
|
|
1307
1307
|
SNAP
|
|
1308
1308
|
|
|
1309
1309
|
snapshots["runs the expected mysql2 query for alert_age_percent 1"] = <<-SNAP
|
|
1310
|
-
SELECT
|
|
1310
|
+
SELECT (CASE WHEN priority >= 0
|
|
1311
1311
|
AND priority < 10 THEN 0 WHEN priority >= 10
|
|
1312
1312
|
AND priority < 20 THEN 10 WHEN priority >= 20
|
|
1313
|
-
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END AS
|
|
1313
|
+
AND priority < 30 THEN 20 WHEN priority >= 30 THEN 30 END) AS priority, queue, MIN(run_at) AS run_at, UTC_TIMESTAMP() AS db_now_utc
|
|
1314
1314
|
FROM (SELECT priority, queue, MIN(run_at) AS run_at
|
|
1315
1315
|
FROM `delayed_jobs`
|
|
1316
1316
|
WHERE (`delayed_jobs`.`locked_at` IS NULL
|
data/spec/delayed/job_spec.rb
CHANGED
|
@@ -1006,12 +1006,6 @@ describe Delayed::Job do
|
|
|
1006
1006
|
end
|
|
1007
1007
|
end
|
|
1008
1008
|
|
|
1009
|
-
if ActiveRecord::VERSION::MAJOR >= 7
|
|
1010
|
-
delegate :default_timezone=, to: ActiveRecord
|
|
1011
|
-
else
|
|
1012
|
-
delegate :default_timezone=, to: ActiveRecord::Base
|
|
1013
|
-
end
|
|
1014
|
-
|
|
1015
1009
|
context "db_time_now" do
|
|
1016
1010
|
after do
|
|
1017
1011
|
Time.zone = nil
|
|
@@ -14,262 +14,333 @@ RSpec.describe Delayed::Monitor do
|
|
|
14
14
|
}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
|
|
20
|
-
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
21
|
-
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
22
|
-
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
23
|
-
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
24
|
-
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
25
|
-
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
26
|
-
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
27
|
-
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
28
|
-
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
29
|
-
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
30
|
-
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
31
|
-
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
32
|
-
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
33
|
-
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
34
|
-
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
35
|
-
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
36
|
-
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
37
|
-
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
38
|
-
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
39
|
-
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
40
|
-
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
41
|
-
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
42
|
-
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
43
|
-
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
44
|
-
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
45
|
-
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
46
|
-
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
47
|
-
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
48
|
-
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
49
|
-
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
50
|
-
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
51
|
-
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
52
|
-
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
53
|
-
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
54
|
-
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
55
|
-
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
56
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
57
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
58
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
59
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
60
|
-
end
|
|
17
|
+
describe '#run!' do
|
|
18
|
+
let(:app_local_db_time) { false }
|
|
61
19
|
|
|
62
|
-
context 'when named priorities are customized' do
|
|
63
20
|
around do |example|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
end
|
|
21
|
+
if app_local_db_time
|
|
22
|
+
Time.zone = 'US/Central'
|
|
23
|
+
self.default_timezone = :local
|
|
24
|
+
end
|
|
69
25
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
82
|
-
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
83
|
-
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
84
|
-
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
85
|
-
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
86
|
-
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
87
|
-
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
88
|
-
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
89
|
-
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
90
|
-
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
91
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
92
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
26
|
+
# On PostgreSQL, running examples in a transaction allows CURRENT_TIMESTAMP to remain stable.
|
|
27
|
+
# We can in turn use this to set Timecop to the same time as the DB for deterministic time math.
|
|
28
|
+
Delayed::Job.transaction do
|
|
29
|
+
now = described_class.parse_utc_time(
|
|
30
|
+
Delayed::Job.connection.select_value("SELECT #{described_class.sql_now_in_utc}"),
|
|
31
|
+
)
|
|
32
|
+
Timecop.freeze(now) { example.run }
|
|
33
|
+
end
|
|
34
|
+
ensure
|
|
35
|
+
Time.zone = nil
|
|
36
|
+
self.default_timezone = :utc
|
|
93
37
|
end
|
|
94
|
-
end
|
|
95
38
|
|
|
96
|
-
|
|
97
|
-
let(:now) { Delayed::Job.db_time_now.change(nsec: 0) }
|
|
98
|
-
let(:job_attributes) do
|
|
99
|
-
{
|
|
100
|
-
run_at: now,
|
|
101
|
-
queue: 'default',
|
|
102
|
-
handler: "--- !ruby/object:SimpleJob\n",
|
|
103
|
-
attempts: 0,
|
|
104
|
-
}
|
|
105
|
-
end
|
|
106
|
-
let(:failed_attributes) { { run_at: now - 1.week, attempts: 1, failed_at: now - 1.day, locked_at: now - 1.day } }
|
|
107
|
-
let(:p0_attributes) { job_attributes.merge(priority: 1, attempts: 1) }
|
|
108
|
-
let(:p10_attributes) { job_attributes.merge(priority: 13, locked_at: now - 1.day) }
|
|
109
|
-
let(:p20_attributes) { job_attributes.merge(priority: 23, attempts: 1) }
|
|
110
|
-
let(:p30_attributes) { job_attributes.merge(priority: 999, locked_at: now - 1.day) }
|
|
111
|
-
let(:p0_payload) { default_payload.merge(priority: 'interactive') }
|
|
112
|
-
let(:p10_payload) { default_payload.merge(priority: 'user_visible') }
|
|
113
|
-
let(:p20_payload) { default_payload.merge(priority: 'eventual') }
|
|
114
|
-
let(:p30_payload) { default_payload.merge(priority: 'reporting') }
|
|
115
|
-
let!(:p0_workable_job) { Delayed::Job.create! p0_attributes.merge(run_at: now - 30.seconds) }
|
|
116
|
-
let!(:p0_failed_job) { Delayed::Job.create! p0_attributes.merge(failed_attributes) }
|
|
117
|
-
let!(:p0_future_job) { Delayed::Job.create! p0_attributes.merge(run_at: now + 1.hour) }
|
|
118
|
-
let!(:p0_working_job) { Delayed::Job.create! p0_attributes.merge(locked_at: now - 3.minutes) }
|
|
119
|
-
let!(:p10_workable_job) { Delayed::Job.create! p10_attributes.merge(run_at: now - 2.minutes) }
|
|
120
|
-
let!(:p10_failed_job) { Delayed::Job.create! p10_attributes.merge(failed_attributes) }
|
|
121
|
-
let!(:p10_future_job) { Delayed::Job.create! p10_attributes.merge(run_at: now + 1.hour) }
|
|
122
|
-
let!(:p10_working_job) { Delayed::Job.create! p10_attributes.merge(locked_at: now - 7.minutes) }
|
|
123
|
-
let!(:p20_workable_job) { Delayed::Job.create! p20_attributes.merge(run_at: now - 1.hour) }
|
|
124
|
-
let!(:p20_failed_job) { Delayed::Job.create! p20_attributes.merge(failed_attributes) }
|
|
125
|
-
let!(:p20_future_job) { Delayed::Job.create! p20_attributes.merge(run_at: now + 1.hour) }
|
|
126
|
-
let!(:p20_working_job) { Delayed::Job.create! p20_attributes.merge(locked_at: now - 9.minutes) }
|
|
127
|
-
let!(:p30_workable_job) { Delayed::Job.create! p30_attributes.merge(run_at: now - 6.hours) }
|
|
128
|
-
let!(:p30_failed_job) { Delayed::Job.create! p30_attributes.merge(failed_attributes) }
|
|
129
|
-
let!(:p30_future_job) { Delayed::Job.create! p30_attributes.merge(run_at: now + 1.hour) }
|
|
130
|
-
let!(:p30_working_job) { Delayed::Job.create! p30_attributes.merge(locked_at: now - 11.minutes) }
|
|
131
|
-
let!(:p30_workable_job_in_other_queue) { Delayed::Job.create! p30_attributes.merge(run_at: now - 4.hours, queue: 'banana') }
|
|
39
|
+
let(:now) { Delayed::Job.db_time_now }
|
|
132
40
|
|
|
133
|
-
|
|
134
|
-
Timecop.freeze(now) { example.run }
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
it 'emits the expected results for each metric' do
|
|
41
|
+
it 'emits empty metrics for all default priorities' do
|
|
138
42
|
expect { subject.run! }
|
|
139
43
|
.to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
|
|
140
|
-
.and emit_notification("delayed.job.count").with_payload(
|
|
141
|
-
.and emit_notification("delayed.job.
|
|
142
|
-
.and emit_notification("delayed.job.
|
|
143
|
-
.and emit_notification("delayed.job.
|
|
144
|
-
.and emit_notification("delayed.job.
|
|
145
|
-
.and emit_notification("delayed.job.
|
|
146
|
-
.and emit_notification("delayed.job.
|
|
147
|
-
.and emit_notification("delayed.job.
|
|
148
|
-
.and emit_notification("delayed.job.
|
|
149
|
-
.and emit_notification("delayed.job.
|
|
150
|
-
.and emit_notification("delayed.job.
|
|
151
|
-
.and emit_notification("delayed.job.
|
|
152
|
-
.and emit_notification("delayed.job.
|
|
153
|
-
.and emit_notification("delayed.job.erroring_count").with_payload(
|
|
154
|
-
.and emit_notification("delayed.job.
|
|
155
|
-
.and emit_notification("delayed.job.
|
|
156
|
-
.and emit_notification("delayed.job.
|
|
157
|
-
.and emit_notification("delayed.job.
|
|
158
|
-
.and emit_notification("delayed.job.
|
|
159
|
-
.and emit_notification("delayed.job.
|
|
160
|
-
.and emit_notification("delayed.job.
|
|
161
|
-
.and emit_notification("delayed.job.
|
|
162
|
-
.and emit_notification("delayed.job.
|
|
163
|
-
.and emit_notification("delayed.job.
|
|
164
|
-
.and emit_notification("delayed.job.
|
|
165
|
-
.and emit_notification("delayed.job.
|
|
166
|
-
.and emit_notification("delayed.job.workable_count").with_payload(
|
|
167
|
-
.and emit_notification("delayed.job.
|
|
168
|
-
.and emit_notification("delayed.job.
|
|
169
|
-
.and emit_notification("delayed.job.
|
|
170
|
-
.and emit_notification("delayed.job.
|
|
171
|
-
.and emit_notification("delayed.job.
|
|
172
|
-
.and emit_notification("delayed.job.
|
|
173
|
-
.and emit_notification("delayed.job.
|
|
174
|
-
.and emit_notification("delayed.job.
|
|
175
|
-
.and emit_notification("delayed.job.
|
|
176
|
-
.and emit_notification("delayed.job.
|
|
177
|
-
.and emit_notification("delayed.job.
|
|
178
|
-
.and emit_notification("delayed.job.
|
|
179
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(
|
|
180
|
-
.and emit_notification("delayed.job.workable_count").with_payload(p30_payload.merge(queue: 'banana')).with_value(1)
|
|
181
|
-
.and emit_notification("delayed.job.max_age").with_payload(p30_payload.merge(queue: 'banana')).with_value(4.hours)
|
|
44
|
+
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
45
|
+
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
46
|
+
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
47
|
+
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
48
|
+
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
49
|
+
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
50
|
+
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
51
|
+
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
52
|
+
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
53
|
+
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
54
|
+
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
55
|
+
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
56
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
57
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
58
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
59
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
60
|
+
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
61
|
+
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
62
|
+
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
63
|
+
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
64
|
+
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
65
|
+
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
66
|
+
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
67
|
+
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
68
|
+
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
|
|
69
|
+
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
|
|
70
|
+
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
|
|
71
|
+
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
|
|
72
|
+
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'interactive')).approximately.with_value(0)
|
|
73
|
+
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'user_visible')).approximately.with_value(0)
|
|
74
|
+
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'eventual')).approximately.with_value(0)
|
|
75
|
+
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'reporting')).approximately.with_value(0)
|
|
76
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'interactive')).approximately.with_value(0)
|
|
77
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'user_visible')).approximately.with_value(0)
|
|
78
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'eventual')).approximately.with_value(0)
|
|
79
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'reporting')).approximately.with_value(0)
|
|
80
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'interactive')).approximately.with_value(0)
|
|
81
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'user_visible')).approximately.with_value(0)
|
|
82
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'eventual')).approximately.with_value(0)
|
|
83
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'reporting')).approximately.with_value(0)
|
|
182
84
|
end
|
|
183
85
|
|
|
184
86
|
context 'when named priorities are customized' do
|
|
185
87
|
around do |example|
|
|
186
|
-
Delayed::Priority.names = { high: 0, low:
|
|
88
|
+
Delayed::Priority.names = { high: 0, low: 7 }
|
|
187
89
|
example.run
|
|
188
90
|
ensure
|
|
189
91
|
Delayed::Priority.names = nil
|
|
190
92
|
end
|
|
191
|
-
|
|
192
|
-
|
|
93
|
+
|
|
94
|
+
it 'emits empty metrics for all custom priorities' do
|
|
95
|
+
expect { subject.run! }
|
|
96
|
+
.to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
|
|
97
|
+
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
98
|
+
.and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
99
|
+
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
100
|
+
.and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
101
|
+
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
102
|
+
.and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
103
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
104
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
105
|
+
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
106
|
+
.and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
107
|
+
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
108
|
+
.and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
109
|
+
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
110
|
+
.and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
111
|
+
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'high')).approximately.with_value(0)
|
|
112
|
+
.and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'low')).approximately.with_value(0)
|
|
113
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'high')).approximately.with_value(0)
|
|
114
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'low')).approximately.with_value(0)
|
|
115
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'high')).with_value(0)
|
|
116
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'low')).with_value(0)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context 'when there are jobs in the queue' do
|
|
121
|
+
let(:job_attributes) do
|
|
122
|
+
{
|
|
123
|
+
run_at: now,
|
|
124
|
+
queue: 'default',
|
|
125
|
+
handler: "--- !ruby/object:SimpleJob\n",
|
|
126
|
+
attempts: 0,
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
let(:failed_attributes) { { run_at: now - 1.week, attempts: 1, failed_at: now - 1.day, locked_at: now - 1.day } }
|
|
130
|
+
let(:p0_attributes) { job_attributes.merge(priority: 1, attempts: 1) }
|
|
131
|
+
let(:p10_attributes) { job_attributes.merge(priority: 13, locked_at: now - 1.day) }
|
|
132
|
+
let(:p20_attributes) { job_attributes.merge(priority: 23, attempts: 1) }
|
|
133
|
+
let(:p30_attributes) { job_attributes.merge(priority: 999, locked_at: now - 1.day) }
|
|
134
|
+
let(:p0_payload) { default_payload.merge(priority: 'interactive') }
|
|
135
|
+
let(:p10_payload) { default_payload.merge(priority: 'user_visible') }
|
|
136
|
+
let(:p20_payload) { default_payload.merge(priority: 'eventual') }
|
|
137
|
+
let(:p30_payload) { default_payload.merge(priority: 'reporting') }
|
|
138
|
+
let!(:p0_workable_job) { Delayed::Job.create! p0_attributes.merge(run_at: now - 30.seconds) }
|
|
139
|
+
let!(:p0_failed_job) { Delayed::Job.create! p0_attributes.merge(failed_attributes) }
|
|
140
|
+
let!(:p0_future_job) { Delayed::Job.create! p0_attributes.merge(run_at: now + 1.hour) }
|
|
141
|
+
let!(:p0_working_job) { Delayed::Job.create! p0_attributes.merge(locked_at: now - 3.minutes) }
|
|
142
|
+
let!(:p10_workable_job) { Delayed::Job.create! p10_attributes.merge(run_at: now - 2.minutes) }
|
|
143
|
+
let!(:p10_failed_job) { Delayed::Job.create! p10_attributes.merge(failed_attributes) }
|
|
144
|
+
let!(:p10_future_job) { Delayed::Job.create! p10_attributes.merge(run_at: now + 1.hour) }
|
|
145
|
+
let!(:p10_working_job) { Delayed::Job.create! p10_attributes.merge(locked_at: now - 7.minutes) }
|
|
146
|
+
let!(:p20_workable_job) { Delayed::Job.create! p20_attributes.merge(run_at: now - 1.hour) }
|
|
147
|
+
let!(:p20_failed_job) { Delayed::Job.create! p20_attributes.merge(failed_attributes) }
|
|
148
|
+
let!(:p20_future_job) { Delayed::Job.create! p20_attributes.merge(run_at: now + 1.hour) }
|
|
149
|
+
let!(:p20_working_job) { Delayed::Job.create! p20_attributes.merge(locked_at: now - 9.minutes) }
|
|
150
|
+
let!(:p30_workable_job) { Delayed::Job.create! p30_attributes.merge(run_at: now - 6.hours) }
|
|
151
|
+
let!(:p30_failed_job) { Delayed::Job.create! p30_attributes.merge(failed_attributes) }
|
|
152
|
+
let!(:p30_future_job) { Delayed::Job.create! p30_attributes.merge(run_at: now + 1.hour) }
|
|
153
|
+
let!(:p30_working_job) { Delayed::Job.create! p30_attributes.merge(locked_at: now - 11.minutes) }
|
|
154
|
+
let!(:p30_workable_job_in_other_queue) { Delayed::Job.create! p30_attributes.merge(run_at: now - 4.hours, queue: 'banana') }
|
|
193
155
|
|
|
194
156
|
it 'emits the expected results for each metric' do
|
|
195
157
|
expect { subject.run! }
|
|
196
158
|
.to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
|
|
197
|
-
.and emit_notification("delayed.job.count").with_payload(p0_payload).with_value(
|
|
198
|
-
.and emit_notification("delayed.job.future_count").with_payload(p0_payload).with_value(
|
|
199
|
-
.and emit_notification("delayed.job.locked_count").with_payload(p0_payload).with_value(
|
|
159
|
+
.and emit_notification("delayed.job.count").with_payload(p0_payload).with_value(4)
|
|
160
|
+
.and emit_notification("delayed.job.future_count").with_payload(p0_payload).with_value(1)
|
|
161
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p0_payload).with_value(1)
|
|
200
162
|
.and emit_notification("delayed.job.erroring_count").with_payload(p0_payload).with_value(3)
|
|
201
|
-
.and emit_notification("delayed.job.failed_count").with_payload(p0_payload).with_value(
|
|
202
|
-
.and emit_notification("delayed.job.working_count").with_payload(p0_payload).with_value(
|
|
203
|
-
.and emit_notification("delayed.job.workable_count").with_payload(p0_payload).with_value(
|
|
204
|
-
.and emit_notification("delayed.job.max_age").with_payload(p0_payload).with_value(
|
|
205
|
-
.and emit_notification("delayed.job.max_lock_age").with_payload(p0_payload).with_value(
|
|
206
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(p0_payload).with_value(0)
|
|
207
|
-
.and emit_notification("delayed.job.count").with_payload(
|
|
208
|
-
.and emit_notification("delayed.job.future_count").with_payload(
|
|
209
|
-
.and emit_notification("delayed.job.locked_count").with_payload(
|
|
163
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p0_payload).with_value(1)
|
|
164
|
+
.and emit_notification("delayed.job.working_count").with_payload(p0_payload).with_value(1)
|
|
165
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p0_payload).with_value(1)
|
|
166
|
+
.and emit_notification("delayed.job.max_age").with_payload(p0_payload).approximately.with_value(30.seconds)
|
|
167
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p0_payload).approximately.with_value(3.minutes)
|
|
168
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p0_payload).approximately.with_value(30.0.seconds / 1.minute * 100)
|
|
169
|
+
.and emit_notification("delayed.job.count").with_payload(p10_payload).with_value(4)
|
|
170
|
+
.and emit_notification("delayed.job.future_count").with_payload(p10_payload).with_value(1)
|
|
171
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p10_payload).with_value(1)
|
|
172
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(p10_payload).with_value(0)
|
|
173
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p10_payload).with_value(1)
|
|
174
|
+
.and emit_notification("delayed.job.working_count").with_payload(p10_payload).with_value(1)
|
|
175
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p10_payload).with_value(1)
|
|
176
|
+
.and emit_notification("delayed.job.max_age").with_payload(p10_payload).approximately.with_value(2.minutes)
|
|
177
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p10_payload).approximately.with_value(7.minutes)
|
|
178
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p10_payload).approximately.with_value(2.0.minutes / 3.minutes * 100)
|
|
179
|
+
.and emit_notification("delayed.job.count").with_payload(p20_payload).with_value(4)
|
|
180
|
+
.and emit_notification("delayed.job.future_count").with_payload(p20_payload).with_value(1)
|
|
181
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p20_payload).with_value(1)
|
|
210
182
|
.and emit_notification("delayed.job.erroring_count").with_payload(p20_payload).with_value(3)
|
|
211
|
-
.and emit_notification("delayed.job.failed_count").with_payload(p20_payload).with_value(
|
|
212
|
-
.and emit_notification("delayed.job.working_count").with_payload(p20_payload).with_value(
|
|
213
|
-
.and emit_notification("delayed.job.workable_count").with_payload(p20_payload).with_value(
|
|
214
|
-
.and emit_notification("delayed.job.max_age").with_payload(p20_payload).with_value(
|
|
215
|
-
.and emit_notification("delayed.job.max_lock_age").with_payload(p20_payload).with_value(
|
|
216
|
-
.and emit_notification("delayed.job.alert_age_percent").with_payload(p20_payload).with_value(
|
|
217
|
-
.and emit_notification("delayed.job.
|
|
218
|
-
.and emit_notification("delayed.job.
|
|
183
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p20_payload).with_value(1)
|
|
184
|
+
.and emit_notification("delayed.job.working_count").with_payload(p20_payload).with_value(1)
|
|
185
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p20_payload).with_value(1)
|
|
186
|
+
.and emit_notification("delayed.job.max_age").with_payload(p20_payload).approximately.with_value(1.hour)
|
|
187
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p20_payload).approximately.with_value(9.minutes)
|
|
188
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p20_payload).approximately.with_value(1.hour / 1.5.hours * 100)
|
|
189
|
+
.and emit_notification("delayed.job.count").with_payload(p30_payload).with_value(4)
|
|
190
|
+
.and emit_notification("delayed.job.future_count").with_payload(p30_payload).with_value(1)
|
|
191
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p30_payload).with_value(1)
|
|
192
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(p30_payload).with_value(0)
|
|
193
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p30_payload).with_value(1)
|
|
194
|
+
.and emit_notification("delayed.job.working_count").with_payload(p30_payload).with_value(1)
|
|
195
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p30_payload).with_value(1)
|
|
196
|
+
.and emit_notification("delayed.job.max_age").with_payload(p30_payload).approximately.with_value(6.hours)
|
|
197
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p30_payload).approximately.with_value(11.minutes)
|
|
198
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p30_payload).approximately.with_value(100) # 6 hours / 4 hours (overflow)
|
|
199
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p30_payload.merge(queue: 'banana')).with_value(1)
|
|
200
|
+
.and emit_notification("delayed.job.max_age").with_payload(p30_payload.merge(queue: 'banana')).approximately.with_value(4.hours)
|
|
219
201
|
end
|
|
220
202
|
|
|
221
|
-
context 'when
|
|
203
|
+
context 'when named priorities are customized' do
|
|
222
204
|
around do |example|
|
|
223
|
-
Delayed::Priority.
|
|
205
|
+
Delayed::Priority.names = { high: 0, low: 20 }
|
|
224
206
|
example.run
|
|
225
207
|
ensure
|
|
226
|
-
Delayed::Priority.
|
|
208
|
+
Delayed::Priority.names = nil
|
|
227
209
|
end
|
|
210
|
+
let(:p0_payload) { default_payload.merge(priority: 'high') }
|
|
211
|
+
let(:p20_payload) { default_payload.merge(priority: 'low') }
|
|
228
212
|
|
|
229
|
-
it 'emits the expected
|
|
213
|
+
it 'emits the expected results for each metric' do
|
|
230
214
|
expect { subject.run! }
|
|
231
|
-
.to emit_notification("delayed.
|
|
232
|
-
.and emit_notification("delayed.job.
|
|
215
|
+
.to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
|
|
216
|
+
.and emit_notification("delayed.job.count").with_payload(p0_payload).with_value(8)
|
|
217
|
+
.and emit_notification("delayed.job.future_count").with_payload(p0_payload).with_value(2)
|
|
218
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p0_payload).with_value(2)
|
|
219
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(p0_payload).with_value(3)
|
|
220
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p0_payload).with_value(2)
|
|
221
|
+
.and emit_notification("delayed.job.working_count").with_payload(p0_payload).with_value(2)
|
|
222
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p0_payload).with_value(2)
|
|
223
|
+
.and emit_notification("delayed.job.max_age").with_payload(p0_payload).approximately.with_value(2.minutes)
|
|
224
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p0_payload).approximately.with_value(7.minutes)
|
|
225
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p0_payload).approximately.with_value(0)
|
|
226
|
+
.and emit_notification("delayed.job.count").with_payload(p20_payload).with_value(8)
|
|
227
|
+
.and emit_notification("delayed.job.future_count").with_payload(p20_payload).with_value(2)
|
|
228
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p20_payload).with_value(2)
|
|
229
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(p20_payload).with_value(3)
|
|
230
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p20_payload).with_value(2)
|
|
231
|
+
.and emit_notification("delayed.job.working_count").with_payload(p20_payload).with_value(2)
|
|
232
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p20_payload).with_value(2)
|
|
233
|
+
.and emit_notification("delayed.job.max_age").with_payload(p20_payload).approximately.with_value(6.hours)
|
|
234
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p20_payload).approximately.with_value(11.minutes)
|
|
235
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p20_payload).approximately.with_value(0)
|
|
236
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p20_payload.merge(queue: 'banana')).with_value(1)
|
|
237
|
+
.and emit_notification("delayed.job.max_age").with_payload(p20_payload.merge(queue: 'banana')).approximately.with_value(4.hours)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
context 'when alert thresholds are specified' do
|
|
241
|
+
around do |example|
|
|
242
|
+
Delayed::Priority.alerts = { high: { age: 3.hours }, low: { age: 1.year } }
|
|
243
|
+
example.run
|
|
244
|
+
ensure
|
|
245
|
+
Delayed::Priority.alerts = nil
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'emits the expected alert_age_percent results' do
|
|
249
|
+
expect { subject.run! }
|
|
250
|
+
.to emit_notification("delayed.job.alert_age_percent").with_payload(p0_payload).approximately.with_value(2.0.minutes / 3.hours * 100)
|
|
251
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p20_payload).approximately.with_value(6.0.hours / 1.year * 100)
|
|
252
|
+
end
|
|
233
253
|
end
|
|
234
254
|
end
|
|
235
|
-
end
|
|
236
255
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
256
|
+
context 'when worker queues are specified' do
|
|
257
|
+
around do |example|
|
|
258
|
+
Delayed::Worker.queues = %w(banana gram)
|
|
259
|
+
Delayed::Priority.names = { interactive: 0 } # avoid splitting by priority for simplicity
|
|
260
|
+
Delayed::Priority.alerts = { interactive: { age: 8.hours } }
|
|
261
|
+
example.run
|
|
262
|
+
ensure
|
|
263
|
+
Delayed::Priority.names = nil
|
|
264
|
+
Delayed::Worker.queues = []
|
|
265
|
+
end
|
|
266
|
+
let(:banana_payload) { default_payload.merge(queue: 'banana', priority: 'interactive') }
|
|
267
|
+
let(:gram_payload) { default_payload.merge(queue: 'gram', priority: 'interactive') }
|
|
268
|
+
|
|
269
|
+
it 'emits the expected results for each queue' do
|
|
270
|
+
expect { subject.run! }
|
|
271
|
+
.to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
|
|
272
|
+
.and emit_notification("delayed.job.count").with_payload(banana_payload).with_value(1)
|
|
273
|
+
.and emit_notification("delayed.job.future_count").with_payload(banana_payload).with_value(0)
|
|
274
|
+
.and emit_notification("delayed.job.locked_count").with_payload(banana_payload).with_value(0)
|
|
275
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(banana_payload).with_value(0)
|
|
276
|
+
.and emit_notification("delayed.job.failed_count").with_payload(banana_payload).with_value(0)
|
|
277
|
+
.and emit_notification("delayed.job.working_count").with_payload(banana_payload).with_value(0)
|
|
278
|
+
.and emit_notification("delayed.job.workable_count").with_payload(banana_payload).with_value(1)
|
|
279
|
+
.and emit_notification("delayed.job.max_age").with_payload(banana_payload).approximately.with_value(4.hours)
|
|
280
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(banana_payload).approximately.with_value(0)
|
|
281
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(banana_payload).approximately.with_value(4.0.hours / 8.hours * 100)
|
|
282
|
+
.and emit_notification("delayed.job.count").with_payload(gram_payload).with_value(0)
|
|
283
|
+
.and emit_notification("delayed.job.future_count").with_payload(gram_payload).with_value(0)
|
|
284
|
+
.and emit_notification("delayed.job.locked_count").with_payload(gram_payload).with_value(0)
|
|
285
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(gram_payload).with_value(0)
|
|
286
|
+
.and emit_notification("delayed.job.failed_count").with_payload(gram_payload).with_value(0)
|
|
287
|
+
.and emit_notification("delayed.job.working_count").with_payload(gram_payload).with_value(0)
|
|
288
|
+
.and emit_notification("delayed.job.workable_count").with_payload(gram_payload).with_value(0)
|
|
289
|
+
.and emit_notification("delayed.job.max_age").with_payload(gram_payload).approximately.with_value(0)
|
|
290
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(gram_payload).approximately.with_value(0)
|
|
291
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(gram_payload).approximately.with_value(0)
|
|
292
|
+
end
|
|
246
293
|
end
|
|
247
|
-
let(:banana_payload) { default_payload.merge(queue: 'banana', priority: 'interactive') }
|
|
248
|
-
let(:gram_payload) { default_payload.merge(queue: 'gram', priority: 'interactive') }
|
|
249
294
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
295
|
+
context 'when using app-local timezone for DB timestamps' do
|
|
296
|
+
let(:app_local_db_time) { true }
|
|
297
|
+
|
|
298
|
+
it 'emits the expected results for each metric' do
|
|
299
|
+
expect { subject.run! }
|
|
300
|
+
.to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
|
|
301
|
+
.and emit_notification("delayed.job.count").with_payload(p0_payload).with_value(4)
|
|
302
|
+
.and emit_notification("delayed.job.future_count").with_payload(p0_payload).with_value(1)
|
|
303
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p0_payload).with_value(1)
|
|
304
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(p0_payload).with_value(3)
|
|
305
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p0_payload).with_value(1)
|
|
306
|
+
.and emit_notification("delayed.job.working_count").with_payload(p0_payload).with_value(1)
|
|
307
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p0_payload).with_value(1)
|
|
308
|
+
.and emit_notification("delayed.job.max_age").with_payload(p0_payload).approximately.with_value(30.seconds)
|
|
309
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p0_payload).approximately.with_value(3.minutes)
|
|
310
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p0_payload).approximately.with_value(30.0.seconds / 1.minute * 100)
|
|
311
|
+
.and emit_notification("delayed.job.count").with_payload(p10_payload).with_value(4)
|
|
312
|
+
.and emit_notification("delayed.job.future_count").with_payload(p10_payload).with_value(1)
|
|
313
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p10_payload).with_value(1)
|
|
314
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(p10_payload).with_value(0)
|
|
315
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p10_payload).with_value(1)
|
|
316
|
+
.and emit_notification("delayed.job.working_count").with_payload(p10_payload).with_value(1)
|
|
317
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p10_payload).with_value(1)
|
|
318
|
+
.and emit_notification("delayed.job.max_age").with_payload(p10_payload).approximately.with_value(2.minutes)
|
|
319
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p10_payload).approximately.with_value(7.minutes)
|
|
320
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p10_payload).approximately.with_value(2.0.minutes / 3.minutes * 100)
|
|
321
|
+
.and emit_notification("delayed.job.count").with_payload(p20_payload).with_value(4)
|
|
322
|
+
.and emit_notification("delayed.job.future_count").with_payload(p20_payload).with_value(1)
|
|
323
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p20_payload).with_value(1)
|
|
324
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(p20_payload).with_value(3)
|
|
325
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p20_payload).with_value(1)
|
|
326
|
+
.and emit_notification("delayed.job.working_count").with_payload(p20_payload).with_value(1)
|
|
327
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p20_payload).with_value(1)
|
|
328
|
+
.and emit_notification("delayed.job.max_age").with_payload(p20_payload).approximately.with_value(1.hour)
|
|
329
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p20_payload).approximately.with_value(9.minutes)
|
|
330
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p20_payload).approximately.with_value(1.hour / 1.5.hours * 100)
|
|
331
|
+
.and emit_notification("delayed.job.count").with_payload(p30_payload).with_value(4)
|
|
332
|
+
.and emit_notification("delayed.job.future_count").with_payload(p30_payload).with_value(1)
|
|
333
|
+
.and emit_notification("delayed.job.locked_count").with_payload(p30_payload).with_value(1)
|
|
334
|
+
.and emit_notification("delayed.job.erroring_count").with_payload(p30_payload).with_value(0)
|
|
335
|
+
.and emit_notification("delayed.job.failed_count").with_payload(p30_payload).with_value(1)
|
|
336
|
+
.and emit_notification("delayed.job.working_count").with_payload(p30_payload).with_value(1)
|
|
337
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p30_payload).with_value(1)
|
|
338
|
+
.and emit_notification("delayed.job.max_age").with_payload(p30_payload).approximately.with_value(6.hours)
|
|
339
|
+
.and emit_notification("delayed.job.max_lock_age").with_payload(p30_payload).approximately.with_value(11.minutes)
|
|
340
|
+
.and emit_notification("delayed.job.alert_age_percent").with_payload(p30_payload).approximately.with_value(100) # 6 hours / 4 hours (overflow)
|
|
341
|
+
.and emit_notification("delayed.job.workable_count").with_payload(p30_payload.merge(queue: 'banana')).with_value(1)
|
|
342
|
+
.and emit_notification("delayed.job.max_age").with_payload(p30_payload.merge(queue: 'banana')).approximately.with_value(4.hours)
|
|
343
|
+
end
|
|
273
344
|
end
|
|
274
345
|
end
|
|
275
346
|
end
|
data/spec/helper.rb
CHANGED
|
@@ -38,13 +38,12 @@ end
|
|
|
38
38
|
|
|
39
39
|
ENV['RAILS_ENV'] = 'test'
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
db_adapter ||= "sqlite3"
|
|
41
|
+
def current_adapter
|
|
42
|
+
ENV.fetch('ADAPTER', 'sqlite3')
|
|
43
|
+
end
|
|
45
44
|
|
|
46
45
|
config = YAML.load(ERB.new(File.read("spec/database.yml")).result)
|
|
47
|
-
ActiveRecord::Base.establish_connection config[
|
|
46
|
+
ActiveRecord::Base.establish_connection config[current_adapter]
|
|
48
47
|
ActiveRecord::Base.logger = Delayed.logger
|
|
49
48
|
ActiveJob::Base.logger = Delayed.logger
|
|
50
49
|
ActiveRecord::Migration.verbose = false
|
|
@@ -58,7 +57,7 @@ end
|
|
|
58
57
|
# MySQL 5.7 no longer supports null default values for the primary key
|
|
59
58
|
# Override the default primary key type in Rails <= 4.0
|
|
60
59
|
# https://stackoverflow.com/a/34555109
|
|
61
|
-
if
|
|
60
|
+
if current_adapter == "mysql2"
|
|
62
61
|
types = if defined?(ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter)
|
|
63
62
|
# ActiveRecord 3.2+
|
|
64
63
|
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::NATIVE_DATABASE_TYPES
|
|
@@ -208,6 +207,14 @@ else
|
|
|
208
207
|
ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
|
|
209
208
|
end
|
|
210
209
|
|
|
210
|
+
def default_timezone=(zone)
|
|
211
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
|
212
|
+
ActiveRecord.default_timezone = zone
|
|
213
|
+
else
|
|
214
|
+
ActiveRecord::Base.default_timezone = zone
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
211
218
|
RSpec::Matchers.define :emit_notification do |expected_event_name|
|
|
212
219
|
attr_reader :actual, :expected
|
|
213
220
|
|
|
@@ -217,10 +224,15 @@ RSpec::Matchers.define :emit_notification do |expected_event_name|
|
|
|
217
224
|
|
|
218
225
|
chain :with_payload, :expected_payload
|
|
219
226
|
chain :with_value, :expected_value
|
|
227
|
+
chain(:approximately) { @approximately = true }
|
|
220
228
|
diffable
|
|
221
229
|
|
|
222
230
|
match do |block|
|
|
223
|
-
@
|
|
231
|
+
if @approximately && current_adapter != 'postgresql'
|
|
232
|
+
@expected_value = a_value_within([2, @expected_value.abs * 0.05].max).of(@expected_value)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
@expected = { event_name: expected_event_name, payload: expected_payload, value: @expected_value }
|
|
224
236
|
@actuals = []
|
|
225
237
|
callback = ->(name, _started, _finished, _unique_id, payload) do
|
|
226
238
|
@actuals << { event_name: name, payload: payload.except(:value), value: payload[:value] }
|
|
@@ -249,10 +261,6 @@ RSpec::Matchers.define :emit_notification do |expected_event_name|
|
|
|
249
261
|
end
|
|
250
262
|
end
|
|
251
263
|
|
|
252
|
-
def current_adapter
|
|
253
|
-
ENV.fetch('ADAPTER', 'sqlite3')
|
|
254
|
-
end
|
|
255
|
-
|
|
256
264
|
def current_database
|
|
257
265
|
if current_adapter == 'sqlite3'
|
|
258
266
|
a_string_ending_with('tmp/database.sqlite')
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: delayed
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Griffith
|
|
@@ -19,7 +19,7 @@ authors:
|
|
|
19
19
|
autorequire:
|
|
20
20
|
bindir: bin
|
|
21
21
|
cert_chain: []
|
|
22
|
-
date: 2026-02-
|
|
22
|
+
date: 2026-02-05 00:00:00.000000000 Z
|
|
23
23
|
dependencies:
|
|
24
24
|
- !ruby/object:Gem::Dependency
|
|
25
25
|
name: activerecord
|