diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5467afe4..21b023bc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,6 +24,7 @@ jobs: fail-fast: false matrix: ruby-version: + - 3.1.6 - 3.2.0 - 3.2.4 - 3.3.0 @@ -64,42 +65,3 @@ jobs: bin/rails db:setup - name: Run tests run: bin/rails test - - tests-ruby-3-1-6: - name: Tests Ruby 3.1.6 - runs-on: ubuntu-latest - strategy: - matrix: - database: [ mysql, postgres, sqlite ] - services: - mysql: - image: mysql:8.0.31 - env: - MYSQL_ALLOW_EMPTY_PASSWORD: "yes" - ports: - - 33060:3306 - options: --health-cmd "mysql -h localhost -e \"select now()\"" --health-interval 1s --health-timeout 5s --health-retries 30 - postgres: - image: postgres:15.1 - env: - POSTGRES_HOST_AUTH_METHOD: "trust" - ports: - - 55432:5432 - env: - TARGET_DB: ${{ matrix.database }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Setup Ruby and install specific gems for 3.1.6 - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.1.6 - - name: Install dependencies with specific Gemfile.lock - run: | - cp Gemfile.lock.ruby_3_1_6 Gemfile.lock - bundle install - - name: Setup test database - run: | - bin/rails db:setup - - name: Run tests - run: bin/rails test \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 830f9d66..7e7c26b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -178,7 +178,7 @@ GEM unicode-display_width (3.1.3) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - zeitwerk (2.7.1) + zeitwerk (2.6.0) PLATFORMS arm64-darwin-22 @@ -199,6 +199,7 @@ DEPENDENCIES rubocop-rails-omakase solid_queue! sqlite3 + zeitwerk (= 2.6.0) BUNDLED WITH 2.5.9 diff --git a/Gemfile.lock.ruby_3_1_6 b/Gemfile.lock.ruby_3_1_6 deleted file mode 100644 index d95cdea9..00000000 --- a/Gemfile.lock.ruby_3_1_6 +++ /dev/null @@ -1,205 +0,0 @@ -PATH - remote: . - specs: - solid_queue (1.1.2) - activejob (>= 7.1) - activerecord (>= 7.1) - concurrent-ruby (>= 1.3.1) - fugit (~> 1.11.0) - railties (>= 7.1) - thor (~> 1.3.1) - -GEM - remote: https://rubygems.org/ - specs: - actionpack (7.1.5.1) - actionview (= 7.1.5.1) - activesupport (= 7.1.5.1) - nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4) - rack-session (>= 1.0.1) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - actionview (7.1.5.1) - activesupport (= 7.1.5.1) - builder (~> 3.1) - erubi (~> 1.11) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (7.1.5.1) - activesupport (= 7.1.5.1) - globalid (>= 0.3.6) - activemodel (7.1.5.1) - activesupport (= 7.1.5.1) - activerecord (7.1.5.1) - activemodel (= 7.1.5.1) - activesupport (= 7.1.5.1) - timeout (>= 0.4.0) - activesupport (7.1.5.1) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - mutex_m - securerandom (>= 0.3) - tzinfo (~> 2.0) - ast (2.4.2) - base64 (0.2.0) - benchmark (0.4.0) - bigdecimal (3.1.9) - builder (3.3.0) - concurrent-ruby (1.3.4) - connection_pool (2.4.1) - crass (1.0.6) - date (3.4.1) - debug (1.9.2) - irb (~> 1.10) - reline (>= 0.3.8) - drb (2.2.1) - erubi (1.13.1) - et-orbi (1.2.11) - tzinfo - fugit (1.11.1) - et-orbi (~> 1, >= 1.2.11) - raabro (~> 1.4) - globalid (1.2.1) - activesupport (>= 6.1) - i18n (1.14.6) - concurrent-ruby (~> 1.0) - io-console (0.8.0) - irb (1.14.3) - rdoc (>= 4.0.0) - reline (>= 0.4.2) - json (2.9.1) - language_server-protocol (3.17.0.3) - logger (1.6.2) - loofah (2.23.1) - crass (~> 1.0.2) - nokogiri (>= 1.12.0) - minitest (5.25.4) - mocha (2.1.0) - ruby2_keywords (>= 0.0.5) - mutex_m (0.3.0) - mysql2 (0.5.6) - nio4r (2.7.4) - nokogiri (1.18.0-arm64-darwin) - racc (~> 1.4) - nokogiri (1.18.0-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.18.0-x86_64-linux-gnu) - racc (~> 1.4) - parallel (1.26.3) - parser (3.3.6.0) - ast (~> 2.4.1) - racc - pg (1.5.4) - psych (5.2.2) - date - stringio - puma (6.4.3) - nio4r (~> 2.0) - raabro (1.4.0) - racc (1.8.1) - rack (3.1.8) - rack-session (2.0.0) - rack (>= 3.0.0) - rack-test (2.2.0) - rack (>= 1.3) - rackup (2.2.1) - rack (>= 3) - rails-dom-testing (2.2.0) - activesupport (>= 5.0.0) - minitest - nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) - nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.1.5.1) - actionpack (= 7.1.5.1) - activesupport (= 7.1.5.1) - irb - rackup (>= 1.0.0) - rake (>= 12.2) - thor (~> 1.0, >= 1.2.2) - zeitwerk (~> 2.6) - rainbow (3.1.1) - rake (13.2.1) - rdoc (6.8.1) - psych (>= 4.0.0) - regexp_parser (2.10.0) - reline (0.6.0) - io-console (~> 0.5) - rubocop (1.69.2) - json (~> 2.3) - language_server-protocol (>= 3.17.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.36.2, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.37.0) - parser (>= 3.3.1.0) - rubocop-minitest (0.36.0) - rubocop (>= 1.61, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-performance (1.23.0) - rubocop (>= 1.48.1, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.28.0) - activesupport (>= 4.2.0) - rack (>= 1.1) - rubocop (>= 1.52.0, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails-omakase (1.0.0) - rubocop - rubocop-minitest - rubocop-performance - rubocop-rails - ruby-progressbar (1.13.0) - ruby2_keywords (0.0.5) - securerandom (0.4.1) - sqlite3 (1.5.4-arm64-darwin) - sqlite3 (1.5.4-x86_64-darwin) - sqlite3 (1.5.4-x86_64-linux) - stringio (3.1.2) - thor (1.3.2) - timeout (0.4.3) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (3.1.3) - unicode-emoji (~> 4.0, >= 4.0.4) - unicode-emoji (4.0.4) - zeitwerk (2.6.0) - -PLATFORMS - arm64-darwin-22 - arm64-darwin-23 - arm64-darwin-24 - x86_64-darwin-21 - x86_64-darwin-23 - x86_64-linux - -DEPENDENCIES - debug (~> 1.9) - logger - mocha - mysql2 - pg - puma - rdoc - rubocop-rails-omakase - solid_queue! - sqlite3 - zeitwerk (= 2.6.0) - -BUNDLED WITH - 2.5.9 diff --git a/README.md b/README.md index b9dfdc9b..c77ed953 100644 --- a/README.md +++ b/README.md @@ -375,9 +375,9 @@ In Solid queue, you can hook into two different points in the supervisor's life: - `start`: after the supervisor has finished booting and right before it forks workers and dispatchers. - `stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown. -And into two different points in a worker's life: -- `worker_start`: after the worker has finished booting and right before it starts the polling loop. -- `worker_stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown (which is just `exit!`). +And into two different points in the worker's, dispatcher's and scheduler's life: +- `(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. +- `(worker|dispatcher|scheduler)_stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown (which is just `exit!`). You can use the following methods with a block to do this: ```ruby @@ -386,6 +386,12 @@ SolidQueue.on_stop SolidQueue.on_worker_start SolidQueue.on_worker_stop + +SolidQueue.on_dispatcher_start +SolidQueue.on_dispatcher_stop + +SolidQueue.on_scheduler_start +SolidQueue.on_scheduler_stop ``` For example: diff --git a/app/models/solid_queue/scheduled_execution.rb b/app/models/solid_queue/scheduled_execution.rb index 0f45626b..f2159422 100644 --- a/app/models/solid_queue/scheduled_execution.rb +++ b/app/models/solid_queue/scheduled_execution.rb @@ -14,8 +14,7 @@ class << self def dispatch_next_batch(batch_size) transaction do job_ids = next_batch(batch_size).non_blocking_lock.pluck(:job_id) - if job_ids.empty? - 0 + if job_ids.empty? then 0 else SolidQueue.instrument(:dispatch_scheduled, batch_size: batch_size) do |payload| payload[:size] = dispatch_jobs(job_ids) diff --git a/solid_queue.gemspec b/solid_queue.gemspec index dd8d87f0..17242ff9 100644 --- a/solid_queue.gemspec +++ b/solid_queue.gemspec @@ -22,8 +22,9 @@ Gem::Specification.new do |spec| Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md", "UPGRADING.md"] end - rails_version = ">= 7.1" spec.required_ruby_version = '>= 3.1' + + rails_version = ">= 7.1" spec.add_dependency "activerecord", rails_version spec.add_dependency "activejob", rails_version spec.add_dependency "railties", rails_version @@ -40,8 +41,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rubocop-rails-omakase" spec.add_development_dependency "rdoc" spec.add_development_dependency "logger" - - if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2") - spec.add_development_dependency "zeitwerk", "2.6.0" - end + spec.add_development_dependency "zeitwerk", "2.6.0" end diff --git a/test/integration/jobs_lifecycle_test.rb b/test/integration/jobs_lifecycle_test.rb index 1740f760..decab5b0 100644 --- a/test/integration/jobs_lifecycle_test.rb +++ b/test/integration/jobs_lifecycle_test.rb @@ -4,13 +4,14 @@ class JobsLifecycleTest < ActiveSupport::TestCase setup do - SolidQueue.on_thread_error = silent_on_thread_error_for([ ExpectedTestError, RaisingJob::DefaultError ]) + @_on_thread_error = SolidQueue.on_thread_error + SolidQueue.on_thread_error = silent_on_thread_error_for([ ExpectedTestError, RaisingJob::DefaultError ], @_on_thread_error) @worker = SolidQueue::Worker.new(queues: "background", threads: 3) @dispatcher = SolidQueue::Dispatcher.new(batch_size: 10, polling_interval: 0.2) end teardown do - SolidQueue.on_thread_error = @on_thread_error + SolidQueue.on_thread_error = @_on_thread_error @worker.stop @dispatcher.stop diff --git a/test/integration/processes_lifecycle_test.rb b/test/integration/processes_lifecycle_test.rb index b96c452d..ebb1100c 100644 --- a/test/integration/processes_lifecycle_test.rb +++ b/test/integration/processes_lifecycle_test.rb @@ -66,6 +66,8 @@ class ProcessesLifecycleTest < ActiveSupport::TestCase no_pause = enqueue_store_result_job("no pause") pause = enqueue_store_result_job("pause", pause: 1.second) + wait_while_with_timeout(1.second) { SolidQueue::ReadyExecution.count > 0 } + signal_process(@pid, :QUIT, wait: 0.4.second) wait_for_jobs_to_finish_for(2.seconds, except: pause) @@ -121,7 +123,9 @@ class ProcessesLifecycleTest < ActiveSupport::TestCase no_pause = enqueue_store_result_job("no pause") pause = enqueue_store_result_job("pause", pause: SolidQueue.shutdown_timeout + 10.second) - signal_process(@pid, :TERM, wait: 0.5.second) + wait_while_with_timeout(1.second) { SolidQueue::ReadyExecution.count > 0 } + + signal_process(@pid, :TERM, wait: 0.5) sleep(SolidQueue.shutdown_timeout + 0.5.second) @@ -204,6 +208,7 @@ class ProcessesLifecycleTest < ActiveSupport::TestCase worker = find_processes_registered_as("Worker").first + wait_while_with_timeout(1.second) { SolidQueue::ReadyExecution.count > 0 } signal_process(worker.pid, :TERM, wait: 0.1.second) # Worker is gone diff --git a/test/models/solid_queue/process_test.rb b/test/models/solid_queue/process_test.rb index 5504f0e6..489b2aca 100644 --- a/test/models/solid_queue/process_test.rb +++ b/test/models/solid_queue/process_test.rb @@ -60,7 +60,7 @@ class SolidQueue::ProcessTest < ActiveSupport::TestCase worker = SolidQueue::Worker.new(queues: "*", threads: 3, polling_interval: 0.2) hostname = "Basecamp’s-Computer" - Socket.stub :gethostname, hostname.force_encoding("ASCII-8BIT") do + Socket.stub :gethostname, hostname.dup.force_encoding("ASCII-8BIT") do worker.start wait_for_registered_processes(1, timeout: 1.second) diff --git a/test/test_helper.rb b/test/test_helper.rb index 539f67bc..f54b73f2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -31,9 +31,8 @@ class ActiveSupport::TestCase include ConfigurationTestHelper, ProcessesTestHelper, JobsTestHelper setup do - # Could be cleaner with one several minitest gems, but didn't want to add new dependency @_on_thread_error = SolidQueue.on_thread_error - SolidQueue.on_thread_error = silent_on_thread_error_for(ExpectedTestError) + SolidQueue.on_thread_error = silent_on_thread_error_for(ExpectedTestError, @_on_thread_error) end teardown do @@ -84,21 +83,17 @@ def skip_active_record_query_cache(&block) # @param [Exception, Array] expected an Exception or an array of Exceptions to ignore # @yield Executes the provided block with specified exception(s) silenced def silence_on_thread_error_for(expected, &block) - SolidQueue.with(on_thread_error: silent_on_thread_error_for(expected)) do + current_proc = SolidQueue.on_thread_error + + SolidQueue.with(on_thread_error: silent_on_thread_error_for(expected, current_proc)) do block.call end end - # Does not call on_thread_error for expected exceptions - # @param [Exception, Array] expected an Exception or an array of Exceptions to ignore - def silent_on_thread_error_for(expected) - current_proc = SolidQueue.on_thread_error - + def silent_on_thread_error_for(exceptions, on_thread_error) ->(exception) do - expected_exceptions = Array(expected) - - unless expected_exceptions.any? { exception.instance_of?(_1) } - current_proc.call(exception) + unless Array(exceptions).any? { |e| exception.instance_of?(e) } + on_thread_error.call(exception) end end end