que 1.0.0.beta3 → 1.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
@@ -17,7 +17,7 @@ module Que
17
17
 
18
18
  # Use Rails' executor (if present) to make sure that the connection
19
19
  # we're using isn't taken from us while the block runs. See
20
- # https://github.com/chanks/que/issues/166#issuecomment-274218910
20
+ # https://github.com/que-rb/que/issues/166#issuecomment-274218910
21
21
  def wrap_in_rails_executor(&block)
22
22
  if defined?(::Rails.application.executor)
23
23
  ::Rails.application.executor.wrap(&block)
@@ -152,7 +152,13 @@ module Que
152
152
  },
153
153
 
154
154
  # Timestamp with time zone
155
- 1184 => Time.method(:parse),
155
+ 1184 => -> (value) {
156
+ case value
157
+ when Time then value
158
+ when String then Time.parse(value)
159
+ else raise "Unexpected time class: #{value.class} (#{value.inspect})"
160
+ end
161
+ }
156
162
  }
157
163
 
158
164
  # JSON, JSONB
@@ -107,10 +107,6 @@ module Que
107
107
  end
108
108
  end
109
109
 
110
- def jobs_needed?
111
- minimum_size > size
112
- end
113
-
114
110
  def waiting_count
115
111
  count = 0
116
112
  priority_queues.each_value do |pq|
data/lib/que/locker.rb CHANGED
@@ -301,12 +301,15 @@ module Que
301
301
 
302
302
  def poll
303
303
  # Only poll when there are pollers to use (that is, when polling is
304
- # enabled) and when the local queue has dropped below the configured
305
- # minimum size.
306
- return unless pollers && job_buffer.jobs_needed?
304
+ # enabled).
305
+ return unless pollers
307
306
 
308
307
  # Figure out what job priorities we have to fill.
309
308
  priorities = job_buffer.available_priorities
309
+
310
+ # Only poll when there are workers ready for jobs.
311
+ return if priorities.empty?
312
+
310
313
  all_metajobs = []
311
314
 
312
315
  pollers.each do |poller|
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ::Sequel.extension :pg_json_ops
4
+
3
5
  module Que
4
6
  module Sequel
5
7
  QUALIFIED_TABLE = ::Sequel.qualify(:public, :que_jobs)
@@ -7,10 +9,10 @@ module Que
7
9
  class Model < ::Sequel::Model(QUALIFIED_TABLE)
8
10
  dataset_module do
9
11
  conditions = {
10
- errored: ::Sequel.qualify(QUALIFIED_TABLE, :error_count) > 0,
11
- expired: ::Sequel.~(::Sequel.qualify(QUALIFIED_TABLE, :expired_at) => nil),
12
- finished: ::Sequel.~(::Sequel.qualify(QUALIFIED_TABLE, :finished_at) => nil),
13
- scheduled: ::Sequel.qualify(QUALIFIED_TABLE, :run_at) > ::Sequel::CURRENT_TIMESTAMP,
12
+ errored: QUALIFIED_TABLE[:error_count] > 0,
13
+ expired: QUALIFIED_TABLE[:expired_at] !~ nil,
14
+ finished: QUALIFIED_TABLE[:finished_at] !~ nil,
15
+ scheduled: QUALIFIED_TABLE[:run_at] > ::Sequel::CURRENT_TIMESTAMP,
14
16
  }
15
17
 
16
18
  conditions.each do |name, condition|
@@ -18,32 +20,28 @@ module Que
18
20
  subset :"not_#{name}", ~condition
19
21
  end
20
22
 
21
- subset :ready, conditions.values.map(&:~).inject{|a, b| a & b}
22
- subset :not_ready, conditions.values. inject{|a, b| a | b}
23
+ subset :ready, conditions.values.map(&:~).inject(:&)
24
+ subset :not_ready, conditions.values. inject(:|)
23
25
 
24
26
  def by_job_class(job_class)
25
27
  job_class = job_class.name if job_class.is_a?(Class)
26
28
  where(
27
- ::Sequel.|(
28
- {::Sequel.qualify(QUALIFIED_TABLE, :job_class) => job_class},
29
- {
30
- ::Sequel.qualify(QUALIFIED_TABLE, :job_class) => "ActiveJob::QueueAdapters::QueAdapter::JobWrapper",
31
- ::Sequel.lit("public.que_jobs.args->0->>'job_class'") => job_class,
32
- }
33
- )
29
+ (QUALIFIED_TABLE[:job_class] =~ job_class) |
30
+ (QUALIFIED_TABLE[:job_class] =~ "ActiveJob::QueueAdapters::QueAdapter::JobWrapper") &
31
+ (QUALIFIED_TABLE[:args].pg_jsonb[0].get_text("job_class") =~ job_class)
34
32
  )
35
33
  end
36
34
 
37
35
  def by_queue(queue)
38
- where(::Sequel.qualify(QUALIFIED_TABLE, :queue) => queue)
36
+ where(QUALIFIED_TABLE[:queue] => queue)
39
37
  end
40
38
 
41
39
  def by_tag(tag)
42
- where(::Sequel.lit("public.que_jobs.data @> ?", JSON.dump(tags: [tag])))
40
+ where(QUALIFIED_TABLE[:data].pg_jsonb.contains(JSON.dump(tags: [tag])))
43
41
  end
44
42
 
45
43
  def by_args(*args)
46
- where(::Sequel.lit("public.que_jobs.args @> ?", JSON.dump(args)))
44
+ where(QUALIFIED_TABLE[:args].pg_jsonb.contains(JSON.dump(args)))
47
45
  end
48
46
  end
49
47
  end
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.beta3'
4
+ VERSION = '1.0.0.beta4'
5
5
  end
data/que.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ['christopher.m.hanks@gmail.com']
11
11
  spec.description = %q{A job queue that uses PostgreSQL's advisory locks for speed and reliability.}
12
12
  spec.summary = %q{A PostgreSQL-based Job Queue}
13
- spec.homepage = 'https://github.com/chanks/que'
13
+ spec.homepage = 'https://github.com/que-rb/que'
14
14
  spec.license = 'MIT'
15
15
 
16
16
  files_to_exclude = [
@@ -29,5 +29,5 @@ Gem::Specification.new do |spec|
29
29
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
30
30
  spec.require_paths = ['lib']
31
31
 
32
- spec.add_development_dependency 'bundler', '~> 1.3'
32
+ spec.add_development_dependency 'bundler'
33
33
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: que
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta3
4
+ version: 1.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hanks
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-18 00:00:00.000000000 Z
11
+ date: 2020-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.3'
26
+ version: '0'
27
27
  description: A job queue that uses PostgreSQL's advisory locks for speed and reliability.
28
28
  email:
29
29
  - christopher.m.hanks@gmail.com
@@ -32,6 +32,7 @@ executables:
32
32
  extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
+ - ".github/workflows/ruby.yml"
35
36
  - ".gitignore"
36
37
  - CHANGELOG.1.0.beta.md
37
38
  - CHANGELOG.md
@@ -41,21 +42,6 @@ files:
41
42
  - bin/command_line_interface.rb
42
43
  - bin/que
43
44
  - docs/README.md
44
- - docs/active_job.md
45
- - docs/advanced_setup.md
46
- - docs/command_line_interface.md
47
- - docs/error_handling.md
48
- - docs/inspecting_the_queue.md
49
- - docs/job_helper_methods.md
50
- - docs/logging.md
51
- - docs/managing_workers.md
52
- - docs/middleware.md
53
- - docs/migrating.md
54
- - docs/multiple_queues.md
55
- - docs/shutting_down_safely.md
56
- - docs/using_plain_connections.md
57
- - docs/using_sequel.md
58
- - docs/writing_reliable_jobs.md
59
45
  - lib/que.rb
60
46
  - lib/que/active_job/extensions.rb
61
47
  - lib/que/active_record/connection.rb
@@ -94,7 +80,7 @@ files:
94
80
  - lib/que/version.rb
95
81
  - lib/que/worker.rb
96
82
  - que.gemspec
97
- homepage: https://github.com/chanks/que
83
+ homepage: https://github.com/que-rb/que
98
84
  licenses:
99
85
  - MIT
100
86
  metadata: {}
@@ -113,8 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
99
  - !ruby/object:Gem::Version
114
100
  version: 1.3.1
115
101
  requirements: []
116
- rubyforge_project:
117
- rubygems_version: 2.7.3
102
+ rubygems_version: 3.0.3
118
103
  signing_key:
119
104
  specification_version: 4
120
105
  summary: A PostgreSQL-based Job Queue
data/docs/active_job.md DELETED
@@ -1,6 +0,0 @@
1
- ## Using Que With ActiveJob
2
-
3
- You can include `Que::ActiveJob::JobExtensions` into your `ApplicationJob` subclass to get support for all of Que's
4
- [helper methods](/docs/job_helper_methods.md). These methods will become no-ops if you use a queue adapter that isn't Que, so if you like to use a different adapter in development they shouldn't interfere.
5
-
6
- Additionally, including `Que::ActiveJob::JobExtensions` lets you define a run() method that supports keyword arguments.
@@ -1,49 +0,0 @@
1
- ## Advanced Setup
2
-
3
- ### Using ActiveRecord Without Rails
4
-
5
- If you're using both Rails and ActiveRecord, the README describes how to get started with Que (which is pretty straightforward, since it includes a Railtie that handles a lot of setup for you). Otherwise, you'll need to do some manual setup.
6
-
7
- If you're using ActiveRecord outside of Rails, you'll need to tell Que to piggyback on its connection pool after you've connected to the database:
8
-
9
- ```ruby
10
- ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
11
-
12
- require 'que'
13
- Que.connection = ActiveRecord
14
- ```
15
-
16
- Then you can queue jobs just as you would in Rails:
17
-
18
- ```ruby
19
- ActiveRecord::Base.transaction do
20
- @user = User.create(params[:user])
21
- SendRegistrationEmail.enqueue user_id: @user.id
22
- end
23
- ```
24
-
25
- There are other docs to read if you're using [Sequel](https://github.com/chanks/que/blob/master/docs/using_sequel.md) or [plain Postgres connections](https://github.com/chanks/que/blob/master/docs/using_plain_connections.md) (with no ORM at all) instead of ActiveRecord.
26
-
27
- ### Managing the Jobs Table
28
-
29
- After you've connected Que to the database, you can manage the jobs table. You'll want to migrate to a specific version in a migration file, to ensure that they work the same way even when you upgrade Que in the future:
30
-
31
- ```ruby
32
- # Update the schema to version #4.
33
- Que.migrate! version: 4
34
-
35
- # Remove Que's jobs table entirely.
36
- Que.migrate! version: 0
37
- ```
38
-
39
- There's also a helper method to clear all jobs from the jobs table:
40
-
41
- ```ruby
42
- Que.clear!
43
- ```
44
-
45
- ### Other Setup
46
-
47
- Be sure to read the docs on [managing workers](https://github.com/chanks/que/blob/master/docs/managing_workers.md) for more information on using the worker pool.
48
-
49
- You'll also want to set up [logging](https://github.com/chanks/que/blob/master/docs/logging.md) and an [error handler](https://github.com/chanks/que/blob/master/docs/error_handling.md) to track errors raised by jobs.
@@ -1,49 +0,0 @@
1
- ## Command Line Interface
2
-
3
- ```
4
- usage: que [options] [file/to/require] ...
5
- -h, --help Show this help text.
6
- -i, --poll-interval [INTERVAL] Set maximum interval between polls for available jobs, in seconds (default: 5)
7
- -l, --log-level [LEVEL] Set level at which to log to STDOUT (debug, info, warn, error, fatal) (default: info)
8
- -p, --worker-priorities [LIST] List of priorities to assign to workers (default: 10,30,50,any,any,any)
9
- -q, --queue-name [NAME] Set a queue name to work jobs from. Can be passed multiple times. (default: the default queue only)
10
- -w, --worker-count [COUNT] Set number of workers in process (default: 6)
11
- -v, --version Print Que version and exit.
12
- --connection-url [URL] Set a custom database url to connect to for locking purposes.
13
- --log-internals Log verbosely about Que's internal state. Only recommended for debugging issues
14
- --maximum-buffer-size [SIZE] Set maximum number of jobs to be locked and held in this process awaiting a worker (default: 8)
15
- --minimum-buffer-size [SIZE] Set minimum number of jobs to be locked and held in this process awaiting a worker (default: 2)
16
- --wait-period [PERIOD] Set maximum interval between checks of the in-memory job queue, in milliseconds (default: 50)
17
- ```
18
-
19
- Some explanation of the more unusual options:
20
-
21
- ### worker-priorities and worker-count
22
-
23
- These options dictate the size and priority distribution of the worker pool. The default worker-priorities is `10,30,50,any,any,any`. This means that the default worker pool will reserve one worker to only works jobs with priorities under 10, one for priorities under 30, and one for priorities under 50. Three more workers will work any job.
24
-
25
- For example, with these defaults, you could have a large backlog of jobs of priority 100. When a more important job (priority 40) comes in, there's guaranteed to be a free worker. If the process then becomes saturated with jobs of priority 40, and then a priority 20 job comes in, there's guaranteed to be a free worker for it, and so on. You can pass a priority more than once to have multiple workers at that level (for example: `--worker-priorities=100,100,any,any`). This gives you a lot of freedom to manage your worker capacity at different priority levels.
26
-
27
- Instead of passing worker-priorities, you can pass a `worker-count` - this is a shorthand for creating the given number of workers at the `any` priority level. So, `--worker-count=3` is just like passing equivalent to `worker-priorities=any,any,any`.
28
-
29
- If you pass both worker-count and worker-priorities, the count will trim or pad the priorities list with `any` workers. So, `--worker-priorities=20,30,40 --worker-count=6` would be the same as passing `--worker-priorities=20,30,40,any,any,any`.
30
-
31
- ### poll-interval
32
-
33
- This option sets the number of seconds the process will wait between polls of the job queue. Jobs that are ready to be worked immediately will be broadcast via the LISTEN/NOTIFY system, so polling is unnecessary for them - polling is only necessary for jobs that are scheduled in the future or which are being delayed due to errors. The default is 5 seconds.
34
-
35
- ### minimum-buffer-size and maximum-buffer-size
36
-
37
- These options set the size of the internal buffer that Que uses to hold jobs until they're ready for workers. The default minimum is 2 and the maximum is 8, meaning that the process won't buffer more than 8 jobs that aren't yet ready to be worked, and will only resort to polling if the buffer dips below 2. If you don't want jobs to be buffered at all, you can set both of these values to zero.
38
-
39
- ### connection-url
40
-
41
- This option sets the URL to be used to open a connection to the database for locking purposes. By default, Que will simply use a connection from the connection pool for locking - this option is only useful if your application connections can't use advisory locks - for example, if they're passed through an external connection pool like PgBouncer. In that case, you'll need to use this option to specify your actual database URL so that Que can establish a direct connection.
42
-
43
- ### wait-period
44
-
45
- This option specifies (in milliseconds) how often the locking thread wakes up to check whether the workers have finished jobs, whether it's time to poll, etc. You shouldn't generally need to tweak this, but it may come in handy for some workloads. The default is 50 milliseconds.
46
-
47
- ### log-internals
48
-
49
- This option instructs Que to output a lot of information about its internal state to the logger. It should only be used if it becomes necessary to debug issues.
@@ -1,94 +0,0 @@
1
- ## Error Handling
2
-
3
- If an error is raised and left uncaught by your job, Que will save the error message and backtrace to the database and schedule the job to be retried later.
4
-
5
- If a given job fails repeatedly, Que will retry it at exponentially-increasing intervals equal to (failure_count^4 + 3) seconds. This means that a job will be retried 4 seconds after its first failure, 19 seconds after its second, 84 seconds after its third, 259 seconds after its fourth, and so on until it succeeds. This pattern is very similar to DelayedJob's. Alternately, you can define your own retry logic by setting an interval to delay each time, or a callable that accepts the number of failures and returns an interval:
6
-
7
- ```ruby
8
- class MyJob < Que::Job
9
- # Just retry a failed job every 5 seconds:
10
- self.retry_interval = 5
11
-
12
- # Always retry this job immediately (not recommended, or transient
13
- # errors will spam your error reporting):
14
- self.retry_interval = 0
15
-
16
- # Increase the delay by 30 seconds every time this job fails:
17
- self.retry_interval = proc { |count| count * 30 }
18
- end
19
- ```
20
-
21
- There is a maximum_retry_count option for jobs. It defaults to 15 retries, which with the default retry interval means that a job will stop retrying after a little more than two days.
22
-
23
- ## Error Notifications
24
-
25
- If you're using an error notification system (highly recommended, of course), you can hook Que into it by setting a callable as the error notifier:
26
-
27
- ```ruby
28
- Que.error_notifier = proc do |error, job|
29
- # Do whatever you want with the error object or job row here. Note that the
30
- # job passed is not the actual job object, but the hash representing the job
31
- # row in the database, which looks like:
32
-
33
- # {
34
- # :priority => 100,
35
- # :run_at => "2017-09-15T20:18:52.018101Z",
36
- # :id => 172340879,
37
- # :job_class => "TestJob",
38
- # :error_count => 0,
39
- # :last_error_message => nil,
40
- # :queue => "default",
41
- # :last_error_backtrace => nil,
42
- # :finished_at => nil,
43
- # :expired_at => nil,
44
- # :args => [],
45
- # :data => {}
46
- # }
47
-
48
- # This is done because the job may not have been able to be deserialized
49
- # properly, if the name of the job class was changed or the job class isn't
50
- # loaded for some reason. The job argument may also be nil, if there was a
51
- # connection failure or something similar.
52
- end
53
- ```
54
-
55
- ## Error-Specific Handling
56
-
57
- You can also define a handle_error method in your job, like so:
58
-
59
- ```ruby
60
- class MyJob < Que::Job
61
- def run(*args)
62
- # Your code goes here.
63
- end
64
-
65
- def handle_error(error)
66
- case error
67
- when TemporaryError then retry_in 10.seconds
68
- when PermanentError then expire
69
- else super # Default (exponential backoff) behavior.
70
- end
71
- end
72
- end
73
- ```
74
-
75
- The return value of handle_error determines whether the error object is passed to the error notifier. The helper methods like expire and retry_in return true, so these errors will be notified. You can explicitly return false to skip notification.
76
-
77
- ```ruby
78
- class MyJob < Que::Job
79
- def handle_error(error)
80
- case error
81
- when AnnoyingError
82
- retry_in 10.seconds
83
- false
84
- when TransientError
85
- super
86
- error_count > 3
87
- else
88
- super # Default (exponential backoff) behavior.
89
- end
90
- end
91
- end
92
- ```
93
-
94
- In this example, AnnoyingError will never be notified, while TransientError will only be notified once it has affected a given job at least three times.
@@ -1,64 +0,0 @@
1
- ## Inspecting the Queue
2
-
3
- In order to remain simple and compatible with any ORM (or no ORM at all), Que is really just a very thin wrapper around some raw SQL. There are two methods available that query the jobs table and Postgres' system catalogs to retrieve information on the current state of the queue:
4
-
5
- ### Job Stats
6
-
7
- You can call `Que.job_stats` to return some aggregate data on the types of jobs currently in the queue. Example output:
8
-
9
- ```ruby
10
- [
11
- {
12
- :job_class=>"ChargeCreditCard",
13
- :count=>10,
14
- :count_working=>4,
15
- :count_errored=>2,
16
- :highest_error_count=>5,
17
- :oldest_run_at=>2017-09-08 16:13:18 -0400
18
- },
19
- {
20
- :job_class=>"SendRegistrationEmail",
21
- :count=>1,
22
- :count_working=>0,
23
- :count_errored=>0,
24
- :highest_error_count=>0,
25
- :oldest_run_at=>2017-09-08 17:13:18 -0400
26
- }
27
- ]
28
- ```
29
-
30
- This tells you that, for instance, there are ten ChargeCreditCard jobs in the queue, four of which are currently being worked, and two of which have experienced errors. One of them has started to process but experienced an error five times. The oldest_run_at is helpful for determining how long jobs have been sitting around, if you have a large backlog.
31
-
32
- ### Custom Queries
33
-
34
- If you're using ActiveRecord or Sequel, Que ships with models that wrap the job queue so you can write your own logic to inspect it. They include some helpful scopes to write your queries - see the gem source for a complete accounting.
35
-
36
- #### ActiveRecord Example
37
-
38
- ``` ruby
39
- # app/models/que_job.rb
40
-
41
- require 'que/active_record/model'
42
-
43
- class QueJob < Que::ActiveRecord::Model
44
- end
45
-
46
- QueJob.finished.to_sql # => "SELECT \"que_jobs\".* FROM \"que_jobs\" WHERE (\"que_jobs\".\"finished_at\" IS NOT NULL)"
47
-
48
- # You could also name the model whatever you like, or just query from
49
- # Que::ActiveRecord::Model directly if you don't need to write your own model
50
- # logic.
51
- ```
52
-
53
- #### Sequel Example
54
-
55
- ``` ruby
56
- # app/models/que_job.rb
57
-
58
- require 'que/sequel/model'
59
-
60
- class QueJob < Que::Sequel::Model
61
- end
62
-
63
- QueJob.finished # => #<Sequel::Postgres::Dataset: "SELECT * FROM \"public\".\"que_jobs\" WHERE (\"public\".\"que_jobs\".\"finished_at\" IS NOT NULL)">
64
- ```