diff --git a/Gemfile b/Gemfile index 7bea20ca..50abddc9 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,10 @@ git_source(:github) do |repo_name| "https://github.com/#{repo_name}.git" end -gem 'rails', '~> 7.1.6' +gem "mutex_m", "~> 0.3.0" +gem "csv", "~> 3.3" + +gem 'rails', '~> 7.2.3' gem 'zeitwerk', '~> 2.6.18' # keep zeitwerk 2.6 until Ruby is 3.2 or higher gem 'pg', '~> 1.6.2' diff --git a/Gemfile.lock b/Gemfile.lock index 4e6d2065..08d8e685 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,85 +9,79 @@ GEM remote: https://rubygems.org/ specs: 3scale-api (1.4.0) - actioncable (7.1.6) - actionpack (= 7.1.6) - activesupport (= 7.1.6) + actioncable (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.6) - actionpack (= 7.1.6) - activejob (= 7.1.6) - activerecord (= 7.1.6) - activestorage (= 7.1.6) - activesupport (= 7.1.6) - mail (>= 2.7.1) - net-imap - net-pop - net-smtp - actionmailer (7.1.6) - actionpack (= 7.1.6) - actionview (= 7.1.6) - activejob (= 7.1.6) - activesupport (= 7.1.6) - mail (~> 2.5, >= 2.5.4) - net-imap - net-pop - net-smtp + actionmailbox (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + actionmailer (7.2.3) + actionpack (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.1.6) - actionview (= 7.1.6) - activesupport (= 7.1.6) + actionpack (7.2.3) + actionview (= 7.2.3) + activesupport (= 7.2.3) cgi nokogiri (>= 1.8.5) racc - rack (>= 2.2.4) + rack (>= 2.2.4, < 3.3) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.6) - actionpack (= 7.1.6) - activerecord (= 7.1.6) - activestorage (= 7.1.6) - activesupport (= 7.1.6) + useragent (~> 0.16) + actiontext (7.2.3) + actionpack (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.6) - activesupport (= 7.1.6) + actionview (7.2.3) + activesupport (= 7.2.3) builder (~> 3.1) cgi erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.1.6) - activesupport (= 7.1.6) + activejob (7.2.3) + activesupport (= 7.2.3) globalid (>= 0.3.6) - activemodel (7.1.6) - activesupport (= 7.1.6) - activerecord (7.1.6) - activemodel (= 7.1.6) - activesupport (= 7.1.6) + activemodel (7.2.3) + activesupport (= 7.2.3) + activerecord (7.2.3) + activemodel (= 7.2.3) + activesupport (= 7.2.3) timeout (>= 0.4.0) - activestorage (7.1.6) - actionpack (= 7.1.6) - activejob (= 7.1.6) - activerecord (= 7.1.6) - activesupport (= 7.1.6) + activestorage (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activesupport (= 7.2.3) marcel (~> 1.0) - activesupport (7.1.6) + activesupport (7.2.3) base64 benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) 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) + tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) ansi (1.5.0) @@ -103,16 +97,17 @@ GEM concurrent-ruby (~> 1.0) builder (3.3.0) byebug (12.0.0) - cgi (0.5.0) + cgi (0.5.1) codecov (0.4.3) simplecov (>= 0.15, < 0.22) coderay (1.1.3) - concurrent-ruby (1.3.5) - connection_pool (2.5.4) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) crack (0.4.5) rexml crass (1.0.6) - date (3.5.0) + csv (3.3.5) + date (3.5.1) debug_inspector (1.2.0) docile (1.3.5) drb (2.2.3) @@ -142,8 +137,7 @@ GEM dry-inflector (~> 1.0) dry-logic (~> 1.4) zeitwerk (~> 2.6) - erb (4.0.4) - cgi (>= 0.3.3) + erb (6.0.1) erubi (1.13.1) excon (0.112.0) faraday (1.3.0) @@ -154,12 +148,12 @@ GEM globalid (1.3.0) activesupport (>= 6.1) hashdiff (1.2.1) - i18n (1.14.7) + i18n (1.14.8) concurrent-ruby (~> 1.0) ice_nine (0.11.2) interception (0.5) - io-console (0.8.1) - irb (1.15.3) + io-console (0.8.2) + irb (1.16.0) pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) @@ -193,7 +187,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.24.1) + loofah (2.25.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.9.0) @@ -222,7 +216,7 @@ GEM mustermann (2.0.2) ruby2_keywords (~> 0.0.1) mutex_m (0.3.0) - net-imap (0.5.12) + net-imap (0.6.2) date net-protocol net-pop (0.1.2) @@ -232,7 +226,7 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) - nokogiri (1.18.10) + nokogiri (1.19.0) mini_portile2 (~> 2.8.2) racc (~> 1.4) oauth2 (1.4.7) @@ -266,7 +260,7 @@ GEM pry-stack_explorer (0.6.1) binding_of_caller (~> 1.0) pry (~> 0.13) - psych (5.2.6) + psych (5.3.1) date stringio public_suffix (4.0.6) @@ -287,20 +281,20 @@ GEM rackup (1.0.1) rack (< 3) webrick - rails (7.1.6) - actioncable (= 7.1.6) - actionmailbox (= 7.1.6) - actionmailer (= 7.1.6) - actionpack (= 7.1.6) - actiontext (= 7.1.6) - actionview (= 7.1.6) - activejob (= 7.1.6) - activemodel (= 7.1.6) - activerecord (= 7.1.6) - activestorage (= 7.1.6) - activesupport (= 7.1.6) + rails (7.2.3) + actioncable (= 7.2.3) + actionmailbox (= 7.2.3) + actionmailer (= 7.2.3) + actionpack (= 7.2.3) + actiontext (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activemodel (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) bundler (>= 1.15.0) - railties (= 7.1.6) + railties (= 7.2.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -308,11 +302,11 @@ GEM 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.6) - actionpack (= 7.1.6) - activesupport (= 7.1.6) + railties (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) cgi - irb + irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) @@ -320,7 +314,7 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.3.1) - rdoc (6.15.1) + rdoc (7.1.0) erb psych (>= 4.0.0) tsort @@ -374,10 +368,10 @@ GEM rack (~> 2.2) rack-protection (= 2.2.3) tilt (~> 2.0) - stringio (3.1.8) - thor (1.4.0) + stringio (3.2.0) + thor (1.5.0) tilt (2.0.11) - timeout (0.4.4) + timeout (0.6.0) tomlrb (2.0.3) tsort (0.2.0) tzinfo (2.0.6) @@ -385,6 +379,7 @@ GEM unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.1.0) + useragent (0.16.11) validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix @@ -392,7 +387,7 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.2) + webrick (1.9.2) websocket-driver (0.8.0) base64 websocket-extensions (>= 0.1.0) @@ -426,6 +421,7 @@ DEPENDENCIES bootsnap (>= 1.4.4) bugsnag codecov + csv (~> 3.3) httpclient! k8s-ruby license_finder (~> 7.0.1) @@ -433,6 +429,7 @@ DEPENDENCIES message_bus minitest-reporters minitest-stub-const + mutex_m (~> 0.3.0) oauth2 pg (~> 1.6.2) prometheus-client (~> 2.1.0) @@ -443,7 +440,7 @@ DEPENDENCIES puma (~> 5.2) que (~> 2.4.1) que-web - rails (~> 7.1.6) + rails (~> 7.2.3) responders (~> 3.0.1) rubocop rubocop-performance diff --git a/app/models/integration.rb b/app/models/integration.rb index c95a87bc..8082533b 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -4,7 +4,7 @@ class Integration < ApplicationRecord belongs_to :model has_many :integration_states, dependent: :destroy - enum state: %i[active disabled].index_with{ |status| status.to_s } + enum :state, %i[active disabled].index_with{ |status| status.to_s } def self.tenant_or_model(tenant, model) by_tenant = where(tenant: tenant, model_id: nil) diff --git a/bin/setup b/bin/setup index 3cd5a9d7..2442e11a 100755 --- a/bin/setup +++ b/bin/setup @@ -3,6 +3,7 @@ require "fileutils" # path to your application root. APP_ROOT = File.expand_path("..", __dir__) +APP_NAME = "zync" def system!(*args) system(*args, exception: true) diff --git a/config/application.rb b/config/application.rb index 47d46216..42d2cd13 100644 --- a/config/application.rb +++ b/config/application.rb @@ -21,12 +21,12 @@ module Zync class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 7.1 + config.load_defaults 7.2 # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. # Common ones are `templates`, `generators`, or `middleware`, for example. - # config.autoload_lib(ignore: %w(tasks puma generators prometheus que)) + # config.autoload_lib(ignore: %w(assets tasks puma generators prometheus que)) # Que needs :sql because of advanced PostgreSQL features config.active_record.schema_format = :sql @@ -41,6 +41,13 @@ class Application < Rails::Application config.active_job.queue_adapter = :que + # This rails setting changed several time for the last Rails version + # https://github.com/rails/rails/blob/6f39910d26eb590cb214a0fce5858fe0d7ddfff8/activejob/CHANGELOG.md?plain=1#L48-L58 + # + # For Rails 7.2, set it to `:always`: https://github.com/que-rb/que/issues/430 + # For Rails 8.0+, Remove it + config.active_job.enqueue_after_transaction_commit = :always + begin que = config_for(:que)&.deep_symbolize_keys diff --git a/config/environments/development.rb b/config/environments/development.rb index 98b3b542..404ebacb 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -18,7 +18,7 @@ # Show full error reports. config.consider_all_requests_local = true - # Enable server timing + # Enable server timing. config.server_timing = true # Enable/disable caching. By default caching is disabled. @@ -52,13 +52,15 @@ # Highlight code that enqueued background job in logs. config.active_job.verbose_enqueue_logs = true - # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true - # Raise error when a before_action's only/except options reference missing actions + # Raise error when a before_action's only/except options reference missing actions. config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + config.generators.apply_rubocop_autocorrect_after_generate! end diff --git a/config/environments/production.rb b/config/environments/production.rb index 0f66f30a..b3b94441 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -36,6 +36,9 @@ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + # Log to STDOUT by default # config.logger = ActiveSupport::Logger.new(STDOUT) # .tap { |logger| logger.formatter = ::Logger::Formatter.new } @@ -79,6 +82,9 @@ # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false + # Only use :id for inspections in production. + # config.active_record.attributes_for_inspect = [ :id ] + # Enable DNS rebinding protection and other `Host` header attacks. # config.hosts = [ # "example.com", # Allow requests from example.com diff --git a/config/environments/test.rb b/config/environments/test.rb index d349c355..3ecae4b5 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -49,6 +49,8 @@ # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true - # Raise error when a before_action's only/except options reference missing actions + # Raise error when a before_action's only/except options reference missing actions. config.action_controller.raise_on_missing_callback_actions = true + + config.active_job.queue_adapter = :test end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index c72afd9d..6d927818 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -4,5 +4,5 @@ # Use this to limit dissemination of sensitive information. # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. Rails.application.config.filter_parameters += [ - :password, :access_token + :password, :access_token, :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn ] diff --git a/config/puma.rb b/config/puma.rb index 0c0ba2d2..335a2cc7 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,10 +1,26 @@ -# Puma can serve each request in a thread from an internal thread pool. -# The `threads` method setting takes two numbers: a minimum and maximum. -# Any libraries that use thread pools should be configured to match -# the maximum value specified for Puma. Default is set to 5 threads for minimum -# and maximum; this matches the default thread size of Active Record. +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. + +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# to prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. # -max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 3 } min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } threads min_threads_count, max_threads_count diff --git a/lib/prometheus/que_stats.rb b/lib/prometheus/que_stats.rb index b61f91cf..ce707fdd 100644 --- a/lib/prometheus/que_stats.rb +++ b/lib/prometheus/que_stats.rb @@ -69,7 +69,7 @@ def call(*filters) with(common_tables) execute do |connection| - connection.select_all(relation.to_sql) + connection.select_all(relation) end end diff --git a/test/lib/prometheus/que_stats_test.rb b/test/lib/prometheus/que_stats_test.rb index 0bdef172..90bb24a2 100644 --- a/test/lib/prometheus/que_stats_test.rb +++ b/test/lib/prometheus/que_stats_test.rb @@ -12,73 +12,89 @@ class Prometheus::QueStatsTest < ActiveSupport::TestCase Prometheus::QueStats.read_only_transaction = @_readonly_transaction end + def with_que_adapter(&block) + ApplicationJob.stub(:queue_adapter, ActiveJob::QueueAdapters::QueAdapter.new, &block) + end + test 'worker stats' do assert Prometheus::QueStats::WorkerStats.new.call end test 'job stats' do - Que.stop! - ApplicationJob.perform_later - assert_equal 1, stats_count - assert_equal 1, stats_count(where: ['1 > 0']) - assert_equal 1, stats_count(where: ['1 > 0', '2 > 1']) - assert_equal 0, stats_count(where: ['1 > 0', '2 < 1']) + with_que_adapter do + Que.stop! + ApplicationJob.perform_later + assert_equal 1, stats_count + assert_equal 1, stats_count(where: ['1 > 0']) + assert_equal 1, stats_count(where: ['1 > 0', '2 > 1']) + assert_equal 0, stats_count(where: ['1 > 0', '2 < 1']) + end end test 'ready jobs stats' do - Que.stop! - assert_equal 0, stats_count(type: :ready) - jobs = Array.new(3) { ApplicationJob.perform_later } - jobs << ApplicationJob.set(wait_until: 1.day.from_now).perform_later - assert_equal 3, stats_count(type: :ready) - update_job(jobs[0], error_count: 1) - assert_equal 2, stats_count(type: :ready) - update_job(jobs[1], expired_at: 1.minute.ago) - assert_equal 1, stats_count(type: :ready) - update_job(jobs[2], finished_at: 1.minute.ago) - assert_equal 0, stats_count(type: :ready) + with_que_adapter do + Que.stop! + assert_equal 0, stats_count(type: :ready) + jobs = Array.new(3) { ApplicationJob.perform_later } + jobs << ApplicationJob.set(wait_until: 1.day.from_now).perform_later + assert_equal 3, stats_count(type: :ready) + update_job(jobs[0], error_count: 1) + assert_equal 2, stats_count(type: :ready) + update_job(jobs[1], expired_at: 1.minute.ago) + assert_equal 1, stats_count(type: :ready) + update_job(jobs[2], finished_at: 1.minute.ago) + assert_equal 0, stats_count(type: :ready) + end end test 'scheduled jobs stats' do - Que.stop! - assert_equal 0, stats_count(type: :scheduled) - jobs = [ApplicationJob, ApplicationJob.set(wait_until: 1.day.from_now), ApplicationJob.set(wait_until: 2.days.from_now)].map(&:perform_later) - assert_equal 2, stats_count(type: :scheduled) - update_job(jobs[1], error_count: 16, expired_at: 1.minute.ago) - assert_equal 1, stats_count(type: :scheduled) - update_job(jobs.last, run_at: 1.minute.ago) - assert_equal 0, stats_count(type: :scheduled) + with_que_adapter do + Que.stop! + assert_equal 0, stats_count(type: :scheduled) + jobs = [ApplicationJob, ApplicationJob.set(wait_until: 1.day.from_now), ApplicationJob.set(wait_until: 2.days.from_now)].map(&:perform_later) + assert_equal 2, stats_count(type: :scheduled) + update_job(jobs[1], error_count: 16, expired_at: 1.minute.ago) + assert_equal 1, stats_count(type: :scheduled) + update_job(jobs.last, run_at: 1.minute.ago) + assert_equal 0, stats_count(type: :scheduled) + end end test 'finished jobs stats' do - Que.stop! - assert_equal 0, stats_count(type: :finished) - jobs = Array.new(2) { ApplicationJob.perform_later } - assert_equal 0, stats_count(type: :finished) - update_job(jobs.first, finished_at: Time.now) - assert_equal 1, stats_count(type: :finished) + with_que_adapter do + Que.stop! + assert_equal 0, stats_count(type: :finished) + jobs = Array.new(2) { ApplicationJob.perform_later } + assert_equal 0, stats_count(type: :finished) + update_job(jobs.first, finished_at: Time.now) + assert_equal 1, stats_count(type: :finished) + end end test 'failed jobs stats' do - Que.stop! - assert_equal 0, stats_count(type: :failed) - jobs = Array.new(2) { ApplicationJob.perform_later } - assert_equal 0, stats_count(type: :failed) - update_job(jobs.first, error_count: 1) - assert_equal 1, stats_count(type: :failed) - update_job(jobs.first, error_count: 15) - assert_equal 1, stats_count(type: :failed) - update_job(jobs.first, error_count: 16, expired_at: Time.now.utc) - assert_equal 0, stats_count(type: :failed) + with_que_adapter do + Que.stop! + assert_equal 0, stats_count(type: :failed) + jobs = Array.new(2) { ApplicationJob.perform_later } + assert_equal 0, stats_count(type: :failed) + update_job(jobs.first, error_count: 1) + assert_equal 1, stats_count(type: :failed) + update_job(jobs.first, error_count: 15) + assert_equal 1, stats_count(type: :failed) + update_job(jobs.first, error_count: 16, expired_at: Time.now.utc) + assert_equal 0, stats_count(type: :failed) + end end test 'expired jobs stats' do - Que.stop! - assert_equal 0, stats_count(type: :expired) - jobs = Array.new(2) { ApplicationJob.perform_later } - assert_equal 0, stats_count(type: :expired) - update_job(jobs.first, error_count: 16, expired_at: Time.now.utc) - assert_equal 1, stats_count(type: :expired) + with_que_adapter do + Que.stop! + assert_equal 0, stats_count(type: :expired) + jobs = Array.new(2) { ApplicationJob.perform_later } + assert_equal 0, stats_count(type: :expired) + update_job(jobs.first, error_count: 16, expired_at: Time.now.utc) + assert_equal 1, stats_count(type: :expired) + end end class WithTransaction < ActiveSupport::TestCase @@ -91,20 +107,22 @@ def test_readonly_transaction end test 'serialize metrics' do - Que.stop! + with_que_adapter do + Que.stop! - job = ApplicationJob.new - job.enqueue + job = ApplicationJob.new + job.enqueue - job.scheduled_at = 1.day.ago - job.enqueue + job.scheduled_at = 1.day.ago + job.enqueue - job.executions = 1 - job.enqueue + job.executions = 1 + job.enqueue - Yabeda.collectors.each(&:call) + Yabeda.collectors.each(&:call) - assert Prometheus::Client::Formats::Text.marshal(Yabeda::Prometheus.registry) + assert Prometheus::Client::Formats::Text.marshal(Yabeda::Prometheus.registry) + end end protected diff --git a/test/models/model_test.rb b/test/models/model_test.rb index f1625bc0..2429aa62 100644 --- a/test/models/model_test.rb +++ b/test/models/model_test.rb @@ -2,26 +2,40 @@ require 'test_helper' class ModelTest < ActiveSupport::TestCase + self.use_transactional_tests = false + def test_weak_lock - locking_connection = Model.connection_pool.checkout + model = Model.first! + + + # The events in the two threads must happen in a particular order: + # 1. The new thread must lock the record and wait. This way the lock + # and the transaction are kept open. + # 2. The main thread must wait until the lock is taken, only then it can + # try to take the lock + # We use queues to signal threads + locked_signal = Queue.new + release_signal = Queue.new - fiber = Model.stub(:connection, locking_connection) do - Fiber.new do - locking_connection.transaction do - Fiber.yield Model.first!.weak_lock - end + # Take the lock and keep it taken + thread = Thread.new do + Model.transaction do + model.weak_lock + locked_signal.push(true) + release_signal.pop end end - locked_model = fiber.resume - refute_equal Model.connection, locking_connection + # Wait till the lock is taken + locked_signal.pop - connection = Model.connection_pool.checkout - - Model.stub(:connection, connection) do - assert_raises Model::LockTimeoutError do - Model.find(locked_model.id).weak_lock - end + # Try to take the lock, we expect it to fail + assert_raises Model::LockTimeoutError do + model.weak_lock end + + # Release the thread + release_signal.push(true) + thread.join end end