Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid expensive tracking objects for prepared statements #37200

Closed
wants to merge 1 commit into from

Conversation

SamSaffron
Copy link
Contributor

Per #36949 we introduce a race condition fix for #36763

This refines the fix to avoid using Concurrent::ThreadLocalVar

The implementation in the concurrent lib is rather expensive, culminating in
a finalizer per object that spins off a thread to do cleanup work.

None of this expense is needed as we can simply implement
the desired behavior using Ruby primitives. Additionally this moves to a Fiber bound implementation vs a thread bound implementation, something that is not desired for this particular usage.

cc @matthewd / @rafaelfranca / @97jaz

@kaspth
Copy link
Contributor

kaspth commented Sep 14, 2019

Are we sure we need the extra instance variable versus something in this direction?

module PreparedStatementsRegistry
  extend ActiveSupport::PerThreadRegistry

  attr_accessor :enabled

  def disable
    old_enabled, self.enabled = self.enabled, false
    yield
  ensure
    self.enabled = old_enabled
  end
end

if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
  PreparedStatementsRegistry.enabled = true
  @visitor.extend(DetermineIfPreparableVisitor)
else
  PreparedStatementsRegistry.enabled = true
end

def prepared_statements
  PreparedStatementsRegistry.enabled
end

def unprepared_statement(&block)
  PreparedStatementsRegistry.disable(&block)
end

@SamSaffron
Copy link
Contributor Author

SamSaffron commented Sep 14, 2019

An interesting minor advantage of the proposal I made is that it is pretty much free if you are not using prepared statements, we don't even store anything in the thread local storage.

I am not sure per thread registry will work cause it is based off an object name per https://github.com/SamSaffron/rails/blob/f9e843b25efc35ecb47dc1bb4b2aac2fc7239c88/activesupport/lib/active_support/per_thread_registry.rb#L43-L43

So I am guessing what will happen is when two connections are on the same thread and control is yielded between 2 fibers, weird can happen.

Honestly I am not sure how people feel about allowing the same connection to be used at the same time from 2 threads, this is not something we ever practice at Discourse. The whole underlying premise of the original change is somewhat confusing to me.

Per rails#36949 we introduce a race condition fix for rails#36763

This refines the fix to avoid using Concurrent::ThreadLocalVar

The implementation in the concurrent lib is rather expensive, culminating in
a finalizer per object that spins off a thread to do cleanup work.

None of this expense is needed as we can simply implement
the desired behavior using Ruby primitives. Additionally this moves to a Fiber bound implementation vs a thread bound implementation, something that is not desired for this particular usage.
@kaspth
Copy link
Contributor

kaspth commented Sep 15, 2019

@SamSaffron good points! I revised the patch a little further and merged it in b9cc147. Thanks so much! 😊

@kaspth kaspth closed this Sep 15, 2019
@SamSaffron
Copy link
Contributor Author

@kaspth thanks heaps, nice improvement using a Set there, it is a far better fit.

Any chance you can backport this to the next 6 release?

@rafaelfranca
Copy link
Member

rafaelfranca commented Sep 16, 2019

Backported in 46cc85d

@kaspth
Copy link
Contributor

kaspth commented Sep 16, 2019

Thanks, I forgot to backport it ✌️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants