cron-table 0.2 → 0.3
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/README.md +25 -1
- data/app/services/cron_table/server.rb +66 -49
- data/db/migrate/20230723000000_create_cron_table.rb +1 -1
- data/lib/cron-table/context.rb +4 -0
- data/lib/cron-table/definition.rb +7 -2
- data/lib/cron-table/engine.rb +13 -3
- data/lib/cron-table/middlewares.rb +10 -0
- data/lib/cron-table/schedule.rb +15 -0
- data/lib/cron-table/version.rb +1 -1
- data/lib/cron-table.rb +8 -0
- data/lib/tasks/cron_table_tasks.rake +1 -2
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d3679f2b53e9c33edb30d8eaf43c20fbacb038113f1b5e86c1d495f3a312429
|
4
|
+
data.tar.gz: fffbfde8b3008a9c29de5de2466098f3e5e6116c0b54d9527d1f30784a977da9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba77e1aadaf57e1035407aba41c9e3ed1d88358d047a04fe2fe9536b907cae1a37dc105560cfe41aa0c6cbb5b32a11e672f34633a23d741428a4d24b52143114
|
7
|
+
data.tar.gz: a36b8ec47e3a1fd8b3fed8b20282ffa522b5b0feb3f287456bac9796a507822b19ee9e43f68d79ccf56573e05ecbe9adc730bfd87a3571954b574532f9f5e60d
|
data/README.md
CHANGED
@@ -9,7 +9,31 @@ Basic cron-like system to schedule jobs
|
|
9
9
|
## Usage
|
10
10
|
1. Add `include CronTable::Schedule` to the job
|
11
11
|
2. Define cron schedule using `crontable(every: <interval>)`
|
12
|
-
3. Use block if cron requires params, eg `crontable(every: 1.day) { perform_later(Time.
|
12
|
+
3. Use block if cron requires params, eg `crontable(every: 1.day) { perform_later(Time.current) }`
|
13
|
+
|
14
|
+
## Interval specification
|
15
|
+
Use `every` to specify when cron should run. Allowed values include:
|
16
|
+
1. Positive `ActiveSupport::Duration` like `1.hour`, `2.days`
|
17
|
+
2. Predefined intervals
|
18
|
+
- `midnight`
|
19
|
+
- `noon`
|
20
|
+
- `beginning_of_hour`
|
21
|
+
3. Custom interval registered in `CronTable.every`, eg
|
22
|
+
```
|
23
|
+
# config/initializers/cron_table.rb
|
24
|
+
CronTable.every[:custom] = ->(context) { rand(1.hour..1.day).from_now }
|
25
|
+
```
|
26
|
+
|
27
|
+
## Middlewares
|
28
|
+
Cron execution can be instrumented using middleware, eg
|
29
|
+
```
|
30
|
+
module CronTableInstrumentation
|
31
|
+
def process(context)
|
32
|
+
ElasticAPM.with_transaction(context.cron.key, "cron") { super }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
CronTable.register(CronTableInstrumentation)
|
36
|
+
```
|
13
37
|
|
14
38
|
## License
|
15
39
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -1,67 +1,84 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
module CronTable
|
2
|
+
class Server
|
3
|
+
SLEEP = 1.second..1.day
|
4
|
+
SLEEP_IDLE = 1.hour
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
def initialize
|
7
|
+
@middlewares = Middlewares.new
|
8
|
+
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def sync!
|
11
|
+
crons = CronTable.all.clone
|
12
|
+
deleted = []
|
13
|
+
Item.transaction do
|
14
|
+
Item.lock.all.each do |cron|
|
15
|
+
if definition = crons.delete(cron.key)
|
16
|
+
context = Context.new(last_run_at: nil)
|
17
|
+
cron.update(next_run_at: definition.next_run_at(context)) if cron.next_run_at.nil?
|
18
|
+
elsif cron.next_run_at.present?
|
19
|
+
deleted << cron.key
|
20
|
+
end
|
21
|
+
end
|
22
|
+
Item.where(key: deleted).update(next_run_at: nil)
|
23
|
+
crons.each do |key, definition|
|
24
|
+
context = Context.new(last_run_at: nil)
|
25
|
+
Item.create(key: key, next_run_at: definition.next_run_at(context))
|
17
26
|
end
|
18
|
-
end
|
19
|
-
CronTable::Item.where(key: deleted).update(next_run_at: nil)
|
20
|
-
crons.each do |key, definition|
|
21
|
-
CronTable::Item.create(key: key, next_run_at: definition.next_run_at(Time.now))
|
22
27
|
end
|
23
28
|
end
|
24
|
-
end
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
def run!
|
31
|
+
Item.connection_pool.with_connection do
|
32
|
+
sync!
|
33
|
+
end
|
30
34
|
|
31
|
-
|
32
|
-
|
35
|
+
@exit = false
|
36
|
+
|
37
|
+
run_once! until exit?
|
33
38
|
end
|
34
|
-
end
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
+
def exit!
|
41
|
+
@exit = true
|
42
|
+
interrupt!
|
43
|
+
end
|
40
44
|
|
41
|
-
|
42
|
-
|
43
|
-
|
45
|
+
def exit?
|
46
|
+
@exit.present?
|
47
|
+
end
|
44
48
|
|
45
|
-
|
49
|
+
private
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
def run_once!
|
52
|
+
next_run_at = Item.connection_pool.with_connection do
|
53
|
+
Item.lock.where(next_run_at: ..Time.now).each do |cron|
|
54
|
+
process(cron)
|
55
|
+
end
|
56
|
+
Item.minimum(:next_run_at) || SLEEP_IDLE.from_now
|
57
|
+
end
|
58
|
+
interruptible_sleep(next_run_at.to_f - Time.now.to_f)
|
59
|
+
end
|
51
60
|
|
52
|
-
|
61
|
+
def process(cron)
|
62
|
+
Rails.application.reloader.wrap do
|
63
|
+
definition = CronTable.all.fetch(cron.key)
|
64
|
+
context = Context.new(last_run_at: cron.next_run_at, cron: definition)
|
65
|
+
@middlewares.process(context) do
|
66
|
+
definition.call
|
67
|
+
end
|
68
|
+
cron.update(last_run_at: Time.now, next_run_at: definition.next_run_at(context))
|
69
|
+
end
|
70
|
+
rescue => e
|
71
|
+
cron.update(next_run_at: nil)
|
72
|
+
Rails.error.report(e, handled: true, severity: :error, context: { cron: cron.id })
|
53
73
|
end
|
54
|
-
rescue => e
|
55
|
-
cron.update(next_run_at: nil)
|
56
|
-
Rails.error.report(e, handled: true, severity: :error, context: { cron: cron.id })
|
57
|
-
end
|
58
74
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
75
|
+
def interruptible_sleep(seconds)
|
76
|
+
@sleep, @interrupt = IO.pipe
|
77
|
+
IO.select([@sleep], nil, nil, seconds.to_i.clamp(SLEEP))
|
78
|
+
end
|
63
79
|
|
64
|
-
|
65
|
-
|
80
|
+
def interrupt!
|
81
|
+
@interrupt&.close
|
82
|
+
end
|
66
83
|
end
|
67
84
|
end
|
@@ -2,8 +2,13 @@ module CronTable
|
|
2
2
|
class Definition < Struct.new(:key, :every, :block, keyword_init: true)
|
3
3
|
delegate :call, to: :block
|
4
4
|
|
5
|
-
def next_run_at(
|
6
|
-
|
5
|
+
def next_run_at(context)
|
6
|
+
case every
|
7
|
+
when ActiveSupport::Duration
|
8
|
+
(context.last_run_at || Time.current) + every
|
9
|
+
when Symbol
|
10
|
+
CronTable.every.fetch(every).call(context)
|
11
|
+
end
|
7
12
|
end
|
8
13
|
end
|
9
14
|
end
|
data/lib/cron-table/engine.rb
CHANGED
@@ -2,11 +2,21 @@ module CronTable
|
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
isolate_namespace CronTable
|
4
4
|
|
5
|
+
config.before_initialize do
|
6
|
+
CronTable.every[:midnight] = ->(context) {
|
7
|
+
(context.last_run_at || Time.current).since(1.day).midnight
|
8
|
+
}
|
9
|
+
CronTable.every[:noon] = ->(context) {
|
10
|
+
(context.last_run_at&.since(1.day) || 12.hours.from_now).noon
|
11
|
+
}
|
12
|
+
CronTable.every[:beginning_of_hour] = ->(context) {
|
13
|
+
(context.last_run_at || Time.current).since(1.hour).beginning_of_hour
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
5
17
|
server do
|
6
18
|
Thread.new do
|
7
|
-
|
8
|
-
cron.sync!
|
9
|
-
cron.run until true
|
19
|
+
CronTable::Server.new.run!
|
10
20
|
end if CronTable.attach_to_server
|
11
21
|
end
|
12
22
|
end
|
data/lib/cron-table/schedule.rb
CHANGED
@@ -14,6 +14,14 @@ module CronTable
|
|
14
14
|
def message = "Provide a block to `crontable(..) { }` with code to execute"
|
15
15
|
end
|
16
16
|
|
17
|
+
class InvalidEvery < ArgumentError
|
18
|
+
def initialize(every)
|
19
|
+
@every = every
|
20
|
+
end
|
21
|
+
|
22
|
+
def message = "Invalid every: `#{@every.inspect}`"
|
23
|
+
end
|
24
|
+
|
17
25
|
extend ActiveSupport::Concern
|
18
26
|
|
19
27
|
included do |_base|
|
@@ -27,6 +35,13 @@ module CronTable
|
|
27
35
|
block ||= -> { self.perform_later } if self.respond_to?(:perform_later)
|
28
36
|
raise MissingBlockError if block.nil?
|
29
37
|
|
38
|
+
case every
|
39
|
+
when ActiveSupport::Duration
|
40
|
+
when *CronTable.every.keys
|
41
|
+
else
|
42
|
+
raise InvalidEvery.new(every)
|
43
|
+
end
|
44
|
+
|
30
45
|
CronTable.all[key] = Definition.new(key:, every:, block:)
|
31
46
|
|
32
47
|
self
|
data/lib/cron-table/version.rb
CHANGED
data/lib/cron-table.rb
CHANGED
@@ -2,12 +2,16 @@ require "cron-table/version"
|
|
2
2
|
require "cron-table/engine"
|
3
3
|
require "cron-table/definition"
|
4
4
|
require "cron-table/schedule"
|
5
|
+
require "cron-table/context"
|
6
|
+
require "cron-table/middlewares"
|
5
7
|
|
6
8
|
module CronTable
|
7
9
|
mattr_accessor :attach_to_server, default: Rails.env.production?
|
8
10
|
|
9
11
|
mattr_accessor :preload_dirs, default: ["app/jobs"]
|
10
12
|
|
13
|
+
mattr_accessor :every, default: {}
|
14
|
+
|
11
15
|
@@all = nil
|
12
16
|
def self.all
|
13
17
|
if @@all.nil?
|
@@ -20,4 +24,8 @@ module CronTable
|
|
20
24
|
|
21
25
|
@@all
|
22
26
|
end
|
27
|
+
|
28
|
+
def self.register(middleware)
|
29
|
+
Middlewares.include(middleware)
|
30
|
+
end
|
23
31
|
end
|
@@ -2,11 +2,10 @@ namespace :cron_table do
|
|
2
2
|
desc "Run cron_table scheduler"
|
3
3
|
task run: :environment do
|
4
4
|
cron = CronTable::Server.new
|
5
|
-
cron.sync!
|
6
5
|
|
7
6
|
Signal.trap("INT") { cron.exit! }
|
8
7
|
Signal.trap("TERM") { cron.exit! }
|
9
8
|
|
10
|
-
cron.run!
|
9
|
+
cron.run!
|
11
10
|
end
|
12
11
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cron-table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- twratajczak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rufo
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,8 +70,10 @@ files:
|
|
70
70
|
- config/routes.rb
|
71
71
|
- db/migrate/20230723000000_create_cron_table.rb
|
72
72
|
- lib/cron-table.rb
|
73
|
+
- lib/cron-table/context.rb
|
73
74
|
- lib/cron-table/definition.rb
|
74
75
|
- lib/cron-table/engine.rb
|
76
|
+
- lib/cron-table/middlewares.rb
|
75
77
|
- lib/cron-table/schedule.rb
|
76
78
|
- lib/cron-table/version.rb
|
77
79
|
- lib/tasks/cron_table_tasks.rake
|