que 0.14.3 → 1.0.0.beta
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 +5 -5
- data/.gitignore +2 -0
- data/CHANGELOG.md +108 -14
- data/LICENSE.txt +1 -1
- data/README.md +49 -45
- data/bin/command_line_interface.rb +239 -0
- data/bin/que +8 -82
- data/docs/README.md +2 -0
- data/docs/active_job.md +6 -0
- data/docs/advanced_setup.md +7 -64
- data/docs/command_line_interface.md +45 -0
- data/docs/error_handling.md +65 -18
- data/docs/inspecting_the_queue.md +30 -80
- data/docs/job_helper_methods.md +27 -0
- data/docs/logging.md +3 -22
- data/docs/managing_workers.md +6 -61
- data/docs/middleware.md +15 -0
- data/docs/migrating.md +4 -7
- data/docs/multiple_queues.md +8 -4
- data/docs/shutting_down_safely.md +1 -1
- data/docs/using_plain_connections.md +39 -15
- data/docs/using_sequel.md +5 -3
- data/docs/writing_reliable_jobs.md +15 -24
- data/lib/que.rb +98 -182
- data/lib/que/active_job/extensions.rb +97 -0
- data/lib/que/active_record/connection.rb +51 -0
- data/lib/que/active_record/model.rb +48 -0
- data/lib/que/connection.rb +179 -0
- data/lib/que/connection_pool.rb +78 -0
- data/lib/que/job.rb +107 -156
- data/lib/que/job_cache.rb +240 -0
- data/lib/que/job_methods.rb +168 -0
- data/lib/que/listener.rb +176 -0
- data/lib/que/locker.rb +466 -0
- data/lib/que/metajob.rb +47 -0
- data/lib/que/migrations.rb +24 -17
- data/lib/que/migrations/4/down.sql +48 -0
- data/lib/que/migrations/4/up.sql +265 -0
- data/lib/que/poller.rb +267 -0
- data/lib/que/rails/railtie.rb +14 -0
- data/lib/que/result_queue.rb +35 -0
- data/lib/que/sequel/model.rb +51 -0
- data/lib/que/utils/assertions.rb +62 -0
- data/lib/que/utils/constantization.rb +19 -0
- data/lib/que/utils/error_notification.rb +68 -0
- data/lib/que/utils/freeze.rb +20 -0
- data/lib/que/utils/introspection.rb +50 -0
- data/lib/que/utils/json_serialization.rb +21 -0
- data/lib/que/utils/logging.rb +78 -0
- data/lib/que/utils/middleware.rb +33 -0
- data/lib/que/utils/queue_management.rb +18 -0
- data/lib/que/utils/transactions.rb +34 -0
- data/lib/que/version.rb +1 -1
- data/lib/que/worker.rb +128 -167
- data/que.gemspec +13 -2
- metadata +37 -80
- data/.rspec +0 -2
- data/.travis.yml +0 -64
- data/Gemfile +0 -24
- data/docs/customizing_que.md +0 -200
- data/lib/generators/que/install_generator.rb +0 -24
- data/lib/generators/que/templates/add_que.rb +0 -13
- data/lib/que/adapters/active_record.rb +0 -40
- data/lib/que/adapters/base.rb +0 -133
- data/lib/que/adapters/connection_pool.rb +0 -16
- data/lib/que/adapters/pg.rb +0 -21
- data/lib/que/adapters/pond.rb +0 -16
- data/lib/que/adapters/sequel.rb +0 -20
- data/lib/que/railtie.rb +0 -16
- data/lib/que/rake_tasks.rb +0 -59
- data/lib/que/sql.rb +0 -170
- data/spec/adapters/active_record_spec.rb +0 -175
- data/spec/adapters/connection_pool_spec.rb +0 -22
- data/spec/adapters/pg_spec.rb +0 -41
- data/spec/adapters/pond_spec.rb +0 -22
- data/spec/adapters/sequel_spec.rb +0 -57
- data/spec/gemfiles/Gemfile.current +0 -19
- data/spec/gemfiles/Gemfile.old +0 -19
- data/spec/gemfiles/Gemfile.older +0 -19
- data/spec/gemfiles/Gemfile.oldest +0 -19
- data/spec/spec_helper.rb +0 -129
- data/spec/support/helpers.rb +0 -25
- data/spec/support/jobs.rb +0 -35
- data/spec/support/shared_examples/adapter.rb +0 -42
- data/spec/support/shared_examples/multi_threaded_adapter.rb +0 -46
- data/spec/unit/configuration_spec.rb +0 -31
- data/spec/unit/connection_spec.rb +0 -14
- data/spec/unit/customization_spec.rb +0 -251
- data/spec/unit/enqueue_spec.rb +0 -245
- data/spec/unit/helper_spec.rb +0 -12
- data/spec/unit/logging_spec.rb +0 -101
- data/spec/unit/migrations_spec.rb +0 -84
- data/spec/unit/pool_spec.rb +0 -365
- data/spec/unit/run_spec.rb +0 -14
- data/spec/unit/states_spec.rb +0 -50
- data/spec/unit/stats_spec.rb +0 -46
- data/spec/unit/transaction_spec.rb +0 -36
- data/spec/unit/work_spec.rb +0 -596
- data/spec/unit/worker_spec.rb +0 -167
- data/tasks/benchmark.rb +0 -3
- data/tasks/rspec.rb +0 -14
- data/tasks/safe_shutdown.rb +0 -67
data/lib/que/job.rb
CHANGED
@@ -1,11 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# The class that jobs should generally inherit from.
|
4
|
+
|
3
5
|
module Que
|
4
6
|
class Job
|
5
|
-
|
7
|
+
include JobMethods
|
8
|
+
|
9
|
+
MAXIMUM_TAGS_COUNT = 5
|
10
|
+
MAXIMUM_TAG_LENGTH = 100
|
11
|
+
|
12
|
+
SQL[:insert_job] =
|
13
|
+
%{
|
14
|
+
INSERT INTO public.que_jobs
|
15
|
+
(queue, priority, run_at, job_class, args, data)
|
16
|
+
VALUES
|
17
|
+
(
|
18
|
+
coalesce($1, 'default')::text,
|
19
|
+
coalesce($2, 100)::smallint,
|
20
|
+
coalesce($3, now())::timestamptz,
|
21
|
+
$4::text,
|
22
|
+
coalesce($5, '[]')::jsonb,
|
23
|
+
coalesce($6, '{}')::jsonb
|
24
|
+
)
|
25
|
+
RETURNING *
|
26
|
+
}
|
27
|
+
|
28
|
+
attr_reader :que_attrs
|
29
|
+
attr_accessor :que_error, :que_resolved
|
6
30
|
|
7
31
|
def initialize(attrs)
|
8
|
-
@
|
32
|
+
@que_attrs = attrs
|
33
|
+
Que.internal_log(:job_instantiate, self) { attrs }
|
9
34
|
end
|
10
35
|
|
11
36
|
# Subclasses should define their own run methods, but keep an empty one
|
@@ -13,185 +38,111 @@ module Que
|
|
13
38
|
def run(*args)
|
14
39
|
end
|
15
40
|
|
16
|
-
def _run
|
17
|
-
run(*attrs[:args])
|
18
|
-
destroy unless @destroyed
|
19
|
-
rescue => error
|
20
|
-
@_error = error
|
21
|
-
run_error_notifier = handle_error(error)
|
22
|
-
destroy unless @retried || @destroyed
|
23
|
-
|
24
|
-
if run_error_notifier && Que.error_notifier
|
25
|
-
# Protect the work loop from a failure of the error notifier.
|
26
|
-
Que.error_notifier.call(error, @attrs) rescue nil
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
41
|
private
|
31
42
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
def error_message
|
37
|
-
self.class.send(:error_message, @_error)
|
38
|
-
end
|
39
|
-
|
40
|
-
def handle_error(error)
|
41
|
-
error_count = @attrs[:error_count] += 1
|
42
|
-
retry_interval = self.class.retry_interval || Job.retry_interval
|
43
|
-
wait = retry_interval.respond_to?(:call) ? retry_interval.call(error_count) : retry_interval
|
44
|
-
retry_in(wait)
|
45
|
-
end
|
46
|
-
|
47
|
-
def retry_in(period)
|
48
|
-
Que.execute :set_error, [period, error_message] + @attrs.values_at(:queue, :priority, :run_at, :job_id)
|
49
|
-
@retried = true
|
43
|
+
# Have the job helper methods act on this object.
|
44
|
+
def que_target
|
45
|
+
self
|
50
46
|
end
|
51
47
|
|
52
|
-
def destroy
|
53
|
-
Que.execute :destroy_job, attrs.values_at(:queue, :priority, :run_at, :job_id)
|
54
|
-
@destroyed = true
|
55
|
-
end
|
56
|
-
|
57
|
-
@retry_interval = proc { |count| count ** 4 + 3 }
|
58
|
-
|
59
48
|
class << self
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
49
|
+
# Job class configuration options.
|
50
|
+
attr_accessor \
|
51
|
+
:run_synchronously,
|
52
|
+
:retry_interval,
|
53
|
+
:maximum_retry_count,
|
54
|
+
:queue,
|
55
|
+
:priority,
|
56
|
+
:run_at
|
57
|
+
|
58
|
+
def enqueue(
|
59
|
+
*args,
|
60
|
+
queue: nil,
|
61
|
+
priority: nil,
|
62
|
+
run_at: nil,
|
63
|
+
job_class: nil,
|
64
|
+
tags: nil,
|
65
|
+
**arg_opts
|
66
|
+
)
|
67
|
+
|
68
|
+
args << arg_opts if arg_opts.any?
|
69
|
+
|
70
|
+
if tags
|
71
|
+
if tags.length > MAXIMUM_TAGS_COUNT
|
72
|
+
raise Que::Error, "Can't enqueue a job with more than #{MAXIMUM_TAGS_COUNT} tags! (passed #{tags.length})"
|
73
|
+
end
|
85
74
|
|
86
|
-
|
87
|
-
|
75
|
+
tags.each do |tag|
|
76
|
+
if tag.length > MAXIMUM_TAG_LENGTH
|
77
|
+
raise Que::Error, "Can't enqueue a job with a tag longer than 100 characters! (\"#{tag}\")"
|
78
|
+
end
|
79
|
+
end
|
88
80
|
end
|
89
81
|
|
90
|
-
|
91
|
-
|
82
|
+
attrs = {
|
83
|
+
queue: queue || resolve_que_setting(:queue) || Que.default_queue,
|
84
|
+
priority: priority || resolve_que_setting(:priority),
|
85
|
+
run_at: run_at || resolve_que_setting(:run_at),
|
86
|
+
args: Que.serialize_json(args),
|
87
|
+
data: tags ? Que.serialize_json(tags: tags) : "{}",
|
88
|
+
job_class: \
|
89
|
+
job_class || name ||
|
90
|
+
raise(Error, "Can't enqueue an anonymous subclass of Que::Job"),
|
91
|
+
}
|
92
|
+
|
93
|
+
if attrs[:run_at].nil? && resolve_que_setting(:run_synchronously)
|
94
|
+
attrs[:args] = Que.deserialize_json(attrs[:args])
|
95
|
+
attrs[:data] = Que.deserialize_json(attrs[:data])
|
96
|
+
_run_attrs(attrs)
|
92
97
|
else
|
93
|
-
values =
|
94
|
-
|
98
|
+
values =
|
99
|
+
Que.execute(
|
100
|
+
:insert_job,
|
101
|
+
attrs.values_at(:queue, :priority, :run_at, :job_class, :args, :data),
|
102
|
+
).first
|
103
|
+
|
95
104
|
new(values)
|
96
105
|
end
|
97
106
|
end
|
98
107
|
|
99
|
-
def queue(*args)
|
100
|
-
warn "#{to_s}.queue(*args) is deprecated and will be removed in Que version 1.0.0. Please use #{to_s}.enqueue(*args) instead."
|
101
|
-
enqueue(*args)
|
102
|
-
end
|
103
|
-
|
104
108
|
def run(*args)
|
109
|
+
# Make sure things behave the same as they would have with a round-trip
|
110
|
+
# to the DB.
|
111
|
+
args = Que.deserialize_json(Que.serialize_json(args))
|
112
|
+
|
105
113
|
# Should not fail if there's no DB connection.
|
106
|
-
|
114
|
+
_run_attrs(args: args)
|
107
115
|
end
|
108
116
|
|
109
|
-
def
|
110
|
-
|
111
|
-
# same connection throughout the process of getting a job, working it,
|
112
|
-
# deleting it, and removing the lock.
|
113
|
-
return_value =
|
114
|
-
Que.adapter.checkout do
|
115
|
-
begin
|
116
|
-
if job = Que.execute(:lock_job, [queue]).first
|
117
|
-
# Edge case: It's possible for the lock_job query to have
|
118
|
-
# grabbed a job that's already been worked, if it took its MVCC
|
119
|
-
# snapshot while the job was processing, but didn't attempt the
|
120
|
-
# advisory lock until it was finished. Since we have the lock, a
|
121
|
-
# previous worker would have deleted it by now, so we just
|
122
|
-
# double check that it still exists before working it.
|
123
|
-
|
124
|
-
# Note that there is currently no spec for this behavior, since
|
125
|
-
# I'm not sure how to reliably commit a transaction that deletes
|
126
|
-
# the job in a separate thread between lock_job and check_job.
|
127
|
-
if Que.execute(:check_job, job.values_at(:queue, :priority, :run_at, :job_id)).none?
|
128
|
-
{:event => :job_race_condition}
|
129
|
-
else
|
130
|
-
klass = class_for(job[:job_class])
|
131
|
-
instance = klass.new(job)
|
132
|
-
instance._run
|
133
|
-
if e = instance._error
|
134
|
-
{:event => :job_errored, :job => job, :error => e}
|
135
|
-
else
|
136
|
-
{:event => :job_worked, :job => job}
|
137
|
-
end
|
138
|
-
end
|
139
|
-
else
|
140
|
-
{:event => :job_unavailable}
|
141
|
-
end
|
142
|
-
rescue => error
|
143
|
-
begin
|
144
|
-
if job
|
145
|
-
count = job[:error_count].to_i + 1
|
146
|
-
interval = klass && klass.respond_to?(:retry_interval) && klass.retry_interval || retry_interval
|
147
|
-
delay = interval.respond_to?(:call) ? interval.call(count) : interval
|
148
|
-
message = error_message(error)
|
149
|
-
Que.execute :set_error, [delay, message] + job.values_at(:queue, :priority, :run_at, :job_id)
|
150
|
-
end
|
151
|
-
rescue
|
152
|
-
# If we can't reach the database for some reason, too bad, but
|
153
|
-
# don't let it crash the work loop.
|
154
|
-
end
|
155
|
-
|
156
|
-
if Que.error_notifier
|
157
|
-
# Similarly, protect the work loop from a failure of the error notifier.
|
158
|
-
Que.error_notifier.call(error, job) rescue nil
|
159
|
-
end
|
160
|
-
|
161
|
-
return {:event => :job_errored, :error => error, :job => job}
|
162
|
-
ensure
|
163
|
-
# Clear the advisory lock we took when locking the job. Important
|
164
|
-
# to do this so that they don't pile up in the database. Again, if
|
165
|
-
# we can't reach the database, don't crash the work loop.
|
166
|
-
begin
|
167
|
-
Que.execute "SELECT pg_advisory_unlock($1)", [job[:job_id]] if job
|
168
|
-
rescue
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
Que.adapter.cleanup!
|
117
|
+
def resolve_que_setting(setting, *args)
|
118
|
+
value = send(setting) if respond_to?(setting)
|
174
119
|
|
175
|
-
|
120
|
+
if !value.nil?
|
121
|
+
value.respond_to?(:call) ? value.call(*args) : value
|
122
|
+
else
|
123
|
+
c = superclass
|
124
|
+
if c.respond_to?(:resolve_que_setting)
|
125
|
+
c.resolve_que_setting(setting, *args)
|
126
|
+
end
|
127
|
+
end
|
176
128
|
end
|
177
129
|
|
178
130
|
private
|
179
131
|
|
180
|
-
def
|
181
|
-
|
132
|
+
def _run_attrs(attrs)
|
133
|
+
attrs[:error_count] = 0
|
134
|
+
Que.recursively_freeze(attrs)
|
182
135
|
|
183
|
-
|
184
|
-
|
136
|
+
new(attrs).tap do |job|
|
137
|
+
Que.run_middleware(job) do
|
138
|
+
job._run(reraise_errors: true)
|
139
|
+
end
|
185
140
|
end
|
186
|
-
|
187
|
-
message = message.slice(0, 500)
|
188
|
-
|
189
|
-
([message] + error.backtrace).join("\n")
|
190
|
-
end
|
191
|
-
|
192
|
-
def class_for(string)
|
193
|
-
Que.constantize(string)
|
194
141
|
end
|
195
142
|
end
|
143
|
+
|
144
|
+
# Set up some defaults.
|
145
|
+
self.retry_interval = proc { |count| count ** 4 + 3 }
|
146
|
+
self.maximum_retry_count = 15
|
196
147
|
end
|
197
148
|
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A sized thread-safe queue that holds ordered job sort_keys. Supports blocking
|
4
|
+
# while waiting for a job to become available, only returning jobs over a
|
5
|
+
# minimum priority, and stopping gracefully.
|
6
|
+
|
7
|
+
module Que
|
8
|
+
class JobCache
|
9
|
+
attr_reader :maximum_size, :minimum_size, :priority_queues
|
10
|
+
|
11
|
+
def initialize(
|
12
|
+
maximum_size:,
|
13
|
+
minimum_size:,
|
14
|
+
priorities:
|
15
|
+
)
|
16
|
+
@maximum_size = Que.assert(Integer, maximum_size)
|
17
|
+
Que.assert(maximum_size >= 0) { "maximum_size for a JobCache must be at least zero!" }
|
18
|
+
|
19
|
+
@minimum_size = Que.assert(Integer, minimum_size)
|
20
|
+
Que.assert(minimum_size >= 0) { "minimum_size for a JobCache must be at least zero!" }
|
21
|
+
|
22
|
+
Que.assert(minimum_size <= maximum_size) do
|
23
|
+
"minimum queue size (#{minimum_size}) is " \
|
24
|
+
"greater than the maximum queue size (#{maximum_size})!"
|
25
|
+
end
|
26
|
+
|
27
|
+
@stop = false
|
28
|
+
@array = []
|
29
|
+
@monitor = Monitor.new # TODO: Make this a mutex?
|
30
|
+
|
31
|
+
# Make sure that priority = nil sorts highest.
|
32
|
+
@priority_queues = Hash[
|
33
|
+
priorities.sort_by{|p| p || Float::INFINITY}.map do |p|
|
34
|
+
[p, PriorityQueue.new(priority: p, job_cache: self)]
|
35
|
+
end
|
36
|
+
].freeze
|
37
|
+
end
|
38
|
+
|
39
|
+
def push(*metajobs)
|
40
|
+
Que.internal_log(:job_cache_push, self) do
|
41
|
+
{
|
42
|
+
maximum_size: maximum_size,
|
43
|
+
ids: metajobs.map(&:id),
|
44
|
+
current_queue: @array,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
sync do
|
49
|
+
return metajobs if stopping?
|
50
|
+
|
51
|
+
@array.push(*metajobs).sort!
|
52
|
+
|
53
|
+
# Relying on the hash's contents being sorted, here.
|
54
|
+
priority_queues.reverse_each do |_, pq|
|
55
|
+
pq.waiting_count.times do
|
56
|
+
job = shift_job(pq.priority)
|
57
|
+
break if job.nil?
|
58
|
+
pq.push(job)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# If we passed the maximum queue size, drop the lowest sort keys and
|
63
|
+
# return their ids to be unlocked.
|
64
|
+
overage = -cache_space
|
65
|
+
pop(overage) if overage > 0
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def shift(priority = nil)
|
70
|
+
queue = priority_queues.fetch(priority) { raise Error, "not a permitted priority! #{priority}" }
|
71
|
+
queue.pop
|
72
|
+
end
|
73
|
+
|
74
|
+
def shift_job(priority = nil)
|
75
|
+
sync do
|
76
|
+
if stopping?
|
77
|
+
false
|
78
|
+
elsif (job = @array.first) && job.priority_sufficient?(priority)
|
79
|
+
@array.shift
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def accept?(metajobs)
|
85
|
+
return [] if stopping?
|
86
|
+
|
87
|
+
metajobs.sort!
|
88
|
+
|
89
|
+
sync do
|
90
|
+
start_index = cache_space
|
91
|
+
final_index = metajobs.length - 1
|
92
|
+
|
93
|
+
return metajobs if start_index > final_index
|
94
|
+
index_to_lose = @array.length - 1
|
95
|
+
|
96
|
+
start_index.upto(final_index) do |index|
|
97
|
+
if index_to_lose >= 0 && (metajobs[index] <=> @array[index_to_lose]) < 0
|
98
|
+
return metajobs if index == final_index
|
99
|
+
index_to_lose -= 1
|
100
|
+
else
|
101
|
+
return metajobs.slice(0...index)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
[]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def jobs_needed?
|
110
|
+
minimum_size > size
|
111
|
+
end
|
112
|
+
|
113
|
+
def waiting_count
|
114
|
+
count = 0
|
115
|
+
priority_queues.each_value do |pq|
|
116
|
+
count += pq.waiting_count
|
117
|
+
end
|
118
|
+
count
|
119
|
+
end
|
120
|
+
|
121
|
+
def available_priorities
|
122
|
+
hash = {}
|
123
|
+
lowest_priority = true
|
124
|
+
|
125
|
+
priority_queues.reverse_each do |priority, pq|
|
126
|
+
count = pq.waiting_count
|
127
|
+
|
128
|
+
if lowest_priority
|
129
|
+
count += cache_space
|
130
|
+
lowest_priority = false
|
131
|
+
end
|
132
|
+
|
133
|
+
hash[priority || MAXIMUM_PRIORITY] = count if count > 0
|
134
|
+
end
|
135
|
+
|
136
|
+
hash
|
137
|
+
end
|
138
|
+
|
139
|
+
def cache_space
|
140
|
+
sync do
|
141
|
+
maximum_size - size
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def size
|
146
|
+
sync { @array.size }
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_a
|
150
|
+
sync { @array.dup }
|
151
|
+
end
|
152
|
+
|
153
|
+
def stop
|
154
|
+
sync { @stop = true }
|
155
|
+
priority_queues.each_value(&:stop)
|
156
|
+
end
|
157
|
+
|
158
|
+
def clear
|
159
|
+
sync { pop(size) }
|
160
|
+
end
|
161
|
+
|
162
|
+
def stopping?
|
163
|
+
sync { !!@stop }
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def pop(count)
|
169
|
+
@array.pop(count)
|
170
|
+
end
|
171
|
+
|
172
|
+
def sync
|
173
|
+
@monitor.synchronize { yield }
|
174
|
+
end
|
175
|
+
|
176
|
+
# A queue object dedicated to a specific worker priority. It's basically a
|
177
|
+
# Queue object from the standard library, but it's able to reach into the
|
178
|
+
# JobCache's cache in order to satisfy a pop.
|
179
|
+
class PriorityQueue
|
180
|
+
attr_reader :job_cache, :priority
|
181
|
+
|
182
|
+
def initialize(
|
183
|
+
job_cache:,
|
184
|
+
priority:
|
185
|
+
)
|
186
|
+
@job_cache = job_cache
|
187
|
+
@priority = priority
|
188
|
+
@waiting = 0
|
189
|
+
@stopping = false
|
190
|
+
@items = [] # Items pending distribution to waiting threads.
|
191
|
+
@monitor = Monitor.new
|
192
|
+
@cv = Monitor::ConditionVariable.new(@monitor)
|
193
|
+
end
|
194
|
+
|
195
|
+
def pop
|
196
|
+
sync do
|
197
|
+
loop do
|
198
|
+
return false if @stopping
|
199
|
+
|
200
|
+
if item = @items.pop
|
201
|
+
return item
|
202
|
+
end
|
203
|
+
|
204
|
+
job = job_cache.shift_job(priority)
|
205
|
+
return job unless job.nil? # False means we're stopping.
|
206
|
+
|
207
|
+
@waiting += 1
|
208
|
+
@cv.wait
|
209
|
+
@waiting -= 1
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def push(item)
|
215
|
+
sync do
|
216
|
+
Que.assert(waiting_count > 0)
|
217
|
+
@items << item
|
218
|
+
@cv.signal
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def stop
|
223
|
+
sync do
|
224
|
+
@stopping = true
|
225
|
+
@cv.broadcast
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def waiting_count
|
230
|
+
@waiting
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
def sync
|
236
|
+
@monitor.synchronize { yield }
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|