ruby-on-railsrails-activerecordpumamysql2ruby-on-rails-7.2

"This connection is in use by Fiber" error after upgrade to rails 7.2 and ruby 3.2.2


We meet error after app upgrade to rails 7.2 and ruby 3.2.2.

ActiveRecord::StatementInvalid Mysql2::Error: This connection is in use by: #<Fiber:0x00007fad145210e8 (resumed)>

It is occurred in heavy loading mode. Why it could be happened? It looks like threadsafe error, but we do not use threads, except Puma (version 5.6.5)


Solution

  • I faced too with this error. This is because rails 7.2 new behaviour:

    ApplicationRecord.connection_pool.with_connection do
      puts ApplicationRecord.connection_pool.stat
      ApplicationRecord.lease_connection # OR ApplicationRecord.connection
      puts ApplicationRecord.connection_pool.stat
    end
    puts ApplicationRecord.connection_pool.stat
    # =>
    {:size=>16, :connections=>1, :busy=>1, :dead=>0, :idle=>0, :waiting=>0, :checkout_timeout=>5.0}
    {:size=>16, :connections=>1, :busy=>1, :dead=>0, :idle=>0, :waiting=>0, :checkout_timeout=>5.0}
    {:size=>16, :connections=>1, :busy=>1, :dead=>0, :idle=>0, :waiting=>0, :checkout_timeout=>5.0}
    

    #with_connection will no longer release connection if you call #lease_connection or #connection inside block anywhere

    And #release_connection will release connection inside #with_connection block:

    ApplicationRecord.connection_pool.with_connection do
      puts ApplicationRecord.connection_pool.stat
      ApplicationRecord.release_connection
      puts ApplicationRecord.connection_pool.stat
    end
    # =>
    {:size=>16, :connections=>1, :busy=>1, :dead=>0, :idle=>0, :waiting=>0, :checkout_timeout=>5.0}
    {:size=>16, :connections=>1, :busy=>0, :dead=>0, :idle=>1, :waiting=>0, :checkout_timeout=>5.0}
    

    Also, if you work with connection from block argument(.with_connection do |conn|) you will be faced with "this connection is used by FIber" Error:

    ApplicationRecord.connection_pool.with_connection do |conn|
      # # Lets imagine that we have legacy logic that call #release_connection:
      ApplicationRecord.connection_pool.with_connection do
        ApplicationRecord.release_connection
      end
    
      # And let's simulate heavy load to db
      ApplicationRecord.connection_pool.size.times do
        Thread.new { ApplicationRecord.lease_connection.execute('SELECT SLEEP(10);') }
      end
      conn.execute('SELECT SLEEP(1);')
      puts ApplicationRecord.connection_pool.stat
    end
    

    Solutions:

    Dont use #with_connection, use anywhere #lease_connection and explicitly call #release_connection

    Dont use #lease_connection and #release_connection, use only #with_connection and pass it argument anywhere

    Write your own func which will repeat old behaviour of #with_connection:

    def with_connection
      connection_active_was = ApplicationRecord.connection_pool.active_connection?
      ApplicationRecord.connection_pool.with_connection(&block)
    ensure
      ApplicationRecord.release_connection unless connection_active_was
    end
    

    I selected solution with func