que 1.0.0.beta4 → 1.0.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2493b1dd8d4a7042ead34f78d19b83320c65f7eccd08a14336bbcfd8f349766
4
- data.tar.gz: e3142695bd3ba1ad21aee57aeeeee47c260ed15ef86e93c6b942fe9e61745ed3
3
+ metadata.gz: 90ae5c7c65d93f4ea3833d80351c2e0d3ed3f706ba00411937e8aba2137a41d7
4
+ data.tar.gz: 58aba934ee2735522e602dd5777de666c6acbee1cb531d9918ce6f2b75f3b693
5
5
  SHA512:
6
- metadata.gz: 8c2cadc33fc259cacc64cb0bdc7532451e9afbb344959b152d9477c75002ce1581858978ac69ac3a399968fd6f853ff93d41e3ac44edf09f67d8c547f97a92cb
7
- data.tar.gz: 9319db034deccd32abdf97b69e5bcde5fe25d5b6cc424c9b07c26935b4b8c70e083314bf57ca3829f74bbecfdf895b2a92aec9386c17db81adf64127dfc3e4c7
6
+ metadata.gz: 1cbfa5f3c6d13868897ac9f1fcf079aaeaa29c9eadfd231044a484878fbf8e99f4b159a4458690027b181b29c5da3abafcc1c9de2d33b3c487b1fac4a15c7e50
7
+ data.tar.gz: 1ed4b71f51cd72e3968de6ec745e2de592dc021e9d334526630b3011d3f5526388b8562da4bf513066cb68d4dee033043115027ddfc0e9bef5e816ed2c35ede5
@@ -1,18 +1,22 @@
1
- name: Ruby
1
+ name: tests
2
2
 
3
- on: [pull_request]
3
+ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  test:
7
7
  runs-on: ubuntu-latest
8
8
  strategy:
9
9
  matrix:
10
- ruby_version: [2.5.x, 2.6.x]
10
+ ruby_version: [2.5.x, 2.6.x, 2.7.x]
11
11
  gemfile: ["4.2", "5.2", "6.0"]
12
- postgres_version: [9, 10, 11]
12
+ postgres_version: [9, 10, 11, 12]
13
+ exclude:
14
+ - { gemfile: "4.2", ruby_version: "2.7.x" }
13
15
  services:
14
16
  db:
15
17
  image: postgres:${{ matrix.postgres_version }}
18
+ env:
19
+ POSTGRES_HOST_AUTH_METHOD: trust
16
20
  ports: ['5432:5432']
17
21
  options: >-
18
22
  --health-cmd pg_isready
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Que
1
+ # Que ![tests](https://github.com/que-rb/que/workflows/tests/badge.svg)
2
2
 
3
3
  **This README and the rest of the docs on the master branch all refer to Que 1.0, which is currently in beta. If you're using version 0.x, please refer to the docs on [the 0.x branch](https://github.com/que-rb/que/tree/0.x).**
4
4
 
@@ -17,7 +17,7 @@ Additionally, there are the general benefits of storing jobs in Postgres, alongs
17
17
  * **Fewer Dependencies** - If you're already using Postgres (and you probably should be), a separate queue is another moving part that can break.
18
18
  * **Security** - Postgres' support for SSL connections keeps your data safe in transport, for added protection when you're running workers on cloud platforms that you can't completely control.
19
19
 
20
- Que's primary goal is reliability. You should be able to leave your application running indefinitely without worrying about jobs being lost due to a lack of transactional support, or left in limbo due to a crashing process. Que does everything it can to ensure that jobs you queue are performed exactly once (though the occasional repetition of a job can be impossible to avoid - see the docs on [how to write a reliable job](https://github.com/que-rb/que/blob/master/docs/writing_reliable_jobs.md)).
20
+ Que's primary goal is reliability. You should be able to leave your application running indefinitely without worrying about jobs being lost due to a lack of transactional support, or left in limbo due to a crashing process. Que does everything it can to ensure that jobs you queue are performed exactly once (though the occasional repetition of a job can be impossible to avoid - see the docs on [how to write a reliable job](/docs/README.md#writing-reliable-jobs)).
21
21
 
22
22
  Que's secondary goal is performance. The worker process is multithreaded, so that a single process can run many jobs simultaneously.
23
23
 
@@ -114,8 +114,14 @@ You can also add options to run the job after a specific time, or with a specifi
114
114
  ``` ruby
115
115
  ChargeCreditCard.enqueue card.id, user_id: current_user.id, run_at: 1.day.from_now, priority: 5
116
116
  ```
117
+ ## Running the Que Worker
118
+ In order to process jobs, you must start a separate worker process outside of your main server.
117
119
 
118
- Finally, you can work jobs using the included `que` CLI. Try running `que -h` to get a list of runtime options:
120
+ ```
121
+ bundle exec que
122
+ ```
123
+
124
+ Try running `que -h` to get a list of runtime options:
119
125
  ```
120
126
  $ que -h
121
127
  usage: que [options] [file/to/require] ...
@@ -130,7 +136,21 @@ You may need to pass que a file path to require so that it can load your app. Qu
130
136
 
131
137
  If you're using ActiveRecord to dump your database's schema, please [set your schema_format to :sql](http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps) so that Que's table structure is managed correctly. This is a good idea regardless, as the `:ruby` schema format doesn't support many of PostgreSQL's advanced features.
132
138
 
133
- Pre-1.0, the default queue name needed to be configured in order for Que to work out of the box with Rails. In 1.0 the default queue name is now 'default', as Rails expects, but when Rails enqueues some types of jobs it may try to use another queue name that isn't worked by default - in particular, ActionMailer uses a queue named 'mailers' by default, so in your app config you'll also need to set `config.action_mailer.deliver_later_queue_name = 'default'` if you're using ActionMailer.
139
+ Pre-1.0, the default queue name needed to be configured in order for Que to work out of the box with Rails. In 1.0 the default queue name is now 'default', as Rails expects, but when Rails enqueues some types of jobs it may try to use another queue name that isn't worked by default. You can either:
140
+
141
+ * [Configure Rails](https://guides.rubyonrails.org/configuring.html) to send all internal job types to the 'default' queue by adding the following to `config/application.rb`:
142
+ ```ruby
143
+ config.action_mailer.deliver_later_queue_name = :default
144
+ config.action_mailbox.queues.incineration = :default
145
+ config.action_mailbox.queues.routing = :default
146
+ config.active_storage.queues.analysis = :default
147
+ config.active_storage.queues.purge = :default
148
+ ```
149
+
150
+ * [Tell que](/docs#multiple-queues) to work all of these queues (less efficient because it requires polling all of them):
151
+ ```
152
+ que -q default -q mailers -q action_mailbox_incineration -q action_mailbox_routing -q active_storage_analysis -q active_storage_purge
153
+ ```
134
154
 
135
155
  Also, if you would like to integrate Que with Active Job, you can do it by setting the adapter in `config/application.rb` or in a specific environment by setting it in `config/environments/production.rb`, for example:
136
156
  ```ruby
@@ -147,12 +167,17 @@ If you later decide to switch a job from Active Job to Que to have transactional
147
167
 
148
168
  There are a couple ways to do testing. You may want to set `Que::Job.run_synchronously = true`, which will cause JobClass.enqueue to simply execute the job's logic synchronously, as if you'd run JobClass.run(*your_args). Or, you may want to leave it disabled so you can assert on the job state once they are stored in the database.
149
169
 
170
+ ## Documentation
171
+
172
+ **For full documentation, see [here](docs/README.md)**.
173
+
150
174
  ## Related Projects
151
175
 
152
176
  These projects are tested to be compatible with Que 1.x:
153
177
 
154
178
  - [que-web](https://github.com/statianzo/que-web) is a Sinatra-based UI for inspecting your job queue.
155
179
  - [que-scheduler](https://github.com/hlascelles/que-scheduler) lets you schedule tasks using a cron style config file.
180
+ - [que-locks](https://github.com/airhorns/que-locks) lets you lock around job execution for so only one job runs at once for a set of arguments.
156
181
 
157
182
  If you have a project that uses or relates to Que, feel free to submit a PR adding it to the list!
158
183
 
@@ -164,7 +189,6 @@ If you have a project that uses or relates to Que, feel free to submit a PR addi
164
189
 
165
190
  Regarding contributions, one of the project's priorities is to keep Que as simple, lightweight and dependency-free as possible, and pull requests that change too much or wouldn't be useful to the majority of Que's users have a good chance of being rejected. If you're thinking of submitting a pull request that adds a new feature, consider starting a discussion in [que-talk](https://groups.google.com/forum/#!forum/que-talk) first about what it would do and how it would be implemented. If it's a sufficiently large feature, or if most of Que's users wouldn't find it useful, it may be best implemented as a standalone gem, like some of the related projects above.
166
191
 
167
-
168
192
  ### Specs
169
193
 
170
194
  A note on running specs - Que's worker system is multithreaded and therefore prone to race conditions. As such, if you've touched that code, a single spec run passing isn't a guarantee that any changes you've made haven't introduced bugs. One thing I like to do before pushing changes is rerun the specs many times and watching for hangs. You can do this from the command line with something like:
@@ -183,8 +183,8 @@ OUTPUT
183
183
  args.each do |file|
184
184
  begin
185
185
  require file
186
- rescue LoadError
187
- output.puts "Could not load file '#{file}'"
186
+ rescue LoadError => e
187
+ output.puts "Could not load file '#{file}': #{e}"
188
188
  return 1
189
189
  end
190
190
  end
data/docs/README.md CHANGED
@@ -37,9 +37,9 @@ Docs Index
37
37
  * [destroy](#destroy)
38
38
  * [finish](#finish)
39
39
  * [expire](#expire)
40
- * [retry_in](#retry-in)
41
- * [error_count](#error-count)
42
- * [default_resolve_action](#default-resolve-action)
40
+ * [retry_in](#retry_in)
41
+ * [error_count](#error_count)
42
+ * [default_resolve_action](#default_resolve_action)
43
43
  - [Writing Reliable Jobs](#writing-reliable-jobs)
44
44
  * [Timeouts](#timeouts)
45
45
  - [Middleware](#middleware)
@@ -122,7 +122,7 @@ ActiveRecord::Base.transaction do
122
122
  end
123
123
  ```
124
124
 
125
- There are other docs to read if you're using [Sequel](#using-sequel) or [plain Postgres connections](#using-plain-connections) (with no ORM at all) instead of ActiveRecord.
125
+ There are other docs to read if you're using [Sequel](#using-sequel) or [plain Postgres connections](#using-plain-postgres-connections) (with no ORM at all) instead of ActiveRecord.
126
126
 
127
127
  ### Managing the Jobs Table
128
128
 
@@ -463,7 +463,7 @@ Que.enqueue current_user.id, job_class: 'ProcessCreditCard', queue: 'credit_card
463
463
 
464
464
  To ensure safe operation, Que needs to be very careful in how it shuts down. When a Ruby process ends normally, it calls Thread#kill on any threads that are still running - unfortunately, if a thread is in the middle of a transaction when this happens, there is a risk that it will be prematurely commited, resulting in data corruption. See [here](http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html) and [here](http://coderrr.wordpress.com/2011/05/03/beware-of-threadkill-or-your-activerecord-transactions-are-in-danger-of-being-partially-committed/) for more detail on this.
465
465
 
466
- To prevent this, Que will block the worker process from exiting until all jobs it is working have completed normally. Unfortunately, if you have long-running jobs, this may take a very long time (and if something goes wrong with a job's logic, it may never happen). The solution in this case is SIGKILL - luckily, Ruby processes that are killed via SIGKILL will end without using Thread#kill on its running threads. This is safer than exiting normally - when PostgreSQL loses the connection it will simply roll back the open transaction, if any, and unlock the job so it can be retried later by another worker. Be sure to read [Writing Reliable Jobs](#writing-reliable-jobs.md) for information on how to design your jobs to fail safely.
466
+ To prevent this, Que will block the worker process from exiting until all jobs it is working have completed normally. Unfortunately, if you have long-running jobs, this may take a very long time (and if something goes wrong with a job's logic, it may never happen). The solution in this case is SIGKILL - luckily, Ruby processes that are killed via SIGKILL will end without using Thread#kill on its running threads. This is safer than exiting normally - when PostgreSQL loses the connection it will simply roll back the open transaction, if any, and unlock the job so it can be retried later by another worker. Be sure to read [Writing Reliable Jobs](#writing-reliable-jobs) for information on how to design your jobs to fail safely.
467
467
 
468
468
  So, be prepared to use SIGKILL on your Ruby processes if they run for too long. For example, Heroku takes a good approach to this - when Heroku's platform is shutting down a process, it sends SIGTERM, waits ten seconds, then sends SIGKILL if the process still hasn't exited. This is a nice compromise - it will give each of your currently running jobs ten seconds to complete, and any jobs that haven't finished by then will be interrupted and retried later.
469
469
 
@@ -593,7 +593,7 @@ Additionally, including `Que::ActiveJob::JobExtensions` lets you define a run()
593
593
 
594
594
  ## Job Helper Methods
595
595
 
596
- There are a number of instance methods on Que::Job that you can use in your jobs, preferably in transactions. See [Writing Reliable Jobs](/writing_reliable_jobs.md) for more information on where to use these methods.
596
+ There are a number of instance methods on Que::Job that you can use in your jobs, preferably in transactions. See [Writing Reliable Jobs](#writing-reliable-jobs) for more information on where to use these methods.
597
597
 
598
598
  ### destroy
599
599
 
@@ -3,7 +3,7 @@
3
3
  module Que
4
4
  module ActiveRecord
5
5
  class Model < ::ActiveRecord::Base
6
- self.table_name = :que_jobs
6
+ self.table_name = 'public.que_jobs'
7
7
 
8
8
  t = arel_table
9
9
 
@@ -119,6 +119,10 @@ module Que
119
119
  loop { break if next_notification.nil? }
120
120
  end
121
121
 
122
+ def server_version
123
+ wrapped_connection.server_version
124
+ end
125
+
122
126
  def in_transaction?
123
127
  wrapped_connection.transaction_status != ::PG::PQTRANS_IDLE
124
128
  end
@@ -59,10 +59,8 @@ module Que
59
59
 
60
60
  # Relying on the hash's contents being sorted, here.
61
61
  priority_queues.reverse_each do |_, pq|
62
- pq.waiting_count.times do
63
- job = _shift_job(pq.priority)
64
- break if job.nil? # False would mean we're stopping.
65
- pq.push(job)
62
+ pq.populate do
63
+ _shift_job(pq.priority)
66
64
  end
67
65
  end
68
66
 
@@ -75,7 +73,7 @@ module Que
75
73
 
76
74
  def shift(priority = nil)
77
75
  queue = priority_queues.fetch(priority) { raise Error, "not a permitted priority! #{priority}" }
78
- queue.pop
76
+ queue.pop || shift_job(priority)
79
77
  end
80
78
 
81
79
  def shift_job(priority = nil)
@@ -158,6 +156,10 @@ module Que
158
156
  sync { _stopping? }
159
157
  end
160
158
 
159
+ def job_available?(priority)
160
+ (job = @array.first) && job.priority_sufficient?(priority)
161
+ end
162
+
161
163
  private
162
164
 
163
165
  def _buffer_space
@@ -210,15 +212,14 @@ module Que
210
212
  def pop
211
213
  sync do
212
214
  loop do
213
- return false if @stopping
214
-
215
- if item = @items.pop
215
+ if @stopping
216
+ return false
217
+ elsif item = @items.pop
216
218
  return item
219
+ elsif job_buffer.job_available?(priority)
220
+ return false
217
221
  end
218
222
 
219
- job = job_buffer.shift_job(priority)
220
- return job unless job.nil? # False means we're stopping.
221
-
222
223
  @waiting += 1
223
224
  @cv.wait(mutex)
224
225
  @waiting -= 1
@@ -226,18 +227,20 @@ module Que
226
227
  end
227
228
  end
228
229
 
229
- def push(item)
230
+ def stop
230
231
  sync do
231
- Que.assert(waiting_count > 0)
232
- @items << item
233
- @cv.signal
232
+ @stopping = true
233
+ @cv.broadcast
234
234
  end
235
235
  end
236
236
 
237
- def stop
237
+ def populate
238
238
  sync do
239
- @stopping = true
240
- @cv.broadcast
239
+ waiting_count.times do
240
+ job = yield
241
+ break if job.nil? # False would mean we're stopping.
242
+ _push(job)
243
+ end
241
244
  end
242
245
  end
243
246
 
@@ -250,6 +253,12 @@ module Que
250
253
  def sync(&block)
251
254
  mutex.synchronize(&block)
252
255
  end
256
+
257
+ def _push(item)
258
+ Que.assert(waiting_count > 0)
259
+ @items << item
260
+ @cv.signal
261
+ end
253
262
  end
254
263
  end
255
264
  end
data/lib/que/locker.rb CHANGED
@@ -393,10 +393,12 @@ module Que
393
393
  }
394
394
  end
395
395
 
396
+ materalize_cte = connection.server_version >= 12_00_00
397
+
396
398
  jobs =
397
399
  connection.execute \
398
400
  <<-SQL
399
- WITH jobs AS (SELECT * FROM que_jobs WHERE id IN (#{ids.join(', ')}))
401
+ WITH jobs AS #{materalize_cte ? 'MATERIALIZED' : ''} (SELECT * FROM que_jobs WHERE id IN (#{ids.join(', ')}))
400
402
  SELECT * FROM jobs WHERE pg_try_advisory_lock(id)
401
403
  SQL
402
404
 
data/lib/que/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Que
4
- VERSION = '1.0.0.beta4'
4
+ VERSION = '1.0.0.beta5'
5
5
  end
data/lib/que/worker.rb CHANGED
@@ -54,10 +54,17 @@ module Que
54
54
  private
55
55
 
56
56
  def work_loop
57
- # Blocks until a job of the appropriate priority is available. If the
58
- # queue is shutting down this will return nil, which breaks the loop and
57
+ # Blocks until a job of the appropriate priority is available.
58
+ # `fetch_next_metajob` normally returns a job to be processed.
59
+ # If the queue is shutting down it will return false, which breaks the loop and
59
60
  # lets the thread finish.
60
- while metajob = fetch_next_metajob
61
+ while (metajob = fetch_next_metajob) != false
62
+ # If metajob is nil instead of false, we've hit a rare race condition where
63
+ # there was a job in the buffer when the worker code checked, but the job was
64
+ # picked up by the time we got around to shifting it off the buffer.
65
+ # Letting this case go unhandled leads to worker threads exiting pre-maturely, so
66
+ # we check explicitly and continue the loop.
67
+ next if metajob.nil?
61
68
  id = metajob.id
62
69
 
63
70
  Que.internal_log(:worker_received_job, self) { {id: id} }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: que
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta4
4
+ version: 1.0.0.beta5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hanks
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-17 00:00:00.000000000 Z
11
+ date: 2021-12-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -32,7 +32,7 @@ executables:
32
32
  extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
- - ".github/workflows/ruby.yml"
35
+ - ".github/workflows/tests.yml"
36
36
  - ".gitignore"
37
37
  - CHANGELOG.1.0.beta.md
38
38
  - CHANGELOG.md
@@ -99,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
99
99
  - !ruby/object:Gem::Version
100
100
  version: 1.3.1
101
101
  requirements: []
102
- rubygems_version: 3.0.3
102
+ rubygems_version: 3.1.6
103
103
  signing_key:
104
104
  specification_version: 4
105
105
  summary: A PostgreSQL-based Job Queue