Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Unreleased

- Read the durable row rather than the query cache in `MessageReference#status`,
`MessageReference#result`, and an actor snapshot. A caller that polls holds one
query cache for the whole poll, and the worker that finishes the message is
another process, so its write cannot clear that cache. A poll inside a request,
a job, or `rails runner` reported the first answer forever. The synchronous
wait already read uncached.

## 0.15.2 - 2026-09-21

- Find the actor instance before the insert when an enqueue starts, and lock
Expand Down
10 changes: 6 additions & 4 deletions lib/solid_objects/actor_snapshot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ class ActorSnapshot
def initialize(reference)
@reference = reference
@actor_class = SolidObjects.registry.fetch(reference.actor_type)
@instance = Instance.find_by(
actor_type: reference.actor_type,
actor_id: reference.actor_id
)
@instance = Instance.uncached do
Instance.find_by(
actor_type: reference.actor_type,
actor_id: reference.actor_id
)
end
@instance_id = @instance&.id || 0
@revision = @instance&.state_revision || 0
@actor = build_actor
Expand Down
20 changes: 11 additions & 9 deletions lib/solid_objects/message_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,21 @@ def initialize(id:, request_id:, actor_type:, actor_id:, sequence:)

# @rbs () -> String
def status
message = Message.find(id)
return "rejected" if message.rejected?
return "completed" if message.completed?
return "dead" if message.dead?
return "claimed" if message.claimed?
return "ready" if message.ready?

"unknown"
Message.uncached do
message = Message.find(id)
return "rejected" if message.rejected?
return "completed" if message.completed?
return "dead" if message.dead?
return "claimed" if message.claimed?
return "ready" if message.ready?

"unknown"
end
end

# @rbs () -> untyped
def result
Message.find(id).result
Message.uncached { Message.find(id).result }
end

# @rbs (?timeout: Numeric, ?authorization_context: untyped) -> untyped
Expand Down
69 changes: 69 additions & 0 deletions test/integration/query_cache_reads_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require "database_test_helper"

class QueryCacheReadsTest < ActiveSupport::TestCase
class CounterActor < SolidObjects::Actor
actor_type "query-cache-counter"

attribute :count, default: 0

def increment
self.count += 1
end
end

setup { CounterActor.ensure_registered! }

def from_another_connection
Thread.new do
SolidObjects::Record.connection_pool.with_connection { yield }
end.join
end

test "message status refreshes while a query cache is open" do
message = CounterActor.ref("alice").async.increment

ActiveRecord::Base.cache do
assert_equal "ready", message.status

from_another_connection do
SolidObjects::ReadyMessage.where(message_id: message.id).delete_all
SolidObjects::Message.find(message.id).update!(completed_at: Time.current)
end

assert_equal "completed", message.status
end
end

test "message result refreshes while a query cache is open" do
message = CounterActor.ref("alice").async.increment

ActiveRecord::Base.cache do
assert_nil message.result

from_another_connection do
SolidObjects::Message.find(message.id).update!(result: { "value" => 7 })
end

assert_equal({ "value" => 7 }, message.result)
end
end

test "an actor snapshot refreshes while a query cache is open" do
reference = CounterActor.ref("alice")
reference.async.increment
SolidObjects::Worker.new.run_until_idle

ActiveRecord::Base.cache do
assert_equal 1, reference.snapshot.count

from_another_connection do
instance = SolidObjects::Instance.find_by!(actor_type: "query-cache-counter", actor_id: "alice")
instance.update!(state: instance.state.merge("count" => 9))
end

assert_equal 9, reference.snapshot.count
end
end
end
Loading