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

Fix async queries to work with query cache and other cached async queries #50778

Merged
merged 1 commit into from
Jan 17, 2024

Conversation

fatkodima
Copy link
Member

Fixes #50776.

@@ -107,7 +107,12 @@ def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :n

if async
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
FutureResult::Complete.new(result)

if result.is_a?(FutureResult)
Copy link

@navidemad navidemad Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried your fix still same issue.
I found with that change it worked.

Suggested change
if result.is_a?(FutureResult)
if result.is_a?(FutureResult::Complete)
result.class
ActiveRecord::FutureResult::Complete

result
#<ActiveRecord::FutureResult::Complete:0x0000000142b451c0
 @result=
  #<ActiveRecord::Result:0x0000000141c96778
   @column_types=
    {0=>#<ActiveModel::Type::Integer:0x0000000142271508 @limit=8, @precision=nil, @range=-9223372036854775808...9223372036854775808, @scale=nil>,
     "count"=>#<ActiveModel::Type::Integer:0x0000000142271508 @limit=8, @precision=nil, @range=-9223372036854775808...9223372036854775808, @scale=nil>},
   @columns=["count"],
   @hash_rows=nil,
   @rows=[[70]]>>

result.is_a?(FutureResult)
false

result.is_a?(FutureResult::Complete)
true

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide an example of what queries do you use that cause the issue?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you test now?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for the delay, it works with your latest change


ShipPart.cache do
count = ShipPart.async_count
assert_equal count.value, ShipPart.async_count.value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency?

Suggested change
assert_equal count.value, ShipPart.async_count.value
assert_async_equal count.value, ShipPart.async_count

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will not work, because nil? method is not defined on the ActiveRecord::Promise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh? If count.value returns a promise, something is wrong anyway, right? (Which is the bug we're talking about here.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thanks. I missed that the count.value stayed in your example, so it works.

@@ -107,7 +107,12 @@ def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :n

if async
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
FutureResult::Complete.new(result)

if result.is_a?(FutureResult) || result.is_a?(FutureResult::Complete)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to test for both classes, because when async is configured in the app - we have FutureResult::SelectAll class and when not, FutureResult::Complete.

See

def select(sql, name = nil, binds = [], prepare: false, async: false)
if async && async_enabled?
if current_transaction.joinable?
raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
end
future_result = async.new(
pool,
sql,
name,
binds,
prepare: prepare,
)
if supports_concurrent_connections? && current_transaction.closed?
future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session)
else
future_result.execute!(self)
end
return future_result
end
result = internal_exec_query(sql, name, binds, prepare: prepare)
if async
FutureResult::Complete.new(result)
else
result
end
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot!

I don't suppose we can find a solution involving duck typing or something different more polymorphy-friendly? 🤔

At least we could move this if / else in a method within the FutureResult module, to make it more likely for this case to be added if another class is added there?

FutureResult.wrap(result)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Would be good to introduce a predicate that is common to all Result classes. e.g. Result#future?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But FutureResult.wrap is nice too.

Copy link
Member Author

@fatkodima fatkodima Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for suggestions! ❤️ I implemented a FutureResult.wrap and also updated a few places in the codebase to use it.

@byroot byroot merged commit 8f386d7 into rails:main Jan 17, 2024
3 of 4 checks passed
byroot added a commit that referenced this pull request Jan 17, 2024
Fix async queries to work with query cache and other cached async queries
@byroot
Copy link
Member

byroot commented Jan 17, 2024

Backported to 7-1 stable as 89e9753

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.

Rails 7.1.3: async_count returns a completed Promise instead of the value
4 participants