disqualified 0.2.0 → 0.3.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/README.md +15 -2
- data/lib/disqualified/cli.rb +7 -3
- data/lib/disqualified/logging.rb +16 -2
- data/lib/disqualified/main.rb +8 -20
- data/lib/disqualified/pool.rb +57 -30
- data/lib/disqualified/version.rb +1 -1
- data/lib/generators/disqualified/install/templates/20220703062536_create_disqualified_jobs.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34ac6d57608468421c25cb83e842ce8cadc028b922e4437e39580ba8f8009106
|
4
|
+
data.tar.gz: 8bf4b146c66caaeae5a322c013b9be859436e17940f6747643289f76edb84abc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c5c1472c10779a1f55aa88b946801e3ef7ba725538bda4c415572ca8f103c6df81e709399455cc901b419dec15534714fde445d09503f495d9660c7c4bf7f16
|
7
|
+
data.tar.gz: 6eda9531c46d5002efd09c6784551cd2e1cefc7b912143010a92f97ce87db1d478bbcc05c507d22dcb1272f63d97afca57f9d4a758cd67185e86f4588fb7e876
|
data/README.md
CHANGED
@@ -6,13 +6,26 @@ Since SQLite doesn't have any features like Postgres' `LISTEN`/`NOTIFY`,
|
|
6
6
|
Disqualified resorts to polling the database. This might _disqualify_ it as an
|
7
7
|
option for you, but it works well enough for my workload.
|
8
8
|
|
9
|
-
|
9
|
+
Note that:
|
10
|
+
|
11
|
+
* Disqualified only works with Rails.
|
12
|
+
* Disqualified does not support multiple queues.
|
13
|
+
* Each Disqualified process assumes it's the only process running. Running
|
14
|
+
multiple instances of Disqualified should not hurt, but it is not supported.
|
10
15
|
|
11
16
|
|
12
17
|
## Usage
|
13
18
|
|
14
19
|
Run `bundle exec disqualified --help` for more information on how to run the
|
15
|
-
Disqualified server.
|
20
|
+
Disqualified server. This is what I use in production:
|
21
|
+
|
22
|
+
```
|
23
|
+
env RAILS_ENV=production bundle exec disqualified
|
24
|
+
```
|
25
|
+
|
26
|
+
You can use Disqualified with ActiveJob, or you can use it by itself.
|
27
|
+
The examples below detail how to use it by by itself. See Installation
|
28
|
+
instructions for information on how to set up integration with ActiveJob.
|
16
29
|
|
17
30
|
|
18
31
|
### Defining a job
|
data/lib/disqualified/cli.rb
CHANGED
@@ -31,12 +31,16 @@ class Disqualified::CLI
|
|
31
31
|
logger.info { ' /_/' + "v#{Disqualified::VERSION}".rjust(32, " ") }
|
32
32
|
logger.info { Disqualified.server_options.to_s }
|
33
33
|
|
34
|
-
pool = Disqualified::Pool.new(delay_range:, pool_size:, logger:) do |args|
|
35
|
-
args => {
|
36
|
-
logger.debug { format_log("
|
34
|
+
pool = Disqualified::Pool.new(delay_range:, pool_size:, error_hooks:, logger:) do |args|
|
35
|
+
args => {promise_index:}
|
36
|
+
logger.debug { format_log("Disqualified::CLI#run <block>", "##{promise_index}") }
|
37
37
|
Disqualified::Main.new(error_hooks:, logger:).call
|
38
38
|
end
|
39
39
|
pool.run!
|
40
|
+
rescue Interrupt
|
41
|
+
pool.shutdown
|
42
|
+
puts
|
43
|
+
puts "Gracefully quitting..."
|
40
44
|
end
|
41
45
|
|
42
46
|
private
|
data/lib/disqualified/logging.rb
CHANGED
@@ -1,7 +1,21 @@
|
|
1
1
|
module Disqualified::Logging
|
2
2
|
module_function
|
3
3
|
|
4
|
-
def format_log(
|
5
|
-
|
4
|
+
def format_log(*parts)
|
5
|
+
*extras, message = parts
|
6
|
+
|
7
|
+
if extras.empty?
|
8
|
+
message
|
9
|
+
else
|
10
|
+
"#{extras.map { |x| "[#{x}]" }.join(" ")} #{message}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle_error(error_hooks, error, context)
|
15
|
+
error_hooks.each do |hook|
|
16
|
+
hook.call(error, context)
|
17
|
+
rescue
|
18
|
+
nil
|
19
|
+
end
|
6
20
|
end
|
7
21
|
end
|
data/lib/disqualified/main.rb
CHANGED
@@ -18,7 +18,7 @@ class Disqualified::Main
|
|
18
18
|
.limit(1)
|
19
19
|
.update_all(locked_by: run_id, locked_at: Time.now, updated_at: Time.now, attempts: Arel.sql("attempts + 1"))
|
20
20
|
|
21
|
-
@logger.debug { format_log("
|
21
|
+
@logger.debug { format_log("Disqualified::Main#call", "Runner #{run_id}", "Claimed #{claimed_count}") }
|
22
22
|
|
23
23
|
next if claimed_count == 0
|
24
24
|
|
@@ -30,12 +30,8 @@ class Disqualified::Main
|
|
30
30
|
handler_class = job.handler.constantize
|
31
31
|
arguments = JSON.parse(job.arguments)
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
format_log("[#{run_id}] Running `#{job.handler}' with #{arguments.size} argument(s)")
|
36
|
-
end
|
37
|
-
rescue
|
38
|
-
nil
|
33
|
+
@logger.info do
|
34
|
+
format_log("Disqualified::Main#call", "Runner #{run_id}" "Running `#{job.handler}'")
|
39
35
|
end
|
40
36
|
|
41
37
|
# Run the job
|
@@ -44,20 +40,12 @@ class Disqualified::Main
|
|
44
40
|
|
45
41
|
finish(job)
|
46
42
|
|
47
|
-
|
48
|
-
|
49
|
-
format_log("[#{run_id}] Done")
|
50
|
-
end
|
51
|
-
rescue
|
52
|
-
nil
|
43
|
+
@logger.info do
|
44
|
+
format_log("Disqualified::Main#call", "Runner #{run_id}", "Done")
|
53
45
|
end
|
54
46
|
rescue => e
|
55
|
-
@error_hooks
|
56
|
-
|
57
|
-
rescue
|
58
|
-
nil
|
59
|
-
end
|
60
|
-
@logger.error { format_log("[Disqualified::Main#run] [Runner #{run_id}] Rescued Record ##{job&.id}") }
|
47
|
+
handle_error(@error_hooks, e, {record: job.attributes})
|
48
|
+
@logger.error { format_log("Disqualified::Main#run", "Runner #{run_id}", "Rescued Record ##{job&.id}") }
|
61
49
|
requeue(job)
|
62
50
|
end
|
63
51
|
end
|
@@ -73,7 +61,7 @@ class Disqualified::Main
|
|
73
61
|
# Formula from the Sidekiq wiki
|
74
62
|
retry_count = job.attempts - 1
|
75
63
|
sleep = (retry_count**4) + 15 + (rand(10) * (retry_count + 1))
|
76
|
-
@logger.
|
64
|
+
@logger.debug { format_log("Disqualified::Main#requeue", "Sleeping job for #{sleep} seconds") }
|
77
65
|
job.update!(locked_by: nil, locked_at: nil, run_at: Time.now + sleep)
|
78
66
|
end
|
79
67
|
end
|
data/lib/disqualified/pool.rb
CHANGED
@@ -1,58 +1,91 @@
|
|
1
1
|
class Disqualified::Pool
|
2
2
|
include Disqualified::Logging
|
3
3
|
|
4
|
-
|
4
|
+
CHECK = :check
|
5
|
+
QUIT = :quit
|
6
|
+
RUN = :run
|
7
|
+
|
8
|
+
def initialize(delay_range:, logger:, pool_size:, error_hooks:, &task)
|
5
9
|
@delay_range = delay_range
|
6
10
|
@logger = logger
|
7
11
|
@pool_size = pool_size
|
12
|
+
@error_hooks = error_hooks
|
8
13
|
@task = task
|
9
14
|
@running = Concurrent::AtomicBoolean.new(true)
|
15
|
+
@command_queue = Thread::Queue.new
|
10
16
|
end
|
11
17
|
|
12
18
|
def run!
|
19
|
+
clock.execute
|
13
20
|
Concurrent::Promises
|
14
21
|
.zip(*pool)
|
15
|
-
.rescue { |error| handle_error(error) }
|
22
|
+
.rescue { |error| handle_error(@error_hooks, error, {}) }
|
16
23
|
.run
|
17
24
|
.value!
|
18
25
|
end
|
19
26
|
|
27
|
+
def shutdown
|
28
|
+
@running.make_false
|
29
|
+
clock.shutdown
|
30
|
+
@pool_size.times do
|
31
|
+
@command_queue.push(Disqualified::Pool::QUIT)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def clock
|
36
|
+
@clock ||= Concurrent::TimerTask.new(run_now: true) do |clock_task|
|
37
|
+
@logger.debug { format_log("Disqualified::Pool#clock", "Starting") }
|
38
|
+
clock_task.execution_interval = random_interval
|
39
|
+
@command_queue.push(Disqualified::Pool::CHECK)
|
40
|
+
@logger.debug { format_log("Disqualified::Pool#clock", "Next run in #{clock_task.execution_interval}") }
|
41
|
+
rescue => e
|
42
|
+
handle_error(@error_hooks, e, {})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
20
46
|
def pool
|
21
47
|
@pool ||=
|
22
48
|
@pool_size.times.map do |promise_index|
|
23
|
-
|
24
|
-
Concurrent::Promises
|
25
|
-
.schedule(initial_delay) do
|
26
|
-
repeat(promise_index:, schedule: false, previous_delay: initial_delay)
|
27
|
-
end
|
28
|
-
.rescue { |error| handle_error(error) }
|
49
|
+
repeat(promise_index:)
|
29
50
|
.run
|
30
51
|
end
|
31
52
|
end
|
32
53
|
|
33
|
-
def repeat(promise_index
|
54
|
+
def repeat(promise_index:)
|
34
55
|
if @running.false?
|
35
56
|
return
|
36
57
|
end
|
37
58
|
|
38
|
-
|
39
|
-
if schedule
|
40
|
-
random_interval
|
41
|
-
else
|
42
|
-
0
|
43
|
-
end
|
44
|
-
|
45
|
-
@logger.debug { format_log("[Disqualified::Pool#repeat(#{promise_index})] Interval #{interval}") }
|
59
|
+
@logger.debug { format_log("Disqualified::Pool#repeat(#{promise_index})", "Started") }
|
46
60
|
|
47
|
-
args = {
|
48
|
-
promise_index:,
|
49
|
-
running: @running
|
50
|
-
}
|
61
|
+
args = {promise_index:}
|
51
62
|
|
52
63
|
Concurrent::Promises
|
53
|
-
.
|
54
|
-
|
55
|
-
|
64
|
+
.future(args) do |args|
|
65
|
+
@logger.debug { format_log("Disqualified::Pool#repeat(#{promise_index}) <pre-exec>", "Waiting for command") }
|
66
|
+
command = @command_queue.pop
|
67
|
+
@logger.debug { format_log("Disqualified::Pool#repeat(#{promise_index}) <pre-exec>", "Command: #{command}") }
|
68
|
+
|
69
|
+
case command
|
70
|
+
when Disqualified::Pool::QUIT
|
71
|
+
nil
|
72
|
+
when Disqualified::Pool::CHECK
|
73
|
+
Rails.application.reloader.wrap do
|
74
|
+
pending_job_count = Disqualified::Record
|
75
|
+
.where(finished_at: nil, run_at: (..Time.now), locked_by: nil)
|
76
|
+
.count
|
77
|
+
|
78
|
+
pending_job_count.times do
|
79
|
+
@command_queue.push(Disqualified::Pool::RUN)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
when Disqualified::Pool::RUN
|
83
|
+
@task.call(args)
|
84
|
+
end
|
85
|
+
rescue => e
|
86
|
+
handle_error(@error_hooks, e, {})
|
87
|
+
end
|
88
|
+
.then { repeat(promise_index:) }
|
56
89
|
end
|
57
90
|
|
58
91
|
private
|
@@ -60,10 +93,4 @@ class Disqualified::Pool
|
|
60
93
|
def random_interval
|
61
94
|
rand(@delay_range)
|
62
95
|
end
|
63
|
-
|
64
|
-
def handle_error(error)
|
65
|
-
pp error
|
66
|
-
puts "Gracefully quitting..."
|
67
|
-
@running.make_false
|
68
|
-
end
|
69
96
|
end
|
data/lib/disqualified/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: disqualified
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Ahn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-04-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -115,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
115
|
- !ruby/object:Gem::Version
|
116
116
|
version: '0'
|
117
117
|
requirements: []
|
118
|
-
rubygems_version: 3.
|
118
|
+
rubygems_version: 3.4.7
|
119
119
|
signing_key:
|
120
120
|
specification_version: 4
|
121
121
|
summary: A background job processor tuned for SQLite
|