que 0.14.3 → 1.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- 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
|