FIX: Patch ActiveRecord SchemaCache for safe concurrency support

A single SchemaCache instance is maintained by the connection pool, and made available via a schema_cache method on each connection. When the SchemaCache instance is fetched from the pool, its internal connection reference is updated to equal the requesting connection. However, since there is only one instance of SchemaCache, this internal connection reference is updated everywhere, and can ultimately result in multiple threads accessing the same database connection. In Discourse, this could result in Sidekiq jobs getting 'stuck' in database connections.

This patch modifies SchemaCache so that it caches the internal connection on a per-thread basis

Co-authored-by: Sam Saffron <sam.saffron@gmail.com>
Co-authored-by: Matt Palmer <mpalmer@hezmatt.org>
This commit is contained in:
David Taylor 2020-02-19 16:26:02 +00:00
parent fdb45f2ba1
commit 836ab73d59
No known key found for this signature in database
GPG Key ID: 46904C18B1D3F434

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
#
# Rails has a circular dependency in SchemaCache.
# In certain situation SchemaCache can carry a @connection
# from a different thread. This causes potential concurrency bugs
# in Sidekiq.
#
# This patches it so it is less flexible (theoretically) but always bound to the current connection
# This patch needs to be reviewed in future versions of Rails.
# We should consider upstreaming this optionally.
module ActiveRecord
module ConnectionAdapters
class SchemaCache
def connection=(connection)
# AbstractPool get_schema_cache does schema_cache.connection = connection
Thread.current["schema_cached_connection"] = connection
end
def connection
Thread.current["schema_cached_connection"]
end
end
end
end