solid_queue 1.1.2 → 1.1.3

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: 17a05d42c432fb64bc429ef13665309e61fa0221d0c7e7534e1ae688772a8ed4
4
- data.tar.gz: 7173644a027051dfcd4911b0611995bac6686c9a78cd104836c5b3fbccf6d9dc
3
+ metadata.gz: 0bb548d389cca819bac62ce03ff23b44dd9956c95d1f410cdad9b8ae36ea3e68
4
+ data.tar.gz: 48b6f46f1e093450cc700a259a80b3275cc0c8d8f60108fa1e3b7f0673bfa58a
5
5
  SHA512:
6
- metadata.gz: fb0400861a8946176b1ed8d63cc5504743696ec456c637b459c59489897fcce388e4defba72ecaf061e70362965792474037aef4bcdee48bb071d9f22002611e
7
- data.tar.gz: f7b4f080b7b20b716950ff80ce5eff7bb1cc68c153bb65e2ef797044222dfcf52a5717e9a640817a946a54713ca164fb20ed7acbbc90bfec1effe57015c3d872
6
+ metadata.gz: 80aac013693e8d6f9b18d3f9ea6c4b7aea456dba1512adda885af0e158cbe23c241bfde2c2c00601b2cb7cf694b6e68f4fa9723bdd386bd6fe337eb3084f6efc
7
+ data.tar.gz: d6d26580b30c0ae2bf415849fc4ae678e0c1796abde0c647e8587a8b8408ca61b3de8ad12b5c712f4eeb038d2cb436c242335f233da8a086256e4ae3d20d2ab4
data/README.md CHANGED
@@ -9,6 +9,7 @@ Solid Queue can be used with SQL databases such as MySQL, PostgreSQL or SQLite,
9
9
  ## Table of contents
10
10
 
11
11
  - [Installation](#installation)
12
+ - [Usage in development and other non-production environments](#usage-in-development-and-other-non-production-environments)
12
13
  - [Single database configuration](#single-database-configuration)
13
14
  - [Incremental adoption](#incremental-adoption)
14
15
  - [High performance requirements](#high-performance-requirements)
@@ -38,6 +39,8 @@ Solid Queue is configured by default in new Rails 8 applications. But if you're
38
39
  1. `bundle add solid_queue`
39
40
  2. `bin/rails solid_queue:install`
40
41
 
42
+ (Note: The minimum supported version of Rails is 7.1 and Ruby is 3.1.6.)
43
+
41
44
  This will configure Solid Queue as the production Active Job backend, create the configuration files `config/queue.yml` and `config/recurring.yml`, and create the `db/queue_schema.rb`. It'll also create a `bin/jobs` executable wrapper that you can use to start Solid Queue.
42
45
 
43
46
  Once you've done that, you will then have to add the configuration for the queue database in `config/database.yml`. If you're using SQLite, it'll look like this:
@@ -84,7 +87,7 @@ For example, if you're using SQLite in development, update `database.yml` as fol
84
87
 
85
88
  ```diff
86
89
  development:
87
- primary:
90
+ + primary:
88
91
  <<: *default
89
92
  database: storage/development.sqlite3
90
93
  + queue:
@@ -372,9 +375,9 @@ In Solid queue, you can hook into two different points in the supervisor's life:
372
375
  - `start`: after the supervisor has finished booting and right before it forks workers and dispatchers.
373
376
  - `stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown.
374
377
 
375
- And into two different points in a worker's life:
376
- - `worker_start`: after the worker has finished booting and right before it starts the polling loop.
377
- - `worker_stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown (which is just `exit!`).
378
+ And into two different points in the worker's, dispatcher's and scheduler's life:
379
+ - `(worker|dispatcher|scheduler)_start`: after the worker/dispatcher/scheduler has finished booting and right before it starts the polling loop or loading the recurring schedule.
380
+ - `(worker|dispatcher|scheduler)_stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown (which is just `exit!`).
378
381
 
379
382
  You can use the following methods with a block to do this:
380
383
  ```ruby
@@ -383,6 +386,12 @@ SolidQueue.on_stop
383
386
 
384
387
  SolidQueue.on_worker_start
385
388
  SolidQueue.on_worker_stop
389
+
390
+ SolidQueue.on_dispatcher_start
391
+ SolidQueue.on_dispatcher_stop
392
+
393
+ SolidQueue.on_scheduler_start
394
+ SolidQueue.on_scheduler_stop
386
395
  ```
387
396
 
388
397
  For example:
@@ -14,7 +14,7 @@ module SolidQueue
14
14
  def dispatch_next_batch(batch_size)
15
15
  transaction do
16
16
  job_ids = next_batch(batch_size).non_blocking_lock.pluck(:job_id)
17
- if job_ids.empty? then []
17
+ if job_ids.empty? then 0
18
18
  else
19
19
  SolidQueue.instrument(:dispatch_scheduled, batch_size: batch_size) do |payload|
20
20
  payload[:size] = dispatch_jobs(job_ids)
@@ -141,7 +141,7 @@ module SolidQueue
141
141
 
142
142
  def recurring_tasks
143
143
  @recurring_tasks ||= recurring_tasks_config.map do |id, options|
144
- RecurringTask.from_configuration(id, **options) if options.has_key?(:schedule)
144
+ RecurringTask.from_configuration(id, **options) if options&.has_key?(:schedule)
145
145
  end.compact
146
146
  end
147
147
 
@@ -153,7 +153,9 @@ module SolidQueue
153
153
  end
154
154
 
155
155
  def recurring_tasks_config
156
- @recurring_tasks_config ||= config_from options[:recurring_schedule_file]
156
+ @recurring_tasks_config ||= begin
157
+ config_from options[:recurring_schedule_file]
158
+ end
157
159
  end
158
160
 
159
161
 
@@ -2,10 +2,13 @@
2
2
 
3
3
  module SolidQueue
4
4
  class Dispatcher < Processes::Poller
5
+ include LifecycleHooks
5
6
  attr_accessor :batch_size, :concurrency_maintenance
6
7
 
8
+ after_boot :run_start_hooks
7
9
  after_boot :start_concurrency_maintenance
8
10
  before_shutdown :stop_concurrency_maintenance
11
+ after_shutdown :run_stop_hooks
9
12
 
10
13
  def initialize(**options)
11
14
  options = options.dup.with_defaults(SolidQueue::Configuration::DISPATCHER_DEFAULTS)
@@ -25,7 +28,7 @@ module SolidQueue
25
28
  def poll
26
29
  batch = dispatch_next_batch
27
30
 
28
- batch.size.zero? ? polling_interval : 0.seconds
31
+ batch.zero? ? polling_interval : 0.seconds
29
32
  end
30
33
 
31
34
  def dispatch_next_batch
@@ -38,20 +41,12 @@ module SolidQueue
38
41
  concurrency_maintenance&.start
39
42
  end
40
43
 
41
- def schedule_recurring_tasks
42
- recurring_schedule.schedule_tasks
43
- end
44
-
45
44
  def stop_concurrency_maintenance
46
45
  concurrency_maintenance&.stop
47
46
  end
48
47
 
49
- def unschedule_recurring_tasks
50
- recurring_schedule.unschedule_tasks
51
- end
52
-
53
48
  def all_work_completed?
54
- SolidQueue::ScheduledExecution.none? && recurring_schedule.empty?
49
+ SolidQueue::ScheduledExecution.none?
55
50
  end
56
51
 
57
52
  def set_procline
@@ -37,5 +37,13 @@ module SolidQueue
37
37
  include ActiveJob::ConcurrencyControls
38
38
  end
39
39
  end
40
+
41
+ initializer "solid_queue.include_interruptible_concern" do
42
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2")
43
+ SolidQueue::Processes::Base.include SolidQueue::Processes::Interruptible
44
+ else
45
+ SolidQueue::Processes::Base.include SolidQueue::Processes::OgInterruptible
46
+ end
47
+ end
40
48
  end
41
49
  end
@@ -18,20 +18,16 @@ module SolidQueue
18
18
  def post(execution)
19
19
  available_threads.decrement
20
20
 
21
- future = Concurrent::Future.new(args: [ execution ], executor: executor) do |thread_execution|
21
+ Concurrent::Promises.future_on(executor, execution) do |thread_execution|
22
22
  wrap_in_app_executor do
23
23
  thread_execution.perform
24
24
  ensure
25
25
  available_threads.increment
26
26
  mutex.synchronize { on_idle.try(:call) if idle? }
27
27
  end
28
+ end.on_rejection! do |e|
29
+ handle_thread_error(e)
28
30
  end
29
-
30
- future.add_observer do |_, _, error|
31
- handle_thread_error(error) if error
32
- end
33
-
34
- future.execute
35
31
  end
36
32
 
37
33
  def idle_threads
@@ -4,7 +4,7 @@ module SolidQueue
4
4
  module Processes
5
5
  class Base
6
6
  include Callbacks # Defines callbacks needed by other concerns
7
- include AppExecutor, Registrable, Interruptible, Procline
7
+ include AppExecutor, Registrable, Procline
8
8
 
9
9
  attr_reader :name
10
10
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module SolidQueue::Processes
4
4
  module Interruptible
5
+ include SolidQueue::AppExecutor
6
+
5
7
  def wake_up
6
8
  interrupt
7
9
  end
@@ -13,17 +15,19 @@ module SolidQueue::Processes
13
15
  end
14
16
 
15
17
  # Sleeps for 'time'. Can be interrupted asynchronously and return early via wake_up.
16
- # @param time [Numeric] the time to sleep. 0 returns immediately.
17
- # @return [true, nil]
18
- # * returns `true` if an interrupt was requested via #wake_up between the
19
- # last call to `interruptible_sleep` and now, resulting in an early return.
20
- # * returns `nil` if it slept the full `time` and was not interrupted.
18
+ # @param time [Numeric, Duration] the time to sleep. 0 returns immediately.
21
19
  def interruptible_sleep(time)
22
20
  # Invoking this from the main thread may result in significant slowdown.
23
21
  # Utilizing asynchronous execution (Futures) addresses this performance issue.
24
22
  Concurrent::Promises.future(time) do |timeout|
25
- queue.pop(timeout:).tap { queue.clear }
23
+ queue.clear unless queue.pop(timeout:).nil?
24
+ end.on_rejection! do |e|
25
+ wrapped_exception = RuntimeError.new("Interruptible#interruptible_sleep - #{e.class}: #{e.message}")
26
+ wrapped_exception.set_backtrace(e.backtrace)
27
+ handle_thread_error(wrapped_exception)
26
28
  end.value
29
+
30
+ nil
27
31
  end
28
32
 
29
33
  def queue
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module SolidQueue::Processes
6
+ # The original implementation of Interruptible that works
7
+ # with Ruby 3.1 and earlier
8
+ module OgInterruptible
9
+ def wake_up
10
+ interrupt
11
+ end
12
+
13
+ private
14
+ SELF_PIPE_BLOCK_SIZE = 11
15
+
16
+ def interrupt
17
+ self_pipe[:writer].write_nonblock(".")
18
+ rescue Errno::EAGAIN, Errno::EINTR
19
+ # Ignore writes that would block and retry
20
+ # if another signal arrived while writing
21
+ retry
22
+ end
23
+
24
+ def interruptible_sleep(time)
25
+ if time > 0 && self_pipe[:reader].wait_readable(time)
26
+ loop { self_pipe[:reader].read_nonblock(SELF_PIPE_BLOCK_SIZE) }
27
+ end
28
+ rescue Errno::EAGAIN, Errno::EINTR
29
+ end
30
+
31
+ # Self-pipe for signal-handling (http://cr.yp.to/docs/selfpipe.html)
32
+ def self_pipe
33
+ @self_pipe ||= create_self_pipe
34
+ end
35
+
36
+ def create_self_pipe
37
+ reader, writer = IO.pipe
38
+ { reader: reader, writer: writer }
39
+ end
40
+ end
41
+ end
@@ -3,11 +3,14 @@
3
3
  module SolidQueue
4
4
  class Scheduler < Processes::Base
5
5
  include Processes::Runnable
6
+ include LifecycleHooks
6
7
 
7
8
  attr_accessor :recurring_schedule
8
9
 
10
+ after_boot :run_start_hooks
9
11
  after_boot :schedule_recurring_tasks
10
12
  before_shutdown :unschedule_recurring_tasks
13
+ before_shutdown :run_stop_hooks
11
14
 
12
15
  def initialize(recurring_tasks:, **options)
13
16
  @recurring_schedule = RecurringSchedule.new(recurring_tasks)
@@ -1,3 +1,3 @@
1
1
  module SolidQueue
2
- VERSION = "1.1.2"
2
+ VERSION = "1.1.3"
3
3
  end
data/lib/solid_queue.rb CHANGED
@@ -51,6 +51,22 @@ module SolidQueue
51
51
  Worker.on_stop(...)
52
52
  end
53
53
 
54
+ def on_dispatcher_start(...)
55
+ Dispatcher.on_start(...)
56
+ end
57
+
58
+ def on_dispatcher_stop(...)
59
+ Dispatcher.on_stop(...)
60
+ end
61
+
62
+ def on_scheduler_start(...)
63
+ Scheduler.on_start(...)
64
+ end
65
+
66
+ def on_scheduler_stop(...)
67
+ Scheduler.on_stop(...)
68
+ end
69
+
54
70
  def supervisor?
55
71
  supervisor
56
72
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rosa Gutierrez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-27 00:00:00.000000000 Z
11
+ date: 2025-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -220,6 +220,20 @@ dependencies:
220
220
  - - ">="
221
221
  - !ruby/object:Gem::Version
222
222
  version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: zeitwerk
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - '='
228
+ - !ruby/object:Gem::Version
229
+ version: 2.6.0
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - '='
235
+ - !ruby/object:Gem::Version
236
+ version: 2.6.0
223
237
  description: Database-backed Active Job backend.
224
238
  email:
225
239
  - rosa@37signals.com
@@ -281,6 +295,7 @@ files:
281
295
  - lib/solid_queue/processes/base.rb
282
296
  - lib/solid_queue/processes/callbacks.rb
283
297
  - lib/solid_queue/processes/interruptible.rb
298
+ - lib/solid_queue/processes/og_interruptible.rb
284
299
  - lib/solid_queue/processes/poller.rb
285
300
  - lib/solid_queue/processes/process_exit_error.rb
286
301
  - lib/solid_queue/processes/process_missing_error.rb
@@ -307,15 +322,8 @@ metadata:
307
322
  homepage_uri: https://github.com/rails/solid_queue
308
323
  source_code_uri: https://github.com/rails/solid_queue
309
324
  post_install_message: |
310
- Upgrading to Solid Queue 0.9.0? There are some breaking changes about how recurring tasks are configured.
311
-
312
- Upgrading to Solid Queue 0.8.0 from < 0.6.0? You need to upgrade to 0.6.0 first.
313
-
314
- Upgrading to Solid Queue 0.4.x, 0.5.x, 0.6.x or 0.7.x? There are some breaking changes about how Solid Queue is started,
315
- configuration and new migrations.
316
-
317
- --> Check https://github.com/rails/solid_queue/blob/main/UPGRADING.md
318
- for upgrade instructions.
325
+ Upgrading from Solid Queue < 1.0? Check details on breaking changes and upgrade instructions
326
+ --> https://github.com/rails/solid_queue/blob/main/UPGRADING.md
319
327
  rdoc_options: []
320
328
  require_paths:
321
329
  - lib
@@ -323,7 +331,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
323
331
  requirements:
324
332
  - - ">="
325
333
  - !ruby/object:Gem::Version
326
- version: '0'
334
+ version: '3.1'
327
335
  required_rubygems_version: !ruby/object:Gem::Requirement
328
336
  requirements:
329
337
  - - ">="