solid_objects 0.5.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e251e3648db71bc5e325e1d8a20be55f927ca08208052acebd973eb78e15497
4
- data.tar.gz: 9362cf7145cb58c02273411dc619a775d7693d238d3f2212d7c4f6336abc30cb
3
+ metadata.gz: 8ac17dc212792f2dfa71c01e701a9e4de27f54b6198ec6e64696b855ff82c4c8
4
+ data.tar.gz: 45bff1ddb2bbc0ff80ddfb55488fec0380d02c729e08a7f06b000b744dddb82d
5
5
  SHA512:
6
- metadata.gz: de728511cbd174ba841030f3e29e3dd4245ccd4dd8168523bf132100a8e4ee0c14f02781bf0b29c08a184f3688708f01d8227662068e6a25ebac1ea2b948c72e
7
- data.tar.gz: a9d34e2ae1defd4288bd9322b3d717ae1fbcc99836f96d76ae2a09d62355e17349089e4b53d9febcefe98aa788c5753b5abcbeee1c9dce08e6968b38297f336e
6
+ metadata.gz: ffb590f2e07a0de4d4ba2ab5ac1cb7d76420b742d36d656b85ecdb0fc401dbabd92b75e47df73948f7e1d42fe4a289d2f16fa8d86086c8bff336ac4e33ae89dc
7
+ data.tar.gz: 5a43e24c8358e2352de0ca1428cdc10d15bdf679c62ca1983b420fa1e739d0c975004e0e33ffb64a96fe3d09cbbcd30181db6a95a678c071fd80f85b935811cd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2 - 2026-08-09
4
+
5
+ - Read the database clock once per transaction instead of once per step, and
6
+ resolve the SQLite busy wait from configuration instead of querying the
7
+ connection for it. A synchronous call now issues 49 database queries instead
8
+ of 66, which matters most on PostgreSQL and MySQL where every query is a
9
+ network round trip.
10
+ - Apply every migration in the benchmark harness. It applied only the initial
11
+ migration, so the `state_revision` column added in 0.4.0 was missing, every
12
+ message failed at commit, and the synchronous benchmarks timed out.
13
+
3
14
  ## 0.5.1 - 2026-08-07
4
15
 
5
16
  - Restore the SQLite busy wait that a synchronous invocation suspends for its
data/benchmark/support.rb CHANGED
@@ -228,7 +228,9 @@ module SolidObjectsBenchmark
228
228
  # @rbs () -> void
229
229
  def migrate
230
230
  require_relative "../db/migrate/20260805000000_create_solid_objects_tables"
231
+ require_relative "../db/migrate/20260806000000_add_state_revision_to_solid_objects_instances"
231
232
  CreateSolidObjectsTables.new.migrate(:up)
233
+ AddStateRevisionToSolidObjectsInstances.new.migrate(:up)
232
234
  end
233
235
 
234
236
  # @rbs () -> void
data/docs/benchmarks.md CHANGED
@@ -41,6 +41,13 @@ scenario used four worker threads.
41
41
  | Synchronous latency | p50 1.8 ms, p95 25.6 ms, p99 156.2 ms |
42
42
  | Activation reuse | 98.0%, four activations for 200 messages |
43
43
  | Queries for one message turn | 29 |
44
+ | Queries for one synchronous call | 49 |
45
+
46
+ A synchronous call costs far more queries than a worker turn because the caller
47
+ also registers or heartbeats its caller process, claims the activation, and
48
+ observes the result. Query count, not query time, dominates synchronous latency
49
+ on a networked database: measured locally against SQLite, database time is
50
+ roughly 5% of a call and the remaining 95% is Ruby.
44
51
 
45
52
  The difference between the SQLite development result and the MySQL adoption
46
53
  result is why Solid Objects does not publish one latency promise. Network
@@ -4,6 +4,9 @@ require "time"
4
4
 
5
5
  module SolidObjects
6
6
  class DatabaseAdapter
7
+ TRANSACTION_CLOCK = :solid_objects_transaction_clock
8
+ TRANSACTION_CLOCK_SCOPE = :solid_objects_transaction_clock_scope
9
+
7
10
  class << self
8
11
  # @rbs (untyped) -> DatabaseAdapter
9
12
  def for(connection)
@@ -46,10 +49,9 @@ module SolidObjects
46
49
 
47
50
  # @rbs () -> Time
48
51
  def database_now
49
- value = with_connection do |connection|
50
- connection.select_value("SELECT #{current_time_expression}")
51
- end
52
- value.is_a?(Time) ? value.utc : Time.parse("#{value} UTC").utc
52
+ return read_database_now unless ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE]
53
+
54
+ ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK] ||= read_database_now
53
55
  end
54
56
 
55
57
  # @rbs () { () -> untyped } -> untyped
@@ -70,7 +72,7 @@ module SolidObjects
70
72
  with_transaction_deadline(connection) do
71
73
  connection.transaction(requires_new: true) do
72
74
  configure_transaction_deadline(connection)
73
- block.call
75
+ with_transaction_clock { block.call }
74
76
  end
75
77
  end
76
78
  end
@@ -91,6 +93,27 @@ module SolidObjects
91
93
 
92
94
  attr_reader :connection_pool, :fixed_connection
93
95
 
96
+ # @rbs () { () -> untyped } -> untyped
97
+ def with_transaction_clock
98
+ return yield if ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE]
99
+
100
+ ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE] = true
101
+ begin
102
+ yield
103
+ ensure
104
+ ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE] = false
105
+ ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK] = nil
106
+ end
107
+ end
108
+
109
+ # @rbs () -> Time
110
+ def read_database_now
111
+ value = with_connection do |connection|
112
+ connection.select_value("SELECT #{current_time_expression}")
113
+ end
114
+ value.is_a?(Time) ? value.utc : Time.parse("#{value} UTC").utc
115
+ end
116
+
94
117
  # @rbs (untyped) { () -> untyped } -> untyped
95
118
  def with_transaction_deadline(_connection)
96
119
  yield
@@ -80,13 +80,13 @@ module SolidObjects
80
80
 
81
81
  # @rbs (untyped) -> Hash[Symbol, untyped]?
82
82
  def restorable_busy_wait(connection)
83
- pragma_timeout = connection.select_value("PRAGMA busy_timeout").to_i
84
- return { pragma_timeout: } if pragma_timeout.positive?
85
-
86
83
  handler_timeout = configured_busy_handler_timeout(connection)
87
- return nil unless handler_timeout
84
+ return { handler_timeout: } if handler_timeout
85
+
86
+ pragma_timeout = connection.select_value("PRAGMA busy_timeout").to_i
87
+ return nil unless pragma_timeout.positive?
88
88
 
89
- { pragma_timeout:, handler_timeout: }
89
+ { pragma_timeout: }
90
90
  end
91
91
 
92
92
  # @rbs (untyped, Hash[Symbol, untyped]) -> void
@@ -1,5 +1,5 @@
1
1
  # rbs_inline: enabled
2
2
 
3
3
  module SolidObjects
4
- VERSION = "0.5.1"
4
+ VERSION = "0.5.2"
5
5
  end
@@ -2,6 +2,10 @@
2
2
 
3
3
  module SolidObjects
4
4
  class DatabaseAdapter
5
+ TRANSACTION_CLOCK: ::Symbol
6
+
7
+ TRANSACTION_CLOCK_SCOPE: ::Symbol
8
+
5
9
  # @rbs (untyped) -> DatabaseAdapter
6
10
  def self.for: (untyped) -> DatabaseAdapter
7
11
 
@@ -42,6 +46,12 @@ module SolidObjects
42
46
 
43
47
  attr_reader fixed_connection: untyped
44
48
 
49
+ # @rbs () { () -> untyped } -> untyped
50
+ def with_transaction_clock: () { () -> untyped } -> untyped
51
+
52
+ # @rbs () -> Time
53
+ def read_database_now: () -> Time
54
+
45
55
  # @rbs (untyped) { () -> untyped } -> untyped
46
56
  def with_transaction_deadline: (untyped) { () -> untyped } -> untyped
47
57
 
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.5.1
4
+ version: 0.5.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-08-08 00:00:00.000000000 Z
11
+ date: 2026-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actioncable