disqualified 0.1.1 → 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 +56 -2
- data/lib/disqualified/active_job.rb +25 -0
- data/lib/disqualified/cli.rb +8 -4
- data/lib/disqualified/job.rb +1 -1
- data/lib/disqualified/logging.rb +16 -2
- data/lib/disqualified/main.rb +9 -21
- 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 +18 -5
- data/app/assets/config/disqualified_manifest.js +0 -0
- data/lib/tasks/disqualified_tasks.rake +0 -4
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,12 +6,48 @@ 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
|
-
Run `bundle exec disqualified --help`
|
19
|
+
Run `bundle exec disqualified --help` for more information on how to run the
|
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.
|
29
|
+
|
30
|
+
|
31
|
+
### Defining a job
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class ComplicatedJob
|
35
|
+
include Disqualified::Job
|
36
|
+
|
37
|
+
def perform(arg1, arg2)
|
38
|
+
# ...
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
|
44
|
+
### Queuing
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
ComplicatedJob.perform_async(1, 2)
|
48
|
+
ComplicatedJob.perform_in(1.minute, 1, 2)
|
49
|
+
ComplicatedJob.perform_at(3.days.from_now, 1, 2)
|
50
|
+
```
|
15
51
|
|
16
52
|
|
17
53
|
## Installation
|
@@ -25,6 +61,24 @@ bundle binstub disqualified
|
|
25
61
|
```
|
26
62
|
|
27
63
|
|
64
|
+
### ActiveJob
|
65
|
+
|
66
|
+
You can optionally set up Disqualified as ActiveJob's default backend.
|
67
|
+
|
68
|
+
Usually, you'll just need to update your `config/environments/production.rb`
|
69
|
+
file to include something like this.
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
require "disqualified/active_job"
|
73
|
+
|
74
|
+
Rails.application.configure do
|
75
|
+
# ...
|
76
|
+
config.active_job.queue_adapter = :disqualified
|
77
|
+
# ...
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
|
28
82
|
## Contributing
|
29
83
|
|
30
84
|
PRs are welcome! Please confirm the change with me before you start working;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "active_job"
|
2
|
+
require "disqualified"
|
3
|
+
|
4
|
+
class Disqualified::ActiveJobAdapter
|
5
|
+
include Disqualified::Job
|
6
|
+
|
7
|
+
def perform(serialized_job_data)
|
8
|
+
::ActiveJob::Base.execute(serialized_job_data)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ActiveJob
|
13
|
+
module QueueAdapters
|
14
|
+
class DisqualifiedAdapter
|
15
|
+
def enqueue(job_data)
|
16
|
+
Disqualified::ActiveJobAdapter.perform_async(job_data.serialize)
|
17
|
+
end
|
18
|
+
|
19
|
+
def enqueue_at(job_data, timestamp)
|
20
|
+
timestamp = Time.at(timestamp)
|
21
|
+
Disqualified::ActiveJobAdapter.perform_at(timestamp, job_data.serialize)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/disqualified/cli.rb
CHANGED
@@ -29,14 +29,18 @@ class Disqualified::CLI
|
|
29
29
|
logger.info { ' / /_/ / (__ ) /_/ / /_/ / /_/ / / / __/ / __/ /_/ /' }
|
30
30
|
logger.info { '/_____/_/____/\__, /\__,_/\__,_/_/_/_/ /_/\___/\__,_/' }
|
31
31
|
logger.info { ' /_/' + "v#{Disqualified::VERSION}".rjust(32, " ") }
|
32
|
-
logger.info {
|
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/job.rb
CHANGED
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
@@ -9,7 +9,7 @@ class Disqualified::Main
|
|
9
9
|
def call
|
10
10
|
run_id = SecureRandom.uuid
|
11
11
|
|
12
|
-
Rails.application.
|
12
|
+
Rails.application.reloader.wrap do
|
13
13
|
# Claim a job
|
14
14
|
claimed_count =
|
15
15
|
Disqualified::Record
|
@@ -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
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 7.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: concurrent-ruby
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: mocktail
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,12 +76,12 @@ extra_rdoc_files: []
|
|
62
76
|
files:
|
63
77
|
- LICENSE
|
64
78
|
- README.md
|
65
|
-
- app/assets/config/disqualified_manifest.js
|
66
79
|
- app/models/disqualified/base_record.rb
|
67
80
|
- app/models/disqualified/record.rb
|
68
81
|
- config/routes.rb
|
69
82
|
- exe/disqualified
|
70
83
|
- lib/disqualified.rb
|
84
|
+
- lib/disqualified/active_job.rb
|
71
85
|
- lib/disqualified/cli.rb
|
72
86
|
- lib/disqualified/engine.rb
|
73
87
|
- lib/disqualified/job.rb
|
@@ -79,7 +93,6 @@ files:
|
|
79
93
|
- lib/generators/disqualified/install/USAGE
|
80
94
|
- lib/generators/disqualified/install/install_generator.rb
|
81
95
|
- lib/generators/disqualified/install/templates/20220703062536_create_disqualified_jobs.rb
|
82
|
-
- lib/tasks/disqualified_tasks.rake
|
83
96
|
homepage: https://github.com/zachahn/disqualified
|
84
97
|
licenses:
|
85
98
|
- LGPL-3.0-only
|
@@ -102,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
115
|
- !ruby/object:Gem::Version
|
103
116
|
version: '0'
|
104
117
|
requirements: []
|
105
|
-
rubygems_version: 3.
|
118
|
+
rubygems_version: 3.4.7
|
106
119
|
signing_key:
|
107
120
|
specification_version: 4
|
108
121
|
summary: A background job processor tuned for SQLite
|
File without changes
|