Skip to content

Commit 55ba300

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/reminder-cancellation
# Conflicts: # CHANGELOG.md
2 parents 77c0417 + c1da8fc commit 55ba300

4 files changed

Lines changed: 99 additions & 21 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
## Unreleased
44

5-
- Leave a one-shot reminder that already fired out of `reminder` and
6-
`reminders`. Its row stays as `completed`, so a next-run lookup reported an
7-
old time rather than nothing, and an existence check refused to re-arm an
8-
alarm that could never fire again.
9-
- Refuse an unknown operation in `reminder`, `reminders`, `unschedule`, and
10-
`unschedule_all`. `schedule`
11-
already raised `UnknownMessage` for one, so a typo cancelled nothing quietly
12-
and left a recurring reminder running.
5+
- Read the durable row rather than the query cache in `MessageReference#status`,
6+
`MessageReference#result`, and an actor snapshot. A caller that polls holds one
7+
query cache for the whole poll, and the worker that finishes the message is
8+
another process, so its write cannot clear that cache. A poll inside a request,
9+
a job, or `rails runner` reported the first answer forever. The synchronous
10+
wait already read uncached.
1311
- Add reminder cancellation. `unschedule` removes one reminder by operation and
1412
optional key, or by the handle `schedule` now returns. `unschedule_all`
1513
removes every key of one operation. Both stage an intent, so a cancel commits
@@ -20,6 +18,13 @@
2018
actor reads its own schedule from every path, including activation hooks and
2119
observables, because it carries its instance rather than reading an ambient
2220
context that only message dispatch establishes.
21+
- Leave a one-shot reminder that already fired out of `reminder` and
22+
`reminders`. Its row stays as `completed`, so a next-run lookup reported an
23+
old time rather than nothing, and an existence check refused to re-arm an
24+
alarm that could never fire again.
25+
- Refuse an unknown operation in `reminder`, `reminders`, `unschedule`, and
26+
`unschedule_all`. `schedule` already raised `UnknownMessage` for one, so a
27+
typo cancelled nothing quietly and left a recurring reminder running.
2328
- A cancel cannot recall an occurrence the scheduler already turned into a
2429
message. It does pre-empt one the scheduler claimed but has not yet enqueued.
2530
- `schedule` now returns a reminder handle instead of `nil`. An operation that

‎lib/solid_objects/actor_snapshot.rb‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ class ActorSnapshot
1616
def initialize(reference)
1717
@reference = reference
1818
@actor_class = SolidObjects.registry.fetch(reference.actor_type)
19-
@instance = Instance.find_by(
20-
actor_type: reference.actor_type,
21-
actor_id: reference.actor_id
22-
)
19+
@instance = Instance.uncached do
20+
Instance.find_by(
21+
actor_type: reference.actor_type,
22+
actor_id: reference.actor_id
23+
)
24+
end
2325
@instance_id = @instance&.id || 0
2426
@revision = @instance&.state_revision || 0
2527
@actor = build_actor

‎lib/solid_objects/message_reference.rb‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,21 @@ def initialize(id:, request_id:, actor_type:, actor_id:, sequence:)
3535

3636
# @rbs () -> String
3737
def status
38-
message = Message.find(id)
39-
return "rejected" if message.rejected?
40-
return "completed" if message.completed?
41-
return "dead" if message.dead?
42-
return "claimed" if message.claimed?
43-
return "ready" if message.ready?
44-
45-
"unknown"
38+
Message.uncached do
39+
message = Message.find(id)
40+
return "rejected" if message.rejected?
41+
return "completed" if message.completed?
42+
return "dead" if message.dead?
43+
return "claimed" if message.claimed?
44+
return "ready" if message.ready?
45+
46+
"unknown"
47+
end
4648
end
4749

4850
# @rbs () -> untyped
4951
def result
50-
Message.find(id).result
52+
Message.uncached { Message.find(id).result }
5153
end
5254

5355
# @rbs (?timeout: Numeric, ?authorization_context: untyped) -> untyped
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
require "database_test_helper"
4+
5+
class QueryCacheReadsTest < ActiveSupport::TestCase
6+
class CounterActor < SolidObjects::Actor
7+
actor_type "query-cache-counter"
8+
9+
attribute :count, default: 0
10+
11+
def increment
12+
self.count += 1
13+
end
14+
end
15+
16+
setup { CounterActor.ensure_registered! }
17+
18+
def from_another_connection
19+
Thread.new do
20+
SolidObjects::Record.connection_pool.with_connection { yield }
21+
end.join
22+
end
23+
24+
test "message status refreshes while a query cache is open" do
25+
message = CounterActor.ref("alice").async.increment
26+
27+
ActiveRecord::Base.cache do
28+
assert_equal "ready", message.status
29+
30+
from_another_connection do
31+
SolidObjects::ReadyMessage.where(message_id: message.id).delete_all
32+
SolidObjects::Message.find(message.id).update!(completed_at: Time.current)
33+
end
34+
35+
assert_equal "completed", message.status
36+
end
37+
end
38+
39+
test "message result refreshes while a query cache is open" do
40+
message = CounterActor.ref("alice").async.increment
41+
42+
ActiveRecord::Base.cache do
43+
assert_nil message.result
44+
45+
from_another_connection do
46+
SolidObjects::Message.find(message.id).update!(result: { "value" => 7 })
47+
end
48+
49+
assert_equal({ "value" => 7 }, message.result)
50+
end
51+
end
52+
53+
test "an actor snapshot refreshes while a query cache is open" do
54+
reference = CounterActor.ref("alice")
55+
reference.async.increment
56+
SolidObjects::Worker.new.run_until_idle
57+
58+
ActiveRecord::Base.cache do
59+
assert_equal 1, reference.snapshot.count
60+
61+
from_another_connection do
62+
instance = SolidObjects::Instance.find_by!(actor_type: "query-cache-counter", actor_id: "alice")
63+
instance.update!(state: instance.state.merge("count" => 9))
64+
end
65+
66+
assert_equal 9, reference.snapshot.count
67+
end
68+
end
69+
end

0 commit comments

Comments
 (0)