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