Skip to content

Commit

Permalink
Merge pull request #36949 from 97jaz/thread-local-prepared-statements
Browse files Browse the repository at this point in the history
Make prepared statement status thread and instance-specific
  • Loading branch information
rafaelfranca committed Aug 16, 2019
2 parents d16f39d + d553213 commit 5e2d3d1
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 9 deletions.
Expand Up @@ -10,6 +10,7 @@
require "arel/collectors/composite"
require "arel/collectors/sql_string"
require "arel/collectors/substitute_binds"
require "concurrent/atomic/thread_local_var"

module ActiveRecord
module ConnectionAdapters # :nodoc:
Expand Down Expand Up @@ -77,7 +78,7 @@ class AbstractAdapter
SIMPLE_INT = /\A\d+\z/

attr_accessor :pool
attr_reader :visitor, :owner, :logger, :lock, :prepared_statements
attr_reader :visitor, :owner, :logger, :lock
alias :in_use? :owner

set_callback :checkin, :after, :enable_lazy_transactions!
Expand Down Expand Up @@ -128,10 +129,10 @@ def initialize(connection, logger = nil, config = {}) # :nodoc:
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new

if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@prepared_statement_status = Concurrent::ThreadLocalVar.new(true)
@visitor.extend(DetermineIfPreparableVisitor)
else
@prepared_statements = false
@prepared_statement_status = Concurrent::ThreadLocalVar.new(false)
end

@advisory_locks_enabled = self.class.type_cast_config_to_boolean(
Expand Down Expand Up @@ -174,6 +175,10 @@ def schema_migration # :nodoc:
end
end

def prepared_statements
@prepared_statement_status.value
end

class Version
include Comparable

Expand Down Expand Up @@ -258,10 +263,7 @@ def seconds_idle # :nodoc:
end

def unprepared_statement
old_prepared_statements, @prepared_statements = @prepared_statements, false
yield
ensure
@prepared_statements = old_prepared_statements
@prepared_statement_status.bind(false) { yield }
end

# Returns the human-readable name of the adapter. Use mixed case - one
Expand Down
Expand Up @@ -39,8 +39,8 @@ class Mysql2Adapter < AbstractMysqlAdapter
include MySQL::DatabaseStatements

def initialize(connection, logger, connection_options, config)
super
@prepared_statements = false unless config.key?(:prepared_statements)
superclass_config = config.reverse_merge(prepared_statements: false)
super(connection, logger, connection_options, superclass_config)
configure_connection
end

Expand Down
50 changes: 50 additions & 0 deletions activerecord/test/cases/prepared_statement_status_test.rb
@@ -0,0 +1,50 @@
# frozen_string_literal: true

require "cases/helper"
require "models/course"
require "models/entrant"

module ActiveRecord
class PreparedStatementStatusTest < ActiveRecord::TestCase
def test_prepared_statement_status_is_thread_and_instance_specific
course_conn = Course.connection
entrant_conn = Entrant.connection

inside = Concurrent::Event.new
preventing = Concurrent::Event.new
finished = Concurrent::Event.new

assert_not_same course_conn, entrant_conn

if current_adapter?(:Mysql2Adapter)
# The mysql adapter does not use prepared
# statements by default.
assert_not course_conn.prepared_statements
assert_not entrant_conn.prepared_statements
else
t1 = Thread.new do
course_conn.unprepared_statement do
inside.set
preventing.wait
assert_not course_conn.prepared_statements
assert entrant_conn.prepared_statements
finished.set
end
end

t2 = Thread.new do
entrant_conn.unprepared_statement do
inside.wait
assert course_conn.prepared_statements
assert_not entrant_conn.prepared_statements
preventing.set
finished.wait
end
end

t1.join
t2.join
end
end
end
end

0 comments on commit 5e2d3d1

Please sign in to comment.