resque-scheduler 5.0.0 → 5.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/.github/workflows/ruby.yml +16 -9
- data/CHANGELOG.md +42 -1
- data/Gemfile +0 -7
- data/README.md +60 -3
- data/lib/resque/scheduler/cli.rb +17 -1
- data/lib/resque/scheduler/configuration.rb +15 -2
- data/lib/resque/scheduler/delaying_extensions.rb +42 -20
- data/lib/resque/scheduler/env.rb +15 -7
- data/lib/resque/scheduler/plugin.rb +36 -0
- data/lib/resque/scheduler/railtie.rb +11 -0
- data/lib/resque/scheduler/server/views/delayed_timestamp.erb +1 -1
- data/lib/resque/scheduler/server/views/requeue-params.erb +5 -5
- data/lib/resque/scheduler/server/views/scheduler.erb +2 -2
- data/lib/resque/scheduler/server/views/search.erb +6 -6
- data/lib/resque/scheduler/server/views/search_form.erb +2 -0
- data/lib/resque/scheduler/server.rb +15 -15
- data/lib/resque/scheduler/util.rb +5 -0
- data/lib/resque/scheduler/version.rb +1 -1
- data/lib/resque/scheduler.rb +60 -25
- data/resque-scheduler.gemspec +4 -5
- metadata +23 -20
- data/.github/funding.yml +0 -4
- data/.github/workflows/codeql-analysis.yml +0 -59
- data/.github/workflows/rubocop.yml +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6aa6e1f1a8fe48f091032409157797e48011e473fca30c8fd12d18d05517de49
|
|
4
|
+
data.tar.gz: cbcc8d19930096797a1d028a9765b9c7c0b65ae023011ccec64cf8005420f183
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3842dbcedbb2768e715aef2355f11333cadfa101ccb5a1475dbda155005d494408e0554c027ba5cf72afcabc7dcd3778125b9f512680850c875efbccb94f64f
|
|
7
|
+
data.tar.gz: 9623d1a81de5c0e1179e7be932ad6a19641dbcbc57c7b9034fc531160ae6e896374611d5d0f1dc64a710c27293cbc0e1db0eda9f6e5ad4c5ed8208398e20cdae
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -6,9 +6,11 @@ on:
|
|
|
6
6
|
pull_request:
|
|
7
7
|
branches: [master]
|
|
8
8
|
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
9
12
|
jobs:
|
|
10
13
|
test:
|
|
11
|
-
continue-on-error: true
|
|
12
14
|
runs-on: ubuntu-latest
|
|
13
15
|
services:
|
|
14
16
|
redis:
|
|
@@ -19,20 +21,16 @@ jobs:
|
|
|
19
21
|
fail-fast: false
|
|
20
22
|
matrix:
|
|
21
23
|
ruby-version:
|
|
22
|
-
- "3.0"
|
|
23
|
-
- 3.1
|
|
24
24
|
- 3.2
|
|
25
25
|
- 3.3
|
|
26
26
|
- 3.4
|
|
27
|
+
- "4.0"
|
|
27
28
|
- head
|
|
28
29
|
resque-version:
|
|
29
30
|
- "master"
|
|
30
|
-
rufus-scheduler:
|
|
31
|
-
- "3.4"
|
|
32
|
-
- "3.5"
|
|
33
|
-
- "3.6"
|
|
34
31
|
redis-version:
|
|
35
32
|
- "4.8"
|
|
33
|
+
- "5.4"
|
|
36
34
|
- "latest"
|
|
37
35
|
rack-version:
|
|
38
36
|
- 2
|
|
@@ -40,15 +38,24 @@ jobs:
|
|
|
40
38
|
env:
|
|
41
39
|
REDIS_VERSION: "${{ matrix.redis-version }}"
|
|
42
40
|
RESQUE: "${{ matrix.resque-version }}"
|
|
43
|
-
RUFUS_SCHEDULER: "${{ matrix.rufus-scheduler }}"
|
|
44
41
|
RACK_VERSION: "${{ matrix.rack-version }}"
|
|
45
42
|
COVERAGE: 1
|
|
46
43
|
|
|
47
44
|
steps:
|
|
48
|
-
- uses: actions/checkout@
|
|
45
|
+
- uses: actions/checkout@v7
|
|
49
46
|
- uses: ruby/setup-ruby@v1
|
|
50
47
|
with:
|
|
51
48
|
ruby-version: "${{ matrix.ruby-version }}"
|
|
52
49
|
bundler-cache: true
|
|
53
50
|
- run: bundle exec rake
|
|
54
51
|
- run: bin/bundle_console_test.sh
|
|
52
|
+
|
|
53
|
+
rubocop:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v7
|
|
57
|
+
- uses: ruby/setup-ruby@v1
|
|
58
|
+
with:
|
|
59
|
+
ruby-version: 3.4
|
|
60
|
+
bundler-cache: true
|
|
61
|
+
- run: bundle exec rubocop
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [5.1.0] - 2026-10-28
|
|
6
|
+
### Breaking Changes
|
|
7
|
+
* Ruby requirement: `>= 3.0.0` → `>= 3.2.0` by @neuromanskie in #837
|
|
8
|
+
* rufus-scheduler dependency: `~> 3.2, != 3.3` → `~> 3.9` by @neuromanskie in #837
|
|
9
|
+
* Schedule entries (`cron`/`every`) no longer run `before_schedule`, `after_schedule` or `before_delayed_enqueue`. Move that code to `before_recurring`/`after_recurring` by @top-sigrid in #844
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
* `before_recurring` and `after_recurring` hooks for jobs enqueued from the schedule by @top-sigrid in #844
|
|
13
|
+
* `DISABLE_DELAYED_REQUEUE_BATCHES` to opt out of batched delayed job enqueues by @onyxraven in #796
|
|
14
|
+
* Search delayed jobs by args in the web UI by @bmo in #839 (originally #714)
|
|
15
|
+
* Allow a number of seconds for `every` by @carsonreinke in #838 (originally #607)
|
|
16
|
+
* Railtie to load resque-scheduler automatically in Rails apps by @josh-m-sharpe in #694
|
|
17
|
+
* Add Ruby 4.0 and redis-rb 5.4 to the CI matrix by @neuromanskie in #837
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
* Upgrade RuboCop to 1.91 and run it as a CI job by @neuromanskie in #837
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
* Delayed jobs no longer run `before_schedule`/`after_schedule` a second time when they become due by @top-sigrid in #844
|
|
24
|
+
* Skip schedule hooks when `enqueue_at` is given a timestamp that is not in the future by @top-sigrid in #817
|
|
25
|
+
* Schedule Active Job classes through `perform_later` by @neuromanskie and @josh-m-sharpe in #842 (originally #696)
|
|
26
|
+
* Schedule delayed test jobs far enough out to not race the clock by @neuromanskie in #843
|
|
27
|
+
* Fail CI when tests fail by @neuromanskie in #837
|
|
28
|
+
|
|
29
|
+
## [5.0.1] - 2026-10-11
|
|
30
|
+
### Changed
|
|
31
|
+
* Pipeline `count_all_scheduled_jobs` for significantly faster execution by @top-sigrid in #822
|
|
32
|
+
* Avoid unnecessary reloads in static mode by @ekmartin in #823
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
* Skip invalid schedules without interrupting scheduler loading by @taf2 in #543
|
|
36
|
+
* Encode a delayed job once so its timestamps key can be cleaned up by @kamaljoshi in #827 (originally #599)
|
|
37
|
+
* Log to the logfile when backgrounded by @meatballhat in #828 (originally #568)
|
|
38
|
+
* Require redis before using its error classes by @neuromanskie in #829
|
|
39
|
+
* Fix flaky test by using strings instead of symbols for JSON-serialized args by @top-sigrid in #813
|
|
40
|
+
|
|
41
|
+
### Security
|
|
42
|
+
* Escape job class and queue names in the scheduler web UI by @PatrickTulskie in #830
|
|
43
|
+
* Escape schedule names and parameters on the requeue params page by @PatrickTulskie in #831
|
|
44
|
+
* Escape the scheduler environment and master lock holder on the schedule page by @PatrickTulskie in #832
|
|
45
|
+
|
|
5
46
|
## [5.0.0] - 2026-01-15
|
|
6
47
|
### Breaking Changes
|
|
7
48
|
* Ruby requirement: `>= 2.3.0` → `>= 3.0.0`
|
|
@@ -32,7 +73,7 @@
|
|
|
32
73
|
* Fixed test compatibility with Rack 3 by @PatrickTulskie in #809
|
|
33
74
|
* Fixed race condition in scheduler task tests by @PatrickTulskie in #809
|
|
34
75
|
|
|
35
|
-
## [4.
|
|
76
|
+
## [4.11.0] - 2024-12-30
|
|
36
77
|
### Added
|
|
37
78
|
* Expose timeout for lock via environment variable configuration by @pmm4654 in #786
|
|
38
79
|
* Add Ruby 3.3 to the CI matrix by @santiagorodriguez96 in #797
|
data/Gemfile
CHANGED
|
@@ -8,13 +8,6 @@ else
|
|
|
8
8
|
gem 'resque', resque_version
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
case rufus_scheduler_version = ENV.fetch('RUFUS_SCHEDULER', '3.6')
|
|
12
|
-
when 'master'
|
|
13
|
-
gem 'rufus-scheduler', git: 'https://github.com/jmettraux/rufus-scheduler'
|
|
14
|
-
else
|
|
15
|
-
gem 'rufus-scheduler', rufus_scheduler_version
|
|
16
|
-
end
|
|
17
|
-
|
|
18
11
|
case redis_version = ENV.fetch('REDIS_VERSION', 'latest')
|
|
19
12
|
when 'master'
|
|
20
13
|
gem 'redis', git: 'https://github.com/redis/redis-rb'
|
data/README.md
CHANGED
|
@@ -48,6 +48,9 @@ Adding the resque:scheduler rake task:
|
|
|
48
48
|
require 'resque/scheduler/tasks'
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
In a Rails app, you can skip this step. A Railtie loads the
|
|
52
|
+
`resque:scheduler` rake task and the Resque web UI tabs automatically.
|
|
53
|
+
|
|
51
54
|
### Rake integration
|
|
52
55
|
|
|
53
56
|
By default, `resque-scheduler` depends on the "resque:setup" rake task.
|
|
@@ -131,6 +134,8 @@ environment variables:
|
|
|
131
134
|
* `APP_NAME` - Application name used in procline (`$0`) (default empty)
|
|
132
135
|
* `BACKGROUND` - [Run in the background](#running-in-the-background) if
|
|
133
136
|
non-empty (via `Process.daemon`, if supported) (default `false`)
|
|
137
|
+
* `DELAYED_REQUEUE_BATCH_SIZE` - Set the delayed job batch size if enabled (default `100`). If `<= 1`, this disables batching.
|
|
138
|
+
* `DISABLE_DELAYED_REQUEUE_BATCHES` - Disable batched delayed job queuing (default `false`) - [See section below on consequences](#batched-delayed-job-and-resque-enqueue-hooks)
|
|
134
139
|
* `DYNAMIC_SCHEDULE` - Enables [dynamic scheduling](#dynamic-schedules)
|
|
135
140
|
if non-empty (default `false`)
|
|
136
141
|
* `RAILS_ENV` - Environment to use in procline (`$0`) (default empty)
|
|
@@ -199,6 +204,19 @@ Resque.enqueue_at_with_queue(
|
|
|
199
204
|
)
|
|
200
205
|
```
|
|
201
206
|
|
|
207
|
+
#### Active Job
|
|
208
|
+
|
|
209
|
+
`ActiveJob::Base` subclasses can be passed to `enqueue_at`, `enqueue_in` and
|
|
210
|
+
their `_with_queue` variants, and used as the `class` of a scheduled job. They
|
|
211
|
+
are handed to `perform_later` (with `set(wait_until:)` for delayed jobs), so
|
|
212
|
+
Active Job must be using the `:resque` adapter. The job's `queue_as` applies
|
|
213
|
+
unless a queue is given.
|
|
214
|
+
|
|
215
|
+
Since the stored class is the adapter's `JobWrapper`, `delayed?`,
|
|
216
|
+
`remove_delayed` and `find_delayed_selection` won't match Active Job classes,
|
|
217
|
+
and [hooks](#hooks) defined on them aren't called. Use Active Job's callbacks
|
|
218
|
+
instead.
|
|
219
|
+
|
|
202
220
|
The delayed queue is stored in redis and is persisted in the same way the
|
|
203
221
|
standard resque jobs are persisted (redis writing to disk). Delayed jobs differ
|
|
204
222
|
from scheduled jobs in that if your scheduler process is down or workers are
|
|
@@ -457,6 +475,8 @@ config[:persist] = true
|
|
|
457
475
|
Resque.set_schedule(name, config)
|
|
458
476
|
```
|
|
459
477
|
|
|
478
|
+
`every` also accepts a number of seconds, so `config[:every] = 1.hour` works too.
|
|
479
|
+
|
|
460
480
|
Schedules can later be removed by passing their name to the `remove_schedule` method:
|
|
461
481
|
|
|
462
482
|
```ruby
|
|
@@ -506,13 +526,18 @@ Similar to the `before_enqueue`- and `after_enqueue`-hooks provided in Resque
|
|
|
506
526
|
the delayed queue. If the hook returns `false`, the job will not be placed on
|
|
507
527
|
the queue.
|
|
508
528
|
* `after_schedule`: Called with the job args after a job is placed on the
|
|
509
|
-
delayed queue. Any exception raised propagates up to the code
|
|
529
|
+
delayed queue. Any exception raised propagates up to the code that queued the
|
|
510
530
|
job.
|
|
511
531
|
* `before_delayed_enqueue`: Called with the job args after the job has been
|
|
512
|
-
removed from the delayed queue, but not yet put on
|
|
532
|
+
removed from the delayed queue, but not yet put on its work queue. It is
|
|
513
533
|
called before `before_enqueue`-hooks, and on the same job instance as the
|
|
514
534
|
`before_enqueue`-hooks will be invoked on. Return values are ignored.
|
|
515
|
-
* `
|
|
535
|
+
* `before_recurring`: Called with the job args before a job from the schedule
|
|
536
|
+
(`cron`, `every` or "Queue now" in resque-web) is put on its work queue. If
|
|
537
|
+
the hook returns `false`, the job will not be enqueued.
|
|
538
|
+
* `after_recurring`: Called with the job args after a job from the schedule has
|
|
539
|
+
been put on its work queue, after the `after_enqueue`-hooks.
|
|
540
|
+
* `on_enqueue_failure`: Called with the job config and the exception that was raised
|
|
516
541
|
while enqueueing a job to resque or external application fails. Return
|
|
517
542
|
values are ignored. For example:
|
|
518
543
|
|
|
@@ -610,6 +635,9 @@ The Delayed tab:
|
|
|
610
635
|
|
|
611
636
|

|
|
612
637
|
|
|
638
|
+
The Delayed tab's search matches job class names, or job arguments when
|
|
639
|
+
"Search Args?" is checked.
|
|
640
|
+
|
|
613
641
|
#### How do I get the schedule tabs to show up???
|
|
614
642
|
|
|
615
643
|
To get these to show up you need to pass a file to `resque-web` to tell it to
|
|
@@ -752,9 +780,38 @@ This table explains the version requirements for rufus-scheduler
|
|
|
752
780
|
|
|
753
781
|
| resque-scheduler | rufus-scheduler |
|
|
754
782
|
|:-----------------|----------------:|
|
|
783
|
+
| `>= 5.1` | `~> 3.9` |
|
|
755
784
|
| `~> 4.0` | `~> 3.0` |
|
|
756
785
|
| `< 4.0` | `~> 2.0` |
|
|
757
786
|
|
|
787
|
+
##### Batched delayed job and resque enqueue hooks
|
|
788
|
+
|
|
789
|
+
Batching delayed job queuing can speed up when per-second job counts grows,
|
|
790
|
+
avoiding situations that may cause delayed enqueues to fall behind. This
|
|
791
|
+
batching wraps enqueues in a `multi` pipeline, making far fewer roundtrips to
|
|
792
|
+
the server.
|
|
793
|
+
|
|
794
|
+
However, in `redis` gem `>= 4.0`, any operations to Redis within the `multi`
|
|
795
|
+
block must use the multi handle so that the actions are captured. Resque's hooks
|
|
796
|
+
do not currently have a way to pass this around, and so compatibility with other
|
|
797
|
+
Resque plugins or hooks which access Redis at enqueue time is impacted with
|
|
798
|
+
batch mode (A main class of plugins this affects are single runner locks which
|
|
799
|
+
use redis for their locking mechanism). In these cases, you should consider
|
|
800
|
+
disabling the batching by setting the `DISABLE_DELAYED_REQUEUE_BATCHES`
|
|
801
|
+
environment variable to `true`.
|
|
802
|
+
|
|
803
|
+
Detecting when this occurs can be tricky, you must watch for logs
|
|
804
|
+
emitted by your `resque-scheduler` process such as:
|
|
805
|
+
|
|
806
|
+
```text
|
|
807
|
+
Redis::CommandError: ERR
|
|
808
|
+
MULTI calls can not be nested
|
|
809
|
+
NoMethodError: undefined method nil? for <Redis::Future
|
|
810
|
+
NoMethodError: undefined method `to_i' for <Redis::Future
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
If you're in this situation, you will also see delayed jobs to not be enqueued
|
|
814
|
+
(they will get dropped). Disabling batching is the workaround.
|
|
758
815
|
|
|
759
816
|
### Contributing
|
|
760
817
|
|
data/lib/resque/scheduler/cli.rb
CHANGED
|
@@ -16,7 +16,9 @@ module Resque
|
|
|
16
16
|
pidfile: 'PIDFILE',
|
|
17
17
|
poll_sleep_amount: 'RESQUE_SCHEDULER_INTERVAL',
|
|
18
18
|
verbose: 'VERBOSE',
|
|
19
|
-
lock_timeout: 'LOCK_TIMEOUT'
|
|
19
|
+
lock_timeout: 'LOCK_TIMEOUT',
|
|
20
|
+
delayed_requeue_batch_size: 'DELAYED_REQUEUE_BATCH_SIZE',
|
|
21
|
+
disable_delayed_requeue_batches: 'DISABLE_DELAYED_REQUEUE_BATCHES'
|
|
20
22
|
}.freeze
|
|
21
23
|
|
|
22
24
|
class Cli
|
|
@@ -74,6 +76,20 @@ module Resque
|
|
|
74
76
|
{
|
|
75
77
|
args: ['-v', '--verbose', 'Run with verbose output [VERBOSE]'],
|
|
76
78
|
callback: ->(options) { ->(v) { options[:verbose] = v } }
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
args: [
|
|
82
|
+
'--delayed-requeue-batch-size [DELAYED_REQUEUE_BATCH_SIZE]',
|
|
83
|
+
'Delayed requeue batch size'
|
|
84
|
+
],
|
|
85
|
+
callback: ->(options) { ->(d) { options[:delayed_requeue_batch_size] = d } }
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
args: [
|
|
89
|
+
'--disable-delayed-requeue-batches',
|
|
90
|
+
'Disable delayed requeue batches [DISABLE_DELAYED_REQUEUE_BATCHES]'
|
|
91
|
+
],
|
|
92
|
+
callback: ->(options) { ->(d) { options[:disable_delayed_requeue_batches] = d } }
|
|
77
93
|
}
|
|
78
94
|
].freeze
|
|
79
95
|
|
|
@@ -65,10 +65,23 @@ module Resque
|
|
|
65
65
|
@app_name ||= environment['APP_NAME']
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
# Batch size for requeueing delayed jobs. If <= 1, this disables batching.
|
|
69
|
+
# Defaults to 100.
|
|
70
|
+
attr_writer :delayed_requeue_batch_size
|
|
71
|
+
|
|
68
72
|
def delayed_requeue_batch_size
|
|
69
73
|
@delayed_requeue_batch_size ||= \
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
Integer(environment.fetch('DELAYED_REQUEUE_BATCH_SIZE', '100'))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
attr_writer :disable_delayed_requeue_batches
|
|
78
|
+
|
|
79
|
+
def disable_delayed_requeue_batches
|
|
80
|
+
if @disable_delayed_requeue_batches.nil?
|
|
81
|
+
to_bool(environment['DISABLE_DELAYED_REQUEUE_BATCHES'])
|
|
82
|
+
else
|
|
83
|
+
@disable_delayed_requeue_batches
|
|
84
|
+
end
|
|
72
85
|
end
|
|
73
86
|
|
|
74
87
|
# Amount of time in seconds to sleep between polls of the delayed
|
|
@@ -10,6 +10,10 @@ module Resque
|
|
|
10
10
|
# for queueing. Until timestamp is in the past, the job will
|
|
11
11
|
# sit in the schedule list.
|
|
12
12
|
def enqueue_at(timestamp, klass, *args)
|
|
13
|
+
if Resque::Scheduler::Util.active_job_class?(klass)
|
|
14
|
+
return enqueue_at_with_queue(nil, timestamp, klass, *args)
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
validate(klass)
|
|
14
18
|
enqueue_at_with_queue(
|
|
15
19
|
queue_from_class(klass), timestamp, klass, *args
|
|
@@ -21,19 +25,29 @@ module Resque
|
|
|
21
25
|
# timestamp has passed. It respects Resque.inline option, by
|
|
22
26
|
# creating the job right away instead of adding to the queue.
|
|
23
27
|
def enqueue_at_with_queue(queue, timestamp, klass, *args)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
if Resque::Scheduler::Util.active_job_class?(klass)
|
|
29
|
+
return klass.set(**{ wait_until: timestamp, queue: queue }.compact)
|
|
30
|
+
.perform_later(*args)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if Resque.inline? || timestamp.to_i <= Time.now.to_i
|
|
34
|
+
# Just create the job and let resque perform it right away with
|
|
35
|
+
# inline. If the class is a custom job class, call self#scheduled
|
|
36
|
+
# on it. This allows you to do things like
|
|
37
|
+
# Resque.enqueue_at(timestamp, CustomJobClass, :opt1 => val1).
|
|
38
|
+
# Otherwise, pass off to Resque.
|
|
39
|
+
#
|
|
40
|
+
# Note: schedule hooks are NOT called here because the job is not
|
|
41
|
+
# actually being added to the delayed queue.
|
|
42
|
+
if klass.respond_to?(:scheduled)
|
|
43
|
+
klass.scheduled(queue, klass.to_s, *args)
|
|
36
44
|
else
|
|
45
|
+
Resque.enqueue_to(queue, klass, *args)
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
# Job is being added to the delayed queue, so we call the
|
|
49
|
+
# before_schedule and after_schedule hooks.
|
|
50
|
+
plugin.process_schedule_hooks(klass, *args) do
|
|
37
51
|
delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
|
|
38
52
|
end
|
|
39
53
|
end
|
|
@@ -83,11 +97,13 @@ module Resque
|
|
|
83
97
|
# insertion time complexity is O(log(n)). Returns true if it's
|
|
84
98
|
# the first job to be scheduled at that time, else false.
|
|
85
99
|
def delayed_push(timestamp, item)
|
|
100
|
+
encoded_job = encode(item)
|
|
101
|
+
|
|
86
102
|
# First add this item to the list for this timestamp
|
|
87
|
-
redis.rpush("delayed:#{timestamp.to_i}",
|
|
103
|
+
redis.rpush("delayed:#{timestamp.to_i}", encoded_job)
|
|
88
104
|
|
|
89
105
|
# Store the timestamps at with this item occurs
|
|
90
|
-
redis.sadd("timestamps:#{
|
|
106
|
+
redis.sadd("timestamps:#{encoded_job}", ["delayed:#{timestamp.to_i}"])
|
|
91
107
|
|
|
92
108
|
# Now, add this timestamp to the zsets. The score and the value are
|
|
93
109
|
# the same since we'll be querying by timestamp, and we don't have
|
|
@@ -171,14 +187,14 @@ module Resque
|
|
|
171
187
|
def enqueue_delayed(klass, *args)
|
|
172
188
|
hash = job_to_hash(klass, args)
|
|
173
189
|
remove_delayed(klass, *args).times do
|
|
174
|
-
Resque::Scheduler.enqueue_from_config(hash)
|
|
190
|
+
Resque::Scheduler.enqueue_from_config(hash, :delayed)
|
|
175
191
|
end
|
|
176
192
|
end
|
|
177
193
|
|
|
178
194
|
def enqueue_delayed_with_queue(klass, queue, *args)
|
|
179
195
|
hash = job_to_hash_with_queue(queue, klass, args)
|
|
180
196
|
remove_delayed_in_queue(klass, queue, *args).times do
|
|
181
|
-
Resque::Scheduler.enqueue_from_config(hash)
|
|
197
|
+
Resque::Scheduler.enqueue_from_config(hash, :delayed)
|
|
182
198
|
end
|
|
183
199
|
end
|
|
184
200
|
|
|
@@ -261,12 +277,18 @@ module Resque
|
|
|
261
277
|
count
|
|
262
278
|
end
|
|
263
279
|
|
|
280
|
+
# Counts all jobs across all delayed timestamps.
|
|
281
|
+
# Uses pipelined LLEN calls in batches to avoid blocking the Redis thread
|
|
282
|
+
# for too long while being significantly faster than sequential calls.
|
|
264
283
|
def count_all_scheduled_jobs
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
284
|
+
timestamps = Array(redis.zrange(:delayed_queue_schedule, 0, -1))
|
|
285
|
+
return 0 if timestamps.empty?
|
|
286
|
+
|
|
287
|
+
timestamps.each_slice(10_000).sum do |batch|
|
|
288
|
+
redis.pipelined do |pipeline|
|
|
289
|
+
batch.each { |ts| pipeline.llen("delayed:#{ts}") }
|
|
290
|
+
end.sum
|
|
268
291
|
end
|
|
269
|
-
total_jobs
|
|
270
292
|
end
|
|
271
293
|
|
|
272
294
|
# Discover if a job has been delayed.
|
data/lib/resque/scheduler/env.rb
CHANGED
|
@@ -30,18 +30,18 @@ module Resque
|
|
|
30
30
|
def setup_backgrounding
|
|
31
31
|
return unless options[:background]
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
# stderr/stdout to /dev/null
|
|
35
|
-
Resque::Scheduler.quiet = if options.key?(:quiet)
|
|
36
|
-
!!options[:quiet]
|
|
37
|
-
else
|
|
38
|
-
true
|
|
39
|
-
end
|
|
33
|
+
Resque::Scheduler.quiet = background_quiet?
|
|
40
34
|
|
|
41
35
|
Process.daemon(true, !Resque::Scheduler.quiet)
|
|
42
36
|
Resque.redis.reconnect
|
|
43
37
|
end
|
|
44
38
|
|
|
39
|
+
def background_quiet?
|
|
40
|
+
return !!options[:quiet] if options.key?(:quiet)
|
|
41
|
+
return false if options.key?(:logfile)
|
|
42
|
+
!!options[:background]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
45
|
def setup_pid_file
|
|
46
46
|
return unless options[:pidfile]
|
|
47
47
|
|
|
@@ -74,6 +74,14 @@ module Resque
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
c.verbose = !!options[:verbose] if options.key?(:verbose)
|
|
77
|
+
|
|
78
|
+
if options.key?(:delayed_requeue_batch_size)
|
|
79
|
+
c.delayed_requeue_batch_size = Integer(options[:delayed_requeue_batch_size])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if options.key?(:disable_delayed_requeue_batches)
|
|
83
|
+
c.disable_delayed_requeue_batches = !!options[:disable_delayed_requeue_batches]
|
|
84
|
+
end
|
|
77
85
|
end
|
|
78
86
|
end
|
|
79
87
|
# rubocop:enable Metrics/AbcSize
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Resque
|
|
4
4
|
module Scheduler
|
|
5
5
|
module Plugin
|
|
6
|
+
ENQUEUE_HOOKS = %i[delayed recurring].freeze
|
|
7
|
+
|
|
6
8
|
def self.process_schedule_hooks(klass, *args)
|
|
7
9
|
# the documentation states that if any before_schedule hook returns
|
|
8
10
|
# false, the process should not be enqueued
|
|
@@ -12,6 +14,32 @@ module Resque
|
|
|
12
14
|
run_after_schedule_hooks(klass, *args)
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
def self.process_recurring_hooks(klass, *args)
|
|
18
|
+
return unless run_before_recurring_hooks(klass, *args)
|
|
19
|
+
|
|
20
|
+
enqueued = yield
|
|
21
|
+
run_after_recurring_hooks(klass, *args) if enqueued
|
|
22
|
+
enqueued
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.validate_enqueue_hooks(hooks)
|
|
26
|
+
return if hooks.nil? || ENQUEUE_HOOKS.include?(hooks)
|
|
27
|
+
|
|
28
|
+
raise ArgumentError, "unknown hooks: #{hooks.inspect}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.process_enqueue_hooks(hooks, klass, *args, &block)
|
|
32
|
+
case hooks
|
|
33
|
+
when :delayed
|
|
34
|
+
run_before_delayed_enqueue_hooks(klass, *args)
|
|
35
|
+
yield
|
|
36
|
+
when :recurring
|
|
37
|
+
process_recurring_hooks(klass, *args, &block)
|
|
38
|
+
else
|
|
39
|
+
yield
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
15
43
|
def self.hooks(job, pattern)
|
|
16
44
|
job.methods.grep(/^#{pattern}/).sort
|
|
17
45
|
end
|
|
@@ -35,6 +63,14 @@ module Resque
|
|
|
35
63
|
def self.run_after_schedule_hooks(klass, *args)
|
|
36
64
|
run_hooks(klass, 'after_schedule', *args)
|
|
37
65
|
end
|
|
66
|
+
|
|
67
|
+
def self.run_before_recurring_hooks(klass, *args)
|
|
68
|
+
run_hooks(klass, 'before_recurring', *args)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.run_after_recurring_hooks(klass, *args)
|
|
72
|
+
run_hooks(klass, 'after_recurring', *args)
|
|
73
|
+
end
|
|
38
74
|
end
|
|
39
75
|
end
|
|
40
76
|
end
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<% jobs = resque.delayed_timestamp_peek(timestamp, start, 20) %>
|
|
13
13
|
<% jobs.each do |job| %>
|
|
14
14
|
<tr>
|
|
15
|
-
<td class='class'><%= job['class'] %></td>
|
|
15
|
+
<td class='class'><%= h job['class'] %></td>
|
|
16
16
|
<td class='args'><%= h show_job_arguments(job['args']) %></td>
|
|
17
17
|
</tr>
|
|
18
18
|
<% end %>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<h1><%= @job_name %></h1>
|
|
1
|
+
<h1><%= h @job_name %></h1>
|
|
2
2
|
|
|
3
3
|
<p class='intro'>
|
|
4
4
|
This job requires parameters:
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
<% value ||= {} %>
|
|
11
11
|
<tr>
|
|
12
12
|
<td>
|
|
13
|
-
<%= key %>
|
|
13
|
+
<%= h key %>
|
|
14
14
|
<% if value['description'] || value[:description] %>
|
|
15
|
-
<span style="border-bottom:1px dotted;" title="<%=value
|
|
15
|
+
<span style="border-bottom:1px dotted;" title="<%= h(value['description'] || value[:description]) %>">(?)</span>
|
|
16
16
|
<% end %>:
|
|
17
17
|
</td>
|
|
18
|
-
<td><input type="text" name="<%= key %>" value="<%= value['default'] || value[:default] %>"></td>
|
|
18
|
+
<td><input type="text" name="<%= h key %>" value="<%= h(value['default'] || value[:default]) %>"></td>
|
|
19
19
|
<% end %>
|
|
20
20
|
</table>
|
|
21
|
-
<input type="hidden" name="job_name" value="<%= @job_name %>">
|
|
21
|
+
<input type="hidden" name="job_name" value="<%= h @job_name %>">
|
|
22
22
|
<input type="submit" value="Queue now">
|
|
23
23
|
</form>
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
The list below contains all scheduled jobs. Click "Queue now" to queue
|
|
5
5
|
a job immediately.
|
|
6
6
|
<br/> Server local time: <%= Time.now %>
|
|
7
|
-
<br/> Server Environment: <%= Resque::Scheduler.env %>
|
|
8
|
-
<br/> Current master: <%= Resque.redis.get(Resque::Scheduler.master_lock.key) %>
|
|
7
|
+
<br/> Server Environment: <%= h Resque::Scheduler.env %>
|
|
8
|
+
<br/> Current master: <%= h Resque.redis.get(Resque::Scheduler.master_lock.key) %>
|
|
9
9
|
</p>
|
|
10
10
|
<p class='intro'>
|
|
11
11
|
The highlighted jobs are skipped for current environment.
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
<td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
|
|
23
23
|
<form action="<%= u "/delayed/cancel_now" %>" method="post">
|
|
24
24
|
<input type="hidden" name="timestamp" value="<%= job['timestamp'].to_i %>">
|
|
25
|
-
<input type="hidden" name="klass" value="<%= job['class'] %>">
|
|
25
|
+
<input type="hidden" name="klass" value="<%= h job['class'] %>">
|
|
26
26
|
<input type="hidden" name="args" value="<%= h(Resque.encode job['args']) %>">
|
|
27
27
|
<input type="submit" value="Cancel Job">
|
|
28
28
|
</form>
|
|
29
29
|
</td>
|
|
30
30
|
<td class='args'><%= format_time(Time.at(job['timestamp'])) %></td>
|
|
31
|
-
<td class='class'><%= job['class'] %></td>
|
|
31
|
+
<td class='class'><%= h job['class'] %></td>
|
|
32
32
|
<td class='args'><%= h job['args'].inspect %></td>
|
|
33
33
|
</tr>
|
|
34
34
|
<% end %>
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
</tr>
|
|
45
45
|
<% queued.each do |job| %>
|
|
46
46
|
<tr>
|
|
47
|
-
<td class='class'><%= job['queue'] %></td>
|
|
48
|
-
<td class='class'><%= job['class'] %></td>
|
|
47
|
+
<td class='class'><%= h job['queue'] %></td>
|
|
48
|
+
<td class='class'><%= h job['class'] %></td>
|
|
49
49
|
<td class='args'><%= h job['args'].inspect %></td>
|
|
50
50
|
</tr>
|
|
51
51
|
<% end %>
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
</tr>
|
|
62
62
|
<% working.each do |job| %>
|
|
63
63
|
<tr>
|
|
64
|
-
<td class='class'><%= job['queue'] %></td>
|
|
65
|
-
<td class='class'><%= job['class'] %></td>
|
|
64
|
+
<td class='class'><%= h job['queue'] %></td>
|
|
65
|
+
<td class='class'><%= h job['class'] %></td>
|
|
66
66
|
<td class='args'><%= h job['args'].inspect %></td>
|
|
67
67
|
</tr>
|
|
68
68
|
<% end %>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<form method="POST" action="<%= u 'delayed/search' %>">
|
|
2
|
+
<input type='checkbox' id='search_args' name='search_args' value="checked" <%= 'checked' if params[:search_args] == 'checked' %>/>
|
|
3
|
+
<label for="search_args"> Search Args?</label><br>
|
|
2
4
|
<input type='input' name='search' value="<%= h params[:search] %>"/>
|
|
3
5
|
<input type='submit' value='Search'/>
|
|
4
6
|
</form>
|
|
@@ -48,7 +48,7 @@ module Resque
|
|
|
48
48
|
if @parameters
|
|
49
49
|
erb scheduler_template('requeue-params')
|
|
50
50
|
else
|
|
51
|
-
Resque::Scheduler.enqueue_from_config(config)
|
|
51
|
+
Resque::Scheduler.enqueue_from_config(config, :recurring)
|
|
52
52
|
redirect u('/overview')
|
|
53
53
|
end
|
|
54
54
|
end
|
|
@@ -68,7 +68,7 @@ module Resque
|
|
|
68
68
|
|
|
69
69
|
# Insert the args hash into config and queue the resque job
|
|
70
70
|
config = config.merge('args' => config_args)
|
|
71
|
-
Resque::Scheduler.enqueue_from_config(config)
|
|
71
|
+
Resque::Scheduler.enqueue_from_config(config, :recurring)
|
|
72
72
|
redirect u('/overview')
|
|
73
73
|
end
|
|
74
74
|
|
|
@@ -99,7 +99,8 @@ module Resque
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def delayed_search
|
|
102
|
-
|
|
102
|
+
field = params[:search_args] == 'checked' ? 'args' : 'class'
|
|
103
|
+
@jobs = find_job(params[:search], field)
|
|
103
104
|
erb scheduler_template('search')
|
|
104
105
|
end
|
|
105
106
|
|
|
@@ -153,21 +154,21 @@ module Resque
|
|
|
153
154
|
)
|
|
154
155
|
end
|
|
155
156
|
|
|
156
|
-
def find_job(
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
def find_job(term, field = 'class')
|
|
158
|
+
term = term.downcase
|
|
159
|
+
matches = ->(job) { job[field].to_s.downcase.include?(term) }
|
|
160
|
+
results = working_jobs_matching(matches)
|
|
159
161
|
|
|
160
|
-
dels = delayed_jobs_for_worker(
|
|
162
|
+
dels = delayed_jobs_for_worker(term)
|
|
161
163
|
results += dels.select do |j|
|
|
162
|
-
|
|
163
|
-
j.merge!('where_at' => 'delayed')
|
|
164
|
+
matches.call(j) && j.merge!('where_at' => 'delayed')
|
|
164
165
|
end
|
|
165
166
|
|
|
166
167
|
Resque.queues.each do |queue|
|
|
167
168
|
queued = Resque.peek(queue, 0, Resque.size(queue))
|
|
168
169
|
queued = [queued] unless queued.is_a?(Array)
|
|
169
170
|
results += queued.select do |j|
|
|
170
|
-
|
|
171
|
+
matches.call(j) &&
|
|
171
172
|
j.merge!('queue' => queue, 'where_at' => 'queued')
|
|
172
173
|
end
|
|
173
174
|
end
|
|
@@ -187,13 +188,13 @@ module Resque
|
|
|
187
188
|
|
|
188
189
|
def schedule_interval_every(every)
|
|
189
190
|
every = [*every]
|
|
190
|
-
s = 'every: ' << every.first
|
|
191
|
+
s = 'every: ' << "#{every.first}#{'s' if every.first.is_a?(Numeric)}"
|
|
191
192
|
|
|
192
193
|
return s unless every.length > 1
|
|
193
194
|
|
|
194
195
|
s << ' ('
|
|
195
196
|
meta = every.last.map do |key, value|
|
|
196
|
-
"#{key.to_s.tr('_', ' ')} #{value}"
|
|
197
|
+
"#{key.to_s.tr('_', ' ')} #{value}#{'s' if value.is_a?(Numeric)}"
|
|
197
198
|
end
|
|
198
199
|
s << meta.join(', ') << ')'
|
|
199
200
|
end
|
|
@@ -228,12 +229,11 @@ module Resque
|
|
|
228
229
|
|
|
229
230
|
private
|
|
230
231
|
|
|
231
|
-
def
|
|
232
|
+
def working_jobs_matching(matches)
|
|
232
233
|
[].tap do |results|
|
|
233
234
|
working = [*Resque.working]
|
|
234
235
|
work = working.select do |w|
|
|
235
|
-
w.job && w.job['payload'] &&
|
|
236
|
-
w.job['payload']['class'].downcase.include?(worker)
|
|
236
|
+
w.job && w.job['payload'] && matches.call(w.job['payload'])
|
|
237
237
|
end
|
|
238
238
|
work.each do |w|
|
|
239
239
|
results += [
|
|
@@ -34,6 +34,11 @@ module Resque
|
|
|
34
34
|
def self.classify(dashed_word)
|
|
35
35
|
dashed_word.split('-').map(&:capitalize).join
|
|
36
36
|
end
|
|
37
|
+
|
|
38
|
+
def self.active_job_class?(klass)
|
|
39
|
+
defined?(::ActiveJob::Base) && klass.is_a?(Class) &&
|
|
40
|
+
klass < ::ActiveJob::Base
|
|
41
|
+
end
|
|
37
42
|
end
|
|
38
43
|
end
|
|
39
44
|
end
|
data/lib/resque/scheduler.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# vim:fileencoding=utf-8
|
|
2
2
|
|
|
3
|
-
require 'redis
|
|
3
|
+
require 'redis'
|
|
4
4
|
require 'rufus/scheduler'
|
|
5
5
|
require_relative 'scheduler/configuration'
|
|
6
6
|
require_relative 'scheduler/locking'
|
|
@@ -8,6 +8,8 @@ require_relative 'scheduler/logger_builder'
|
|
|
8
8
|
require_relative 'scheduler/signal_handling'
|
|
9
9
|
require_relative 'scheduler/failure_handler'
|
|
10
10
|
|
|
11
|
+
require 'resque/scheduler/railtie' if defined?(Rails::Railtie)
|
|
12
|
+
|
|
11
13
|
module Resque
|
|
12
14
|
module Scheduler
|
|
13
15
|
autoload :Cli, 'resque/scheduler/cli'
|
|
@@ -49,6 +51,7 @@ module Resque
|
|
|
49
51
|
$stderr.sync = true
|
|
50
52
|
|
|
51
53
|
was_master = nil
|
|
54
|
+
schedule_loaded = false
|
|
52
55
|
|
|
53
56
|
begin
|
|
54
57
|
@th = Thread.current
|
|
@@ -61,8 +64,14 @@ module Resque
|
|
|
61
64
|
if am_master != was_master
|
|
62
65
|
procline am_master ? 'Master scheduler' : 'Child scheduler'
|
|
63
66
|
|
|
64
|
-
#
|
|
65
|
-
|
|
67
|
+
# The first pass always loads the schedule - load_schedule!
|
|
68
|
+
# reads am_master, so it cannot run before the check above.
|
|
69
|
+
# After that, only a dynamic schedule can have changed while
|
|
70
|
+
# we were not master.
|
|
71
|
+
if dynamic || !schedule_loaded
|
|
72
|
+
reload_schedule!
|
|
73
|
+
schedule_loaded = true
|
|
74
|
+
end
|
|
66
75
|
end
|
|
67
76
|
|
|
68
77
|
if am_master
|
|
@@ -144,12 +153,17 @@ module Resque
|
|
|
144
153
|
interval_defined = false
|
|
145
154
|
interval_types = %w(cron every)
|
|
146
155
|
interval_types.each do |interval_type|
|
|
147
|
-
next unless !config[interval_type].nil? && !config[interval_type].empty?
|
|
156
|
+
next unless !config[interval_type].nil? && !config[interval_type].to_s.empty?
|
|
148
157
|
args = optionizate_interval_value(config[interval_type])
|
|
149
158
|
args = [args, nil, job: true] if args.is_a?(::String)
|
|
150
159
|
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
begin
|
|
161
|
+
job = rufus_scheduler.send(interval_type, *args) do
|
|
162
|
+
enqueue_recurring(name, config)
|
|
163
|
+
end
|
|
164
|
+
rescue ArgumentError => e
|
|
165
|
+
log_error "Invalid #{interval_type} schedule for #{name}: #{e.message} - skipping"
|
|
166
|
+
return
|
|
153
167
|
end
|
|
154
168
|
@scheduled_jobs[name] = job
|
|
155
169
|
interval_defined = true
|
|
@@ -198,31 +212,42 @@ module Resque
|
|
|
198
212
|
|
|
199
213
|
if item
|
|
200
214
|
log "queuing #{item['class']} [delayed]"
|
|
201
|
-
enqueue(item)
|
|
215
|
+
enqueue(item, :delayed)
|
|
202
216
|
end
|
|
203
217
|
|
|
204
218
|
item
|
|
205
219
|
end
|
|
206
220
|
|
|
221
|
+
def batch_delayed_items?
|
|
222
|
+
!disable_delayed_requeue_batches && delayed_requeue_batch_size > 1
|
|
223
|
+
end
|
|
224
|
+
|
|
207
225
|
# Enqueues all delayed jobs for a timestamp
|
|
208
226
|
def enqueue_delayed_items_for_timestamp(timestamp)
|
|
209
227
|
count = 0
|
|
210
|
-
batch_size = delayed_requeue_batch_size
|
|
211
|
-
actual_batch_size = nil
|
|
228
|
+
batch_size = batch_delayed_items? ? delayed_requeue_batch_size : 1
|
|
212
229
|
|
|
213
|
-
|
|
230
|
+
message = "Processing delayed items for timestamp #{timestamp}"
|
|
231
|
+
message += ", in batches of #{batch_size}" if batch_delayed_items?
|
|
232
|
+
log message
|
|
214
233
|
|
|
215
234
|
loop do
|
|
235
|
+
actual_batch_size = 0
|
|
236
|
+
|
|
216
237
|
handle_shutdown do
|
|
217
238
|
# Continually check that it is still the master
|
|
218
239
|
if am_master
|
|
219
|
-
|
|
220
|
-
|
|
240
|
+
if batch_delayed_items?
|
|
241
|
+
actual_batch_size = enqueue_items_in_batch_for_timestamp(timestamp, batch_size)
|
|
242
|
+
log "queued batch of #{actual_batch_size} jobs" if actual_batch_size != -1
|
|
243
|
+
else
|
|
244
|
+
item = enqueue_next_item(timestamp)
|
|
245
|
+
actual_batch_size = item.nil? ? 0 : 1
|
|
246
|
+
end
|
|
221
247
|
end
|
|
222
248
|
end
|
|
223
249
|
|
|
224
250
|
count += actual_batch_size
|
|
225
|
-
log "queued #{count} jobs" if actual_batch_size != -1
|
|
226
251
|
|
|
227
252
|
# continue processing until there are no more items in this
|
|
228
253
|
# timestamp. If we don't have a full batch, this is the last one.
|
|
@@ -231,7 +256,7 @@ module Resque
|
|
|
231
256
|
break if actual_batch_size < batch_size
|
|
232
257
|
end
|
|
233
258
|
|
|
234
|
-
log "finished queueing #{count} total jobs for timestamp #{timestamp}" if count
|
|
259
|
+
log "finished queueing #{count} total jobs for timestamp #{timestamp}" if count > 0
|
|
235
260
|
end
|
|
236
261
|
|
|
237
262
|
def timestamp_key(timestamp)
|
|
@@ -255,7 +280,7 @@ module Resque
|
|
|
255
280
|
pipeline.srem("timestamps:#{encoded_job}", timestamp_bucket_key)
|
|
256
281
|
|
|
257
282
|
decoded_job = Resque.decode(encoded_job)
|
|
258
|
-
enqueue(decoded_job)
|
|
283
|
+
enqueue(decoded_job, :delayed)
|
|
259
284
|
end
|
|
260
285
|
|
|
261
286
|
pipeline.ltrim(timestamp_bucket_key, batch_size, -1)
|
|
@@ -282,8 +307,12 @@ module Resque
|
|
|
282
307
|
encoded_jobs_to_requeue.count
|
|
283
308
|
end
|
|
284
309
|
|
|
285
|
-
|
|
286
|
-
|
|
310
|
+
# Enqueues a job like enqueue_from_config, but hands errors to the failure
|
|
311
|
+
# handler instead of raising them. Pass :delayed as hooks for a job that
|
|
312
|
+
# left the delayed queue or :recurring for a schedule entry to run the
|
|
313
|
+
# matching hooks.
|
|
314
|
+
def enqueue(config, hooks = nil)
|
|
315
|
+
enqueue_from_config(config, hooks)
|
|
287
316
|
rescue => e
|
|
288
317
|
Resque::Scheduler.failure_handler.on_enqueue_failure(config, e)
|
|
289
318
|
end
|
|
@@ -294,8 +323,11 @@ module Resque
|
|
|
294
323
|
exit if @shutdown
|
|
295
324
|
end
|
|
296
325
|
|
|
297
|
-
# Enqueues a job based on a config hash
|
|
298
|
-
|
|
326
|
+
# Enqueues a job based on a config hash. Pass :delayed as hooks for a job
|
|
327
|
+
# that left the delayed queue or :recurring for a schedule entry to run
|
|
328
|
+
# the matching hooks.
|
|
329
|
+
def enqueue_from_config(job_config, hooks = nil)
|
|
330
|
+
Resque::Scheduler::Plugin.validate_enqueue_hooks(hooks)
|
|
299
331
|
args = job_config['args'] || job_config[:args]
|
|
300
332
|
|
|
301
333
|
klass_name = job_config['class'] || job_config[:class]
|
|
@@ -306,9 +338,13 @@ module Resque
|
|
|
306
338
|
end
|
|
307
339
|
|
|
308
340
|
params = args.is_a?(Hash) ? [args] : Array(args)
|
|
309
|
-
queue = job_config['queue'] ||
|
|
310
|
-
|
|
311
|
-
|
|
341
|
+
queue = job_config['queue'] || job_config[:queue]
|
|
342
|
+
|
|
343
|
+
if Resque::Scheduler::Util.active_job_class?(klass)
|
|
344
|
+
return klass.set(**{ queue: queue }.compact).perform_later(*params)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
queue ||= Resque.queue_from_class(klass)
|
|
312
348
|
# Support custom job classes like those that inherit from
|
|
313
349
|
# Resque::JobWithStatus (resque-status)
|
|
314
350
|
job_klass = job_config['custom_job_class']
|
|
@@ -331,8 +367,7 @@ module Resque
|
|
|
331
367
|
# for non-existent classes (for example: running scheduler in
|
|
332
368
|
# one app that schedules for another.
|
|
333
369
|
if Class === klass
|
|
334
|
-
Resque::Scheduler::Plugin.
|
|
335
|
-
Resque::Scheduler::Plugin.run_before_delayed_enqueue_hooks(klass, *params)
|
|
370
|
+
Resque::Scheduler::Plugin.process_enqueue_hooks(hooks, klass, *params) do
|
|
336
371
|
# If the class is a custom job class, call self#scheduled on it.
|
|
337
372
|
# This allows you to do things like Resque.enqueue_at(timestamp,
|
|
338
373
|
# CustomJobClass). Otherwise, pass off to Resque.
|
|
@@ -492,7 +527,7 @@ module Resque
|
|
|
492
527
|
def enqueue_recurring(name, config)
|
|
493
528
|
if am_master
|
|
494
529
|
log! "queueing #{config['class']} (#{name})"
|
|
495
|
-
enqueue(config)
|
|
530
|
+
enqueue(config, :recurring)
|
|
496
531
|
Resque.last_enqueued_at(name, Time.now.to_s)
|
|
497
532
|
end
|
|
498
533
|
end
|
data/resque-scheduler.gemspec
CHANGED
|
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
|
32
32
|
spec.license = 'MIT'
|
|
33
33
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
34
34
|
|
|
35
|
-
spec.required_ruby_version = '>= 3.
|
|
35
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
36
36
|
|
|
37
37
|
spec.files = `git ls-files -z`.split("\0").reject do |f|
|
|
38
38
|
f.match(%r{^(test|spec|features|examples|bin|tasks)/}) ||
|
|
@@ -43,6 +43,7 @@ Gem::Specification.new do |spec|
|
|
|
43
43
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
44
44
|
spec.require_paths = %w(lib)
|
|
45
45
|
|
|
46
|
+
spec.add_development_dependency 'activejob'
|
|
46
47
|
spec.add_development_dependency 'bundler'
|
|
47
48
|
spec.add_development_dependency 'json'
|
|
48
49
|
spec.add_development_dependency 'irb'
|
|
@@ -59,14 +60,12 @@ Gem::Specification.new do |spec|
|
|
|
59
60
|
|
|
60
61
|
# We pin rubocop because new cops have a tendency to result in false-y
|
|
61
62
|
# positives for new contributors, which is not a nice experience.
|
|
62
|
-
spec.add_development_dependency 'rubocop', '~>
|
|
63
|
+
spec.add_development_dependency 'rubocop', '~> 1.91'
|
|
63
64
|
|
|
64
65
|
spec.add_runtime_dependency 'base64', '~> 0.1'
|
|
65
66
|
spec.add_runtime_dependency 'logger'
|
|
66
67
|
spec.add_runtime_dependency 'mono_logger', '~> 1.0'
|
|
67
68
|
spec.add_runtime_dependency 'redis', '>= 4.0'
|
|
68
69
|
spec.add_runtime_dependency 'resque', '>= 3.0'
|
|
69
|
-
|
|
70
|
-
# rufus-scheduler v3.3 is missing a to_local method which fails tests
|
|
71
|
-
spec.add_runtime_dependency 'rufus-scheduler', '~> 3.2', '!= 3.3'
|
|
70
|
+
spec.add_runtime_dependency 'rufus-scheduler', '~> 3.9'
|
|
72
71
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resque-scheduler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben VandenBos
|
|
@@ -10,11 +10,24 @@ authors:
|
|
|
10
10
|
- Dan Buch
|
|
11
11
|
- Michael Bianco
|
|
12
12
|
- Patrick Tulskie
|
|
13
|
-
autorequire:
|
|
14
13
|
bindir: exe
|
|
15
14
|
cert_chain: []
|
|
16
|
-
date:
|
|
15
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
17
16
|
dependencies:
|
|
17
|
+
- !ruby/object:Gem::Dependency
|
|
18
|
+
name: activejob
|
|
19
|
+
requirement: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: '0'
|
|
24
|
+
type: :development
|
|
25
|
+
prerelease: false
|
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
27
|
+
requirements:
|
|
28
|
+
- - ">="
|
|
29
|
+
- !ruby/object:Gem::Version
|
|
30
|
+
version: '0'
|
|
18
31
|
- !ruby/object:Gem::Dependency
|
|
19
32
|
name: bundler
|
|
20
33
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -203,14 +216,14 @@ dependencies:
|
|
|
203
216
|
requirements:
|
|
204
217
|
- - "~>"
|
|
205
218
|
- !ruby/object:Gem::Version
|
|
206
|
-
version: '
|
|
219
|
+
version: '1.91'
|
|
207
220
|
type: :development
|
|
208
221
|
prerelease: false
|
|
209
222
|
version_requirements: !ruby/object:Gem::Requirement
|
|
210
223
|
requirements:
|
|
211
224
|
- - "~>"
|
|
212
225
|
- !ruby/object:Gem::Version
|
|
213
|
-
version: '
|
|
226
|
+
version: '1.91'
|
|
214
227
|
- !ruby/object:Gem::Dependency
|
|
215
228
|
name: base64
|
|
216
229
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -287,20 +300,14 @@ dependencies:
|
|
|
287
300
|
requirements:
|
|
288
301
|
- - "~>"
|
|
289
302
|
- !ruby/object:Gem::Version
|
|
290
|
-
version: '3.
|
|
291
|
-
- - "!="
|
|
292
|
-
- !ruby/object:Gem::Version
|
|
293
|
-
version: '3.3'
|
|
303
|
+
version: '3.9'
|
|
294
304
|
type: :runtime
|
|
295
305
|
prerelease: false
|
|
296
306
|
version_requirements: !ruby/object:Gem::Requirement
|
|
297
307
|
requirements:
|
|
298
308
|
- - "~>"
|
|
299
309
|
- !ruby/object:Gem::Version
|
|
300
|
-
version: '3.
|
|
301
|
-
- - "!="
|
|
302
|
-
- !ruby/object:Gem::Version
|
|
303
|
-
version: '3.3'
|
|
310
|
+
version: '3.9'
|
|
304
311
|
description: |2
|
|
305
312
|
Light weight job scheduling on top of Resque.
|
|
306
313
|
Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
|
|
@@ -322,9 +329,6 @@ files:
|
|
|
322
329
|
- ".devcontainer/devcontainer.json"
|
|
323
330
|
- ".devcontainer/docker-compose.yml"
|
|
324
331
|
- ".github/dependabot.yml"
|
|
325
|
-
- ".github/funding.yml"
|
|
326
|
-
- ".github/workflows/codeql-analysis.yml"
|
|
327
|
-
- ".github/workflows/rubocop.yml"
|
|
328
332
|
- ".github/workflows/ruby.yml"
|
|
329
333
|
- AUTHORS.md
|
|
330
334
|
- CHANGELOG.md
|
|
@@ -350,6 +354,7 @@ files:
|
|
|
350
354
|
- lib/resque/scheduler/locking.rb
|
|
351
355
|
- lib/resque/scheduler/logger_builder.rb
|
|
352
356
|
- lib/resque/scheduler/plugin.rb
|
|
357
|
+
- lib/resque/scheduler/railtie.rb
|
|
353
358
|
- lib/resque/scheduler/scheduling_extensions.rb
|
|
354
359
|
- lib/resque/scheduler/server.rb
|
|
355
360
|
- lib/resque/scheduler/server/views/delayed.erb
|
|
@@ -369,7 +374,6 @@ licenses:
|
|
|
369
374
|
- MIT
|
|
370
375
|
metadata:
|
|
371
376
|
rubygems_mfa_required: 'true'
|
|
372
|
-
post_install_message:
|
|
373
377
|
rdoc_options: []
|
|
374
378
|
require_paths:
|
|
375
379
|
- lib
|
|
@@ -377,15 +381,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
377
381
|
requirements:
|
|
378
382
|
- - ">="
|
|
379
383
|
- !ruby/object:Gem::Version
|
|
380
|
-
version: 3.
|
|
384
|
+
version: 3.2.0
|
|
381
385
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
382
386
|
requirements:
|
|
383
387
|
- - ">="
|
|
384
388
|
- !ruby/object:Gem::Version
|
|
385
389
|
version: '0'
|
|
386
390
|
requirements: []
|
|
387
|
-
rubygems_version:
|
|
388
|
-
signing_key:
|
|
391
|
+
rubygems_version: 4.0.16
|
|
389
392
|
specification_version: 4
|
|
390
393
|
summary: Light weight job scheduling on top of Resque
|
|
391
394
|
test_files: []
|
data/.github/funding.yml
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
name: "CodeQL"
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ master, '*-stable' ]
|
|
6
|
-
pull_request:
|
|
7
|
-
# The branches below must be a subset of the branches above
|
|
8
|
-
branches: [ master ]
|
|
9
|
-
schedule:
|
|
10
|
-
- cron: '44 16 * * 6'
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
analyze:
|
|
14
|
-
name: Analyze
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
permissions:
|
|
17
|
-
actions: read
|
|
18
|
-
contents: read
|
|
19
|
-
security-events: write
|
|
20
|
-
|
|
21
|
-
strategy:
|
|
22
|
-
fail-fast: false
|
|
23
|
-
matrix:
|
|
24
|
-
language: [ 'ruby' ]
|
|
25
|
-
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
|
26
|
-
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
|
27
|
-
|
|
28
|
-
steps:
|
|
29
|
-
- name: Checkout repository
|
|
30
|
-
uses: actions/checkout@v4
|
|
31
|
-
|
|
32
|
-
# Initializes the CodeQL tools for scanning.
|
|
33
|
-
- name: Initialize CodeQL
|
|
34
|
-
uses: github/codeql-action/init@v3
|
|
35
|
-
with:
|
|
36
|
-
languages: ${{ matrix.language }}
|
|
37
|
-
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
38
|
-
# By default, queries listed here will override any specified in a config file.
|
|
39
|
-
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
40
|
-
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
41
|
-
|
|
42
|
-
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
43
|
-
# If this step fails, then you should remove it and run the build manually (see below)
|
|
44
|
-
- name: Autobuild
|
|
45
|
-
uses: github/codeql-action/autobuild@v3
|
|
46
|
-
|
|
47
|
-
# ℹ️ Command-line programs to run using the OS shell.
|
|
48
|
-
# 📚 https://git.io/JvXDl
|
|
49
|
-
|
|
50
|
-
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
51
|
-
# and modify them (or add more) to build your code if your project
|
|
52
|
-
# uses a compiled language
|
|
53
|
-
|
|
54
|
-
#- run: |
|
|
55
|
-
# make bootstrap
|
|
56
|
-
# make release
|
|
57
|
-
|
|
58
|
-
- name: Perform CodeQL Analysis
|
|
59
|
-
uses: github/codeql-action/analyze@v3
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
name: Rubocop
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
rubocop:
|
|
11
|
-
name: Rubocop
|
|
12
|
-
runs-on: ${{ matrix.os }}
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
os: [ubuntu-latest]
|
|
16
|
-
ruby: [
|
|
17
|
-
3.4
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
steps:
|
|
21
|
-
- uses: actions/checkout@v4
|
|
22
|
-
- uses: ruby/setup-ruby@v1
|
|
23
|
-
with:
|
|
24
|
-
ruby-version: ${{ matrix.ruby }}
|
|
25
|
-
bundler-cache: true
|
|
26
|
-
- name: Ruby linter
|
|
27
|
-
run: bundle exec rubocop
|