From 06b035f2ea1398e02533381a0e5af4029e5e6a40 Mon Sep 17 00:00:00 2001 From: Patrick Gansterer Date: Thu, 9 Sep 2021 13:23:20 +0200 Subject: [PATCH] Add Rails 6 backport for ActiveRecord This fixes the "can't create Thread: Resource temporarily unavailable" error. --- .rubocop_todo.yml | 2 + config/initializers/rails6_backports.rb | 98 +++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 config/initializers/rails6_backports.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 285e3465..9401bdbd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -215,6 +215,7 @@ Lint/ShadowingOuterLocalVariable: # Configuration parameters: AllowComments, AllowNil. Lint/SuppressedException: Exclude: + - 'config/initializers/rails6_backports.rb' - 'lib/foodsoft_config.rb' - 'lib/tasks/rspec.rake' @@ -1495,6 +1496,7 @@ Style/MultilineBlockChain: Exclude: - 'app/helpers/group_orders_helper.rb' - 'app/models/order.rb' + - 'config/initializers/rails6_backports.rb' # Offense count: 2 # Cop supports --auto-correct. diff --git a/config/initializers/rails6_backports.rb b/config/initializers/rails6_backports.rb new file mode 100644 index 00000000..b72f4220 --- /dev/null +++ b/config/initializers/rails6_backports.rb @@ -0,0 +1,98 @@ +raise "Remove no-longer-needed #{__FILE__}!" if Rails::VERSION::MAJOR >= 6 + +require "weakref" + +module ActiveRecord + # Backport https://github.com/rails/rails/pull/36998 and https://github.com/rails/rails/pull/36999 + # to avoid `ThreadError: can't create Thread: Resource temporarily unavailable` issues + module ConnectionAdapters + class ConnectionPool + class Reaper + @mutex = Mutex.new + @pools = {} + @threads = {} + + class << self + def register_pool(pool, frequency) # :nodoc: + @mutex.synchronize do + unless @threads[frequency]&.alive? + @threads[frequency] = spawn_thread(frequency) + end + @pools[frequency] ||= [] + @pools[frequency] << WeakRef.new(pool) + end + end + + private + + def spawn_thread(frequency) + Thread.new(frequency) do |t| + running = true + while running + sleep t + @mutex.synchronize do + @pools[frequency].select!(&:weakref_alive?) + @pools[frequency].each do |p| + p.reap + p.flush + rescue WeakRef::RefError + end + + if @pools[frequency].empty? + @pools.delete(frequency) + @threads.delete(frequency) + running = false + end + end + end + end + end + end + + def run + return unless frequency && frequency > 0 + + self.class.register_pool(pool, frequency) + end + end + + def reap + stale_connections = synchronize do + return unless @connections + + @connections.select do |conn| + conn.in_use? && !conn.owner.alive? + end.each(&:steal!) + end + + stale_connections.each do |conn| + if conn.active? + conn.reset! + checkin conn + else + remove conn + end + end + end + + def flush(minimum_idle = @idle_timeout) + return if minimum_idle.nil? + + idle_connections = synchronize do + return unless @connections + + @connections.select do |conn| + !conn.in_use? && conn.seconds_idle >= minimum_idle + end.each do |conn| + conn.lease + + @available.delete conn + @connections.delete conn + end + end + + idle_connections.each(&:disconnect!) + end + end + end +end