solid_objects 0.15.1 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/docs/roadmap.md +6 -1
- data/lib/solid_objects/database_adapter.rb +10 -0
- data/lib/solid_objects/database_adapters/mysql.rb +5 -0
- data/lib/solid_objects/mailbox.rb +16 -7
- data/lib/solid_objects/version.rb +1 -1
- data/sig/generated/lib/solid_objects/database_adapter.rbs +6 -0
- data/sig/generated/lib/solid_objects/database_adapters/mysql.rbs +3 -0
- data/sig/generated/lib/solid_objects/mailbox.rbs +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55b365960ffe5f49934eb8914e35d546af1de656e158477977caf8c75db19fcd
|
|
4
|
+
data.tar.gz: fcea896079cc1f85eb60e6cbc13d080c597a8074a077cf32a1e34142b5055886
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6c21e96eb84ebbaf519c8860b79d952f3daa5d647f6ca0de6d35527b5b24bab53fd086c35fb57446a1173798f4c2849034d368415ff4afaacccedb509e768f23
|
|
7
|
+
data.tar.gz: f11760f2484478b62ad93cf18327aaa9f5a9d53b7b451c8f1233a479854a3f50b75b369f8559d06295794ea19cad30696088889b3258a541c4003f5775cec2fc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.2 - 2026-09-21
|
|
4
|
+
|
|
5
|
+
- Find the actor instance before the insert when an enqueue starts, and lock
|
|
6
|
+
that row by its primary key. A steady-state enqueue now writes no instance
|
|
7
|
+
row and issues 10 statements instead of 12.
|
|
8
|
+
- Stop the deadlock between concurrent enqueues that create the same actor
|
|
9
|
+
inside a transaction that already wrote. MySQL keeps the shared lock of a
|
|
10
|
+
failed insert across a savepoint rollback, so the mailbox reads the winning
|
|
11
|
+
row in shared mode and never asks to upgrade that lock.
|
|
12
|
+
|
|
3
13
|
## 0.15.1 - 2026-09-16
|
|
4
14
|
|
|
5
15
|
- Use the existing cleanup index when finding expired actor instances. Preserve
|
data/docs/roadmap.md
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
- Explicit actor registry, references, JSON state, and state migrations
|
|
7
7
|
- Fluent direct synchronous RPC, configured `sync`, and durable `async`
|
|
8
8
|
- Durable message history plus ready/claimed membership tables
|
|
9
|
-
- Concurrent sequence allocation and actor creation
|
|
9
|
+
- Concurrent sequence allocation and actor creation. An enqueue finds the
|
|
10
|
+
instance row with an unlocked read, then locks that row by its primary key.
|
|
11
|
+
A steady-state enqueue writes no instance row, and issues 10 statements
|
|
12
|
+
instead of 12. Concurrent creation causes no deadlock on SQLite, PostgreSQL,
|
|
13
|
+
or MySQL. MySQL needs a shared read after a duplicate key, because it uses
|
|
14
|
+
repeatable read. The tests count statements, and do not measure latency.
|
|
10
15
|
- Activation leases, renewal, unique activation tokens, generations, and
|
|
11
16
|
fenced commits
|
|
12
17
|
- Bounded activation passes, idle cache, hot-actor yield, and process records
|
|
@@ -99,6 +99,11 @@ module SolidObjects
|
|
|
99
99
|
nil
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
+
# @rbs () -> String?
|
|
103
|
+
def shared_lock
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
102
107
|
# @rbs () -> String
|
|
103
108
|
def current_time_expression
|
|
104
109
|
"CURRENT_TIMESTAMP"
|
|
@@ -163,6 +168,11 @@ module SolidObjects
|
|
|
163
168
|
claim_lock ? relation.lock(claim_lock) : relation
|
|
164
169
|
end
|
|
165
170
|
|
|
171
|
+
# @rbs (ActiveRecord::Relation[untyped]) -> ActiveRecord::Relation[untyped]
|
|
172
|
+
def share_locked(relation)
|
|
173
|
+
shared_lock ? relation.lock(shared_lock) : relation
|
|
174
|
+
end
|
|
175
|
+
|
|
166
176
|
private
|
|
167
177
|
|
|
168
178
|
attr_reader :connection_pool, :fixed_connection
|
|
@@ -15,6 +15,11 @@ module SolidObjects
|
|
|
15
15
|
"FOR UPDATE SKIP LOCKED"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# @rbs () -> String
|
|
19
|
+
def shared_lock
|
|
20
|
+
"FOR SHARE"
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
# A non-transactional engine would silently break fenced commits, so the
|
|
19
24
|
# storage engine is verified rather than assumed.
|
|
20
25
|
# @rbs () -> Array[String]
|
|
@@ -52,7 +52,6 @@ module SolidObjects
|
|
|
52
52
|
max_bytes: SolidObjects.configuration.max_payload_bytes
|
|
53
53
|
)
|
|
54
54
|
instance = find_or_create_instance(reference, actor_class)
|
|
55
|
-
instance.lock!
|
|
56
55
|
|
|
57
56
|
existing = find_idempotent_message(instance, idempotency_key)
|
|
58
57
|
if existing
|
|
@@ -108,13 +107,23 @@ module SolidObjects
|
|
|
108
107
|
|
|
109
108
|
# @rbs (Reference, Class) -> Instance
|
|
110
109
|
def find_or_create_instance(reference, actor_class)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
identity = { actor_type: reference.actor_type, actor_id: reference.actor_id }
|
|
111
|
+
identifier = Instance.where(identity).pick(:id)
|
|
112
|
+
return lock_instance!(identifier) if identifier
|
|
113
|
+
|
|
114
|
+
Instance.transaction(requires_new: true) do
|
|
115
|
+
Instance.create!(**identity, state: {}, state_version: actor_class.state_version)
|
|
117
116
|
end
|
|
117
|
+
rescue ActiveRecord::RecordNotUnique
|
|
118
|
+
lock_instance!(database_adapter.share_locked(Instance.where(identity)).pick(:id))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @rbs (Integer?) -> Instance
|
|
122
|
+
def lock_instance!(identifier)
|
|
123
|
+
instance = identifier && Instance.lock.find_by(id: identifier)
|
|
124
|
+
return instance if instance
|
|
125
|
+
|
|
126
|
+
raise ActiveRecord::RecordNotFound, "actor instance disappeared while enqueueing"
|
|
118
127
|
end
|
|
119
128
|
|
|
120
129
|
# @rbs (Instance, String?) -> Message?
|
|
@@ -49,6 +49,9 @@ module SolidObjects
|
|
|
49
49
|
# @rbs () -> String?
|
|
50
50
|
def claim_lock: () -> String?
|
|
51
51
|
|
|
52
|
+
# @rbs () -> String?
|
|
53
|
+
def shared_lock: () -> String?
|
|
54
|
+
|
|
52
55
|
# @rbs () -> String
|
|
53
56
|
def current_time_expression: () -> String
|
|
54
57
|
|
|
@@ -70,6 +73,9 @@ module SolidObjects
|
|
|
70
73
|
# @rbs (ActiveRecord::Relation[untyped]) -> ActiveRecord::Relation[untyped]
|
|
71
74
|
def lock_candidates: (ActiveRecord::Relation[untyped]) -> ActiveRecord::Relation[untyped]
|
|
72
75
|
|
|
76
|
+
# @rbs (ActiveRecord::Relation[untyped]) -> ActiveRecord::Relation[untyped]
|
|
77
|
+
def share_locked: (ActiveRecord::Relation[untyped]) -> ActiveRecord::Relation[untyped]
|
|
78
|
+
|
|
73
79
|
private
|
|
74
80
|
|
|
75
81
|
attr_reader connection_pool: untyped
|
|
@@ -11,6 +11,9 @@ module SolidObjects
|
|
|
11
11
|
# @rbs () -> String
|
|
12
12
|
def claim_lock: () -> String
|
|
13
13
|
|
|
14
|
+
# @rbs () -> String
|
|
15
|
+
def shared_lock: () -> String
|
|
16
|
+
|
|
14
17
|
# A non-transactional engine would silently break fenced commits, so the
|
|
15
18
|
# storage engine is verified rather than assumed.
|
|
16
19
|
# @rbs () -> Array[String]
|
|
@@ -28,6 +28,9 @@ module SolidObjects
|
|
|
28
28
|
# @rbs (Reference, Class) -> Instance
|
|
29
29
|
def find_or_create_instance: (Reference, Class) -> Instance
|
|
30
30
|
|
|
31
|
+
# @rbs (Integer?) -> Instance
|
|
32
|
+
def lock_instance!: (Integer?) -> Instance
|
|
33
|
+
|
|
31
34
|
# @rbs (Instance, String?) -> Message?
|
|
32
35
|
def find_idempotent_message: (Instance, String?) -> Message?
|
|
33
36
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_objects
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.15.
|
|
4
|
+
version: 0.15.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lucas Carlson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actioncable
|