-
Notifications
You must be signed in to change notification settings - Fork 151
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
Fiber aware support for ActiveRecord 4 #190
Conversation
…e_record_4 Conflicts: spec/activerecord_spec.rb
@dgutov hi! i merged this branch with master, slightly fixed my additional AR4 specs and now it passes. |
@marshall-lee Thanks, the build looks good. I should try running our project specs with this branch sometime. Speaking of |
I added calls to I did this because it's the correct way to use ActiveRecord - release connection when you don't need it. One could never know about |
I would look at what i could do with |
The second reason why i added call to |
I think we should add separate test case for concurrent usage of ActiveRecord with connections becoming stale (without call to |
Right, sorry.
I'm aware of that, but the specs passed fine before. In general, I think it's better to have fewer moving parts there.
Whether we clear the connections in specs or not, has no impact on users. On the other hand, you seem to describe a regression. Are the less careful users (who don't call |
I don't think we should call it a regression. I bet there is the same behavior with stale connections in multithreaded usage of ActiveRecord. If so we have two choises: keep the behaviour as compatible as possible or try to make it better. But I should definitely revisit this problem. Maybe it's really regressing. |
This is the 'official' behaviour of ActiveRecord. ActiveRecord 4.1, multithreaded, with threads = []
10.times do
threads << Thread.new do
ActiveRecord::Base.connection.execute("select 1")
end
end
threads.each(&:join) — this one stucks threads = []
10.times do
threads << Thread.new do
ActiveRecord::Base.connection.execute("select 1")
ActiveRecord::Base.clear_active_connections!
end
end
threads.each(&:join) — this one doesn't, because we release connection acquired by current thread. threads = []
10.times do
threads << Thread.new do
ActiveRecord::Base.connection.execute("select 1")
ActiveRecord::Base.connection.close
end
end
threads.each(&:join) — this one doesn't stuck too, because after closing and terminating the acquiring thread, connection became stale so it can be reacquired by other thread. Behavior with fibers should be the same as with threads. |
Also, this is the original def clear_stale_cached_connections!
keys = @reserved_connections.keys - Thread.list.find_all { |t|
t.alive?
}.map { |thread| thread.object_id }
keys.each do |key|
conn = @reserved_connections[key]
ActiveSupport::Deprecation.warn(<<-eowarn) if conn.in_use?
Database connections will not be closed automatically, please close your
database connection at the end of the thread by calling `close` on your
connection. For example: ActiveRecord::Base.connection.close
eowarn
checkin conn
@reserved_connections.delete(key)
end
end So I think we shouldn't do anything special with stale connections. We should just add separate test case confirming that we behave similarly to multithreaded AR. |
Current implementation worked well because it acquires connection per query (similar to wrapping every query with But it doesn't work well on every version of ActiveRecord (so strange — these versions are 3.2 and 4.2, but on 4.0 and 4.1 it passes). So speaking about existing users is slightly not correct. Also current implementation doesn't fully conform to the official ActiveRecord behavior. But yes... Existing 3.2 and 4.2 users may really experience problems if they don't care about stale connections. Maybe try to temporarily wrap every query |
We should also have a spec that confirms the improvement stated in #179: multiple concurrent transactions.
It seems to me like this scheme can break transactions (which can't be shared between connections). Here we started a transaction in the current fiber, made a query, then launched a second fiber, it checked out a connection (the one we've just been using, right?), then if we want to commit the transaction now, we'd have to wait until the first connection is free again. Is that what happens? The "transactions should work properly" spec consistently passes in 4.0 and 4.1.
Bug-for-bug conformance isn't that valuable. As long as things that work (no errors, no wrong results) with fibers are a superset of things that work with threads, I'd say we're in good place. But if I'm right, the tradeoff being offered is being able to use multiple connections automatically (one fiber = one connection), and employ several concurrent transactions, for the price of having to clean up after ourselves. |
It's not a bug. In ActiveRecrod, working with connection pool you have an option: acquire connection manually and manually release it, or use
So on the other hand, current implementation is not a superset — it's a subset. We don't have an option, we just do Also it wraps a database connection instance with ``EM::Synchrony::ConnectionPool
Seems like it doesn't break them. Look at current implementation in
Yep, something like that. Also it's more predictable: if we want so, we will have exactly the same connection during entire request in Rails. Also i think it's not possible to implement |
|
Just for recap, why current implementation should be replaced with this one:
|
But what if someone has a working Anyway, i suggest you the following strategy:
|
Or there is a simpler way: just merge it 😆 |
I've mistaken about current pool concurrency in #196. Well, when the pool works — it just works, effectively using a |
There's less reasons to have it, but still, if the connection pool were fixed to contain more than one connection, and some were checked out manually by temporary fibers and not returned, then Reaper could do its thing.
Yes, it is pretty nice.
I don't know if it's true. As long as the only problem is that the pool contains only one element, and all fibers somehow manage to use it successfully, the only problem left is the lack of concurrency (that's just speed, right?).
So, don't you think it's the main compatibility problem we have? If we could somehow make each new fiber to inherit the connection from its parent (ideally), or just the first checked out one, but interacting with the connection pool manually allowed to use a different connection, that would be backward-compatible enough, I think.
All right, but just to be clear, I'm not a committer here. :) |
@dgutov Hi! Did you tried this branch in your project? |
@marshall-lee Sorry, I should have some results in a few days. |
Done that now (with AR 4.1 and 4.2), sorry for the delay. Using 4.1, it seems after the pool is exhausted, most specs start failing in the same way: Failure/Error: Unable to find matching line from backtrace
ActiveRecord::ConnectionTimeoutError:
could not obtain a database connection within 5.000 seconds (waited 5.001 seconds)
# ./spec/spec_helper.rb:49:in `block (2 levels) in <module:RollbackExampleGroup>'
# ./spec/spec_helper.rb:39:in `block (2 levels) in <top (required)>'
# ./spec/spec_helper.rb:21:in `block (3 levels) in <top (required)>' Using |
👍 |
I've tried to use it with
With this addition all tests in non Rails app pass 😉 |
Hey, what do you think about adding def each(foreach=nil, after=nil, &blk)
wrapped_block = proc do |*args|
ActiveRecord::Base.connection_pool.with_connection { |_conn| blk.call(*args) }
end
old_each(foreach, after, &wrapped_block)
end |
@exAspArk Hello! What do you mean? Monkey patch FiberIterator#each? But why? |
I think it's a bad idea |
Yes, because it's just how ActiveRecord works. When you use regular (non em-synchrony patched) ActiveRecord outside of Rails or Sidekiq (both have special middleware that calls |
And I also think that in every application middleware-approach will be fine. |
@marshall-lee @dgutov any chance we can rebase this one? What's the status on this -- are we close? |
Hello!
I took #179 and added support for ActiveRecord 4.
All ActiveRecord specs are green for stable versions of 3.2, 4.0, 4.1 and 4.2.
Also I reimplemented
clear_stale_cached_connections!
for 3.2 that could help but not so much. ActiveRecord users should explicitly release connections orActiveRecord::ConnectionAdapters::ConnectionManagement
middleware could do this and there is nothing to do with it. By the way, they should do it in threaded environment too. Anyway.3.1 is not working and I cannot figure out why. So my opinion: we should drop 3.1 support in favor of better implementation for >= 3.2, < 5.
Currently tests may fail because of broken
mysql2
. See brianmario/mysql2#580 (with specs) or brianmario/mysql2#509 — with any of these patches everything works great.