Low commit activity in last 3 years
No release in over a year
Optionally prevents threads from checking out activerecord connections.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

>= 6.0, < 7.1
>= 6.0, < 7.1
>= 6.0, < 7.1
 Project Readme

[Build Status][circleci]

ActiveRecord-ForbidImplicitCheckout

This gem allows a Thread to prevent itself from checking out out an ActiveRecord connection. This can be useful in preventing your application from accidentally checking out more connections than the database can handle.

Inspired by this blog post: https://bibwild.wordpress.com/2014/07/17/activerecord-concurrency-in-rails4-avoid-leaked-connections/

Installation

Add this line to your application's Gemfile:

gem 'activerecord-forbid_implicit_checkout'

And then execute:

$ bundle

Or install it yourself as:

$ gem install activerecord-forbid_implicit_checkout

Usage

require 'active_record-forbid_implicit_connection_checkout'

Thread.new do
  ActiveRecord::Base.forbid_implicit_connection_checkout_for_thread!
  # Code that doesn't require ActiveRecord
end

If your thread needs a connection at some point

Thread.new do
  ActiveRecord::Base.forbid_implicit_connection_checkout_for_thread!
  # Code that doesn't require ActiveRecord

  ActiveRecord::Base.connection_pool.with_connection do
    # Allow the thread to take a connection within this block
    ActiveRecord::Base.connection
  end

  # Code that doesn't require ActiveRecord
end

Why

Consider the following initially:

require 'active_record-forbid_implicit_connection_checkout'

class Foo
  def self.download(id)
    Net::HTTP.get(URI("http://example.com/products/#{URI.encode(id)}"))
  end
end

class Downloader
  def download_all(ids)
    threads = ids.map do |id|
      Thread.new do
        ActiveRecord::Base.forbid_implicit_connection_checkout_for_thread!
        Foo.download
      end
    end
    threads.each(&:join)
  end
end

Downloader.download_all([1,2,3,4,5])

The developer initially designed this parallel downloading to not be dependent on the database, so the developer feels confident in running many parallel processes of Downloader.download_all.

However, Foo.download might be subject to change.

class SomeModel < ApplicationRecord
  # ...
end

class Foo
  def self.download(id)
    id = SomeModel.find(id).alias_id
    Net::HTTP.get(URI("http://example.com/products/#{URI.encode(id)}"))
  end
end

After this modification to Foo.download, each thread will checkout a new connection to the database. Which could overwhelm the database if there are many parallel processes executing Downloader.download_all. While this violates, the initial assumption about Downloader.download_all using the database, it would have been useful to have a safeguard to prevent this situation from occurring.

The error generated by setting ActiveRecord::Base.forbid_implicit_connection_checkout_for_thread! could have detected this situation during testing which should have failed with ActiveRecord::ImplicitConnectionForbiddenError. In the worst case, the production code running this would have failed with ActiveRecord::ImplicitConnectionForbiddenError, but it would have prevented the database from being overwhelmed and have protected the rest of the application.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org .

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/salsify/activerecord-forbid_implicit_checkout.## License

The gem is available as open source under the terms of the MIT License.