solid_objects 0.4.2 → 0.4.3

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: 7f8876ae7a3b7de88ef10870bf9fb32e73e74969571a6b1f21e83cd88f3933ee
4
- data.tar.gz: 6c72ffa8e6e77b6f50b2a2c3f76c470aa136cac490006316810751175a391e5d
3
+ metadata.gz: 8e027a73cc399aa779d8a60acae69f3f379ce505e060123ad49d5d64dcda4809
4
+ data.tar.gz: 5eb1be4b51649c2c8e6056f0138f130f4f1a8cf2e55e46f86061fc82849658aa
5
5
  SHA512:
6
- metadata.gz: e66ea0877bc3dd4e45726ea6361650c0fc02ba1ee0fd6dc00c0588f8080626b687d113fcf02941ff2466146adfa10e8cd8a27026b660509d62e8ca425fbe5fcb
7
- data.tar.gz: 11a6f7c4e27551cfe4389e58edfacbd10c1b05ab17d0e6cbcc75f25f375257fe959237a5beb076d18941c89d33c76239444de57c2d8cb9d185ff33b239752d64
6
+ metadata.gz: 7f36f021e2707bbd67fcca022a88a4aa70b5cc35f8175c0ee526defcfb66fb1d3113434d8f700aad1f813ca9ccf49bc6a7e59e8d7c95717f007a660ca7535bd4
7
+ data.tar.gz: 246e46e83aae40b366992c7c5e763dc65e00945d57b0c0e724595d2f6af0a66ea5c09d5d4dbc5f4c4bdc1ed4de6766d217560e6b376832a98afab158f82e16ea
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.3 - 2026-08-07
4
+
5
+ - Bound SQLite caller-process registration, reuse, heartbeat, and synchronous
6
+ result observation retries by the original invocation deadline.
7
+ - Load host application actors from `app/actors` before CLI workers start,
8
+ including development environments with eager loading disabled.
9
+
3
10
  ## 0.4.2 - 2026-08-07
4
11
 
5
12
  - Decode Action Cable broadcast payloads before parsing observable invalidations
data/README.md CHANGED
@@ -588,9 +588,9 @@ polling as the fallback. A timeout never cancels the durable invocation.
588
588
  durable status, mailbox blocker, and activation-owner diagnostics without
589
589
  including message arguments. The configured timeout also bounds adapter
590
590
  database lock waits from the enqueue attempt through result observation.
591
- PostgreSQL uses transaction lock and statement timeouts, SQLite uses its busy
592
- timeout, and MySQL uses its execution timeout plus InnoDB's one-second minimum
593
- lock-wait granularity.
591
+ PostgreSQL uses transaction lock and statement timeouts, SQLite retries busy
592
+ coordination operations only until the original call deadline, and MySQL uses
593
+ its execution timeout plus InnoDB's one-second minimum lock-wait granularity.
594
594
 
595
595
  The durable call can finish after its original caller gives up. Reauthorize and
596
596
  recover its eventual result through the durable message identity:
@@ -896,6 +896,11 @@ and marks process rows stopped on graceful shutdown. A hard-killed worker's
896
896
  claimed turn is recovered after its process heartbeat or activation lease
897
897
  becomes stale.
898
898
 
899
+ Before any role starts, the CLI loads actors from the host application's
900
+ `app/actors` directories through Rails' main autoloader. This works when
901
+ development eager loading is disabled and does not require actor references in
902
+ an initializer.
903
+
899
904
  See the [operations guide](docs/operations.md) for monitoring, reconciliation,
900
905
  shutdown, retention, and backup guidance.
901
906
 
data/docs/correctness.md CHANGED
@@ -155,10 +155,13 @@ Timeout raises `SolidObjects::SyncTimeout` but does not cancel the message.
155
155
  The exception reports actor identity, message ID and sequence, durable status,
156
156
  an earlier mailbox blocker, and activation-owner metadata without exposing
157
157
  arguments. Its `message_reference` can reauthorize and wait for the eventual
158
- result. Adapter lock/query deadlines cover the durable enqueue and coordination
159
- transactions. If enqueue cannot commit, `SyncEnqueueTimeout` is raised and no
160
- message reference exists. MySQL lock waits have one-second InnoDB granularity.
161
- Ruby handlers that already started are not preempted.
158
+ result. Adapter lock/query deadlines cover the durable enqueue, caller-process
159
+ registration and heartbeat, activation coordination, and result observation.
160
+ SQLite retries busy coordination operations only within the original call
161
+ deadline and reports `waiting_on=database_contention` when the database cannot
162
+ be inspected at timeout. If enqueue cannot commit, `SyncEnqueueTimeout` is
163
+ raised and no message reference exists. MySQL lock waits have one-second InnoDB
164
+ granularity. Ruby handlers that already started are not preempted.
162
165
 
163
166
  A synchronous call made while the Solid Objects connection already has an open
164
167
  transaction raises `SolidObjects::SyncInsideTransaction` before the message is
data/docs/operations.md CHANGED
@@ -23,6 +23,12 @@ Start all configured roles:
23
23
  bundle exec solid_objects start
24
24
  ```
25
25
 
26
+ The command loads the host application's `app/actors` directories before
27
+ starting any runtime role, even when Rails eager loading is disabled. Actors in
28
+ the conventional directory do not need initializer references. The targeted
29
+ loader participates in Rails preparation callbacks so a development reload can
30
+ replace a registered actor class without loading unrelated application code.
31
+
26
32
  Inspect process records and clean stale ownership:
27
33
 
28
34
  ```bash
@@ -13,7 +13,9 @@ module SolidObjects
13
13
  # @rbs (lease: Lease) -> void
14
14
  def initialize(lease:)
15
15
  @lease = lease
16
- instance = Instance.find(lease.instance_id)
16
+ instance = SolidObjects.database_adapter.with_lock_retry do
17
+ Instance.find(lease.instance_id)
18
+ end
17
19
  @actor_class = SolidObjects.registry.fetch(instance.actor_type)
18
20
  @actor = build_actor(instance)
19
21
  @last_used_at = monotonic_now
@@ -74,10 +76,12 @@ module SolidObjects
74
76
 
75
77
  # @rbs () -> void
76
78
  def yield_ready_messages
77
- now = SolidObjects.database_adapter.database_now
78
- ReadyMessage
79
- .where(instance_id: lease.instance_id, available_at: ..now)
80
- .update_all(available_at: now)
79
+ SolidObjects.database_adapter.transaction do
80
+ now = SolidObjects.database_adapter.database_now
81
+ ReadyMessage
82
+ .where(instance_id: lease.instance_id, available_at: ..now)
83
+ .update_all(available_at: now)
84
+ end
81
85
  end
82
86
 
83
87
  # @rbs (Hash[String, untyped]) -> void
@@ -18,7 +18,7 @@ module SolidObjects
18
18
 
19
19
  mutex.synchronize do
20
20
  existing = actors[actor_type]
21
- if existing && existing != actor_class
21
+ if existing && existing != actor_class && !reload_of?(existing, actor_class)
22
22
  raise InvalidActor, "#{actor_type.inspect} is already registered by #{existing.name}"
23
23
  end
24
24
 
@@ -61,5 +61,10 @@ module SolidObjects
61
61
 
62
62
  raise InvalidActor, "registered actor must inherit from SolidObjects::Actor"
63
63
  end
64
+
65
+ # @rbs (Class, Class) -> bool
66
+ def reload_of?(existing, candidate)
67
+ !existing.name.nil? && existing.name == candidate.name
68
+ end
64
69
  end
65
70
  end
@@ -0,0 +1,53 @@
1
+ # rbs_inline: enabled
2
+
3
+ module SolidObjects
4
+ class ApplicationActorLoader
5
+ # @rbs @application: untyped
6
+ # @rbs @autoloader: untyped
7
+
8
+ # @rbs (?application: untyped, ?autoloader: untyped) -> void
9
+ def initialize(application: Rails.application, autoloader: Rails.autoloaders.main)
10
+ @application = application
11
+ @autoloader = autoloader
12
+ end
13
+
14
+ # @rbs () -> void
15
+ def call
16
+ actor_directories.each { |directory| autoloader.eager_load_dir(directory) }
17
+ current_actor_classes.each(&:ensure_registered!)
18
+ end
19
+
20
+ # @rbs () -> void
21
+ def install
22
+ application.reloader.to_prepare { call }
23
+ call
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :application, :autoloader
29
+
30
+ # @rbs () -> Array[String]
31
+ def actor_directories
32
+ configured_directories = application.paths["app/actors"]&.existent || []
33
+ conventional_directories = application.paths["app"].existent.select do |application_directory|
34
+ File.basename(application_directory) == "actors"
35
+ end
36
+ managed_directories = autoloader.dirs.map { |directory| File.expand_path(directory) }
37
+
38
+ (configured_directories + conventional_directories)
39
+ .select { |directory| Dir.exist?(directory) }
40
+ .map { |directory| File.expand_path(directory) }
41
+ .select { |directory| managed_directories.include?(directory) }
42
+ .uniq
43
+ end
44
+
45
+ # @rbs () -> Array[Class]
46
+ def current_actor_classes
47
+ Actor.descendants.select do |actor_class|
48
+ actor_class.name &&
49
+ actor_class.name.safe_constantize.equal?(actor_class)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -52,19 +52,21 @@ module SolidObjects
52
52
  def reusable_registry?
53
53
  return false unless registry&.process_record
54
54
 
55
- registry.process_record.reload.shutdown_state == "running"
55
+ SolidObjects.database_adapter.with_lock_retry do
56
+ registry.process_record.reload.shutdown_state == "running"
57
+ end
56
58
  rescue ActiveRecord::RecordNotFound
57
59
  false
58
60
  end
59
61
 
60
62
  # @rbs () -> ProcessRegistry
61
63
  def register
62
- @registry = ProcessRegistry.new
63
- registry.register(
64
+ process_registry = ProcessRegistry.new
65
+ process_registry.register(
64
66
  kind: "caller",
65
67
  metadata: { execution: "synchronous" }
66
68
  )
67
- registry
69
+ @registry = process_registry
68
70
  end
69
71
 
70
72
  # @rbs () -> void
@@ -1,6 +1,7 @@
1
1
  # rbs_inline: enabled
2
2
 
3
3
  require "thor"
4
+ require "solid_objects/application_actor_loader"
4
5
 
5
6
  module SolidObjects
6
7
  class CLI < Thor
@@ -133,6 +134,7 @@ module SolidObjects
133
134
  end
134
135
 
135
136
  require path
137
+ ApplicationActorLoader.new.install
136
138
  end
137
139
 
138
140
  # @rbs (Symbol, Integer) -> Integer
@@ -65,27 +65,33 @@ module SolidObjects
65
65
 
66
66
  # @rbs (MessageReference, timeout: Numeric, ?authorization_context: untyped) -> untyped
67
67
  def wait(message_reference, timeout:, authorization_context: nil)
68
- message = Message.find(message_reference.id)
69
- validate_message_reference!(message_reference, message)
70
- reference = Reference.new(
71
- actor_type: message.actor_type,
72
- actor_id: message.actor_id
73
- )
74
- actor_class = SolidObjects.registry.fetch(reference.actor_type)
75
- query = actor_class.definition.queries.key?(message.message_name.to_sym)
76
- actor_message = actor_class.definition.messages.key?(message.message_name.to_sym)
77
- unless query || actor_message
78
- raise UnknownMessage, "unknown message #{message.message_name.inspect}"
68
+ SyncDeadline.with(timeout:) do
69
+ message = SolidObjects.database_adapter.with_lock_retry do
70
+ Message.find(message_reference.id)
71
+ end
72
+ validate_message_reference!(message_reference, message)
73
+ reference = Reference.new(
74
+ actor_type: message.actor_type,
75
+ actor_id: message.actor_id
76
+ )
77
+ actor_class = SolidObjects.registry.fetch(reference.actor_type)
78
+ query = actor_class.definition.queries.key?(message.message_name.to_sym)
79
+ actor_message = actor_class.definition.messages.key?(message.message_name.to_sym)
80
+ unless query || actor_message
81
+ raise UnknownMessage, "unknown message #{message.message_name.inspect}"
82
+ end
83
+ authorize!(
84
+ query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
85
+ reference,
86
+ message.message_name,
87
+ message.arguments,
88
+ authorization_context:
89
+ )
90
+ reject_sync_inside_transaction!(reference, message.message_name)
91
+ SynchronousInvocation.new.call(message_reference, timeout:)
79
92
  end
80
- authorize!(
81
- query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
82
- reference,
83
- message.message_name,
84
- message.arguments,
85
- authorization_context:
86
- )
87
- reject_sync_inside_transaction!(reference, message.message_name)
88
- SynchronousInvocation.new.call(message_reference, timeout:)
93
+ rescue DatabaseDeadlineExceeded
94
+ raise SyncDiagnostics.new.database_contention_for(message_reference, timeout:)
89
95
  end
90
96
 
91
97
  # @rbs (Reference, ?authorization_context: untyped) -> StateSnapshot
@@ -52,6 +52,16 @@ module SolidObjects
52
52
  value.is_a?(Time) ? value.utc : Time.parse("#{value} UTC").utc
53
53
  end
54
54
 
55
+ # @rbs () { () -> untyped } -> untyped
56
+ def with_lock_retry
57
+ yield
58
+ end
59
+
60
+ # @rbs () { () -> untyped } -> untyped
61
+ def with_lock_probe
62
+ yield
63
+ end
64
+
55
65
  # @rbs () { () -> untyped } -> untyped
56
66
  def transaction(&block)
57
67
  raise DatabaseDeadlineExceeded, "synchronous invocation deadline expired" if SyncDeadline.expired?
@@ -4,6 +4,8 @@ module SolidObjects
4
4
  module DatabaseAdapters
5
5
  class Sqlite < DatabaseAdapter
6
6
  LOCK_RETRY_INTERVAL = 0.001
7
+ LOCK_RETRY_MUTEX = Thread::Mutex.new
8
+ LOCK_RETRY_CONDITION = Thread::ConditionVariable.new
7
9
 
8
10
  # @rbs () -> String
9
11
  def current_time_expression
@@ -14,14 +16,51 @@ module SolidObjects
14
16
  def transaction(&block)
15
17
  return super unless SyncDeadline.active?
16
18
 
17
- super
19
+ with_lock_retry { super }
20
+ end
21
+
22
+ # @rbs () { () -> untyped } -> untyped
23
+ def with_lock_retry
24
+ return yield unless SyncDeadline.active?
25
+
26
+ raise DatabaseDeadlineExceeded, "synchronous invocation deadline expired" if SyncDeadline.expired?
27
+
28
+ with_connection do |connection|
29
+ with_transaction_deadline(connection) { yield }
30
+ end
18
31
  rescue DatabaseDeadlineExceeded
19
32
  raise if SyncDeadline.expired?
20
33
 
21
- sleep [ LOCK_RETRY_INTERVAL, SyncDeadline.remaining ].min
34
+ wait_before_retry
35
+ retry
36
+ rescue => error
37
+ raise unless deadline_error?(error)
38
+
39
+ if SyncDeadline.expired?
40
+ raise DatabaseDeadlineExceeded,
41
+ "database lock wait exceeded the synchronous invocation deadline",
42
+ cause: error
43
+ end
44
+
45
+ wait_before_retry
22
46
  retry
23
47
  end
24
48
 
49
+ # @rbs () { () -> untyped } -> untyped
50
+ def with_lock_probe
51
+ return yield unless SyncDeadline.active?
52
+
53
+ with_connection do |connection|
54
+ with_transaction_deadline(connection) { yield }
55
+ end
56
+ rescue => error
57
+ raise unless deadline_error?(error)
58
+
59
+ raise DatabaseDeadlineExceeded,
60
+ "database remained locked at the synchronous invocation deadline",
61
+ cause: error
62
+ end
63
+
25
64
  private
26
65
 
27
66
  # @rbs (untyped) { () -> untyped } -> untyped
@@ -47,6 +86,16 @@ module SolidObjects
47
86
  end
48
87
  false
49
88
  end
89
+
90
+ # @rbs () -> void
91
+ def wait_before_retry
92
+ LOCK_RETRY_MUTEX.synchronize do
93
+ LOCK_RETRY_CONDITION.wait(
94
+ LOCK_RETRY_MUTEX,
95
+ [ LOCK_RETRY_INTERVAL, SyncDeadline.remaining ].min
96
+ )
97
+ end
98
+ end
50
99
  end
51
100
  end
52
101
  end
@@ -80,18 +80,21 @@ module SolidObjects
80
80
 
81
81
  # @rbs (?kind: String, ?metadata: Hash[String | Symbol, untyped]) -> Process
82
82
  def register(kind: "worker", metadata: {})
83
- now = SolidObjects.database_adapter.database_now
84
- @process_record = Process.create!(
85
- id: SecureRandom.uuid,
86
- kind:,
87
- hostname: Socket.gethostname,
88
- pid: ::Process.pid,
89
- started_at: now,
90
- last_heartbeat_at: now,
91
- metadata: Serialization.dump(default_metadata.merge(metadata))
92
- )
83
+ process_record = SolidObjects.database_adapter.with_lock_retry do
84
+ now = SolidObjects.database_adapter.database_now
85
+ Process.create!(
86
+ id: SecureRandom.uuid,
87
+ kind:,
88
+ hostname: Socket.gethostname,
89
+ pid: ::Process.pid,
90
+ started_at: now,
91
+ last_heartbeat_at: now,
92
+ metadata: Serialization.dump(default_metadata.merge(metadata))
93
+ )
94
+ end
95
+ @process_record = process_record
93
96
  @last_heartbeat_at = monotonic_now
94
- @process_record
97
+ process_record
95
98
  end
96
99
 
97
100
  # @rbs () -> bool
@@ -99,9 +102,13 @@ module SolidObjects
99
102
  return false unless process_record
100
103
  return false if heartbeat_recent?
101
104
 
102
- process_record.update(
103
- last_heartbeat_at: SolidObjects.database_adapter.database_now
104
- ).tap { @last_heartbeat_at = monotonic_now }
105
+ updated = SolidObjects.database_adapter.with_lock_retry do
106
+ process_record.update(
107
+ last_heartbeat_at: SolidObjects.database_adapter.database_now
108
+ )
109
+ end
110
+ @last_heartbeat_at = monotonic_now if updated
111
+ updated
105
112
  end
106
113
 
107
114
  # @rbs () -> bool
@@ -10,6 +10,62 @@ module SolidObjects
10
10
  status = message_status(message)
11
11
  waiting_on = waiting_reason(message, instance, blocker)
12
12
  activation = activation_details(instance)
13
+ build_error(
14
+ message,
15
+ timeout:,
16
+ status:,
17
+ waiting_on:,
18
+ activation:,
19
+ blocker: blocker_details(blocker)
20
+ )
21
+ end
22
+
23
+ # @rbs (Message, timeout: Numeric) -> SyncTimeout
24
+ def database_contention(message, timeout:)
25
+ build_error(
26
+ message,
27
+ timeout:,
28
+ status: "unknown",
29
+ waiting_on: "database_contention",
30
+ activation: {},
31
+ blocker: nil
32
+ )
33
+ end
34
+
35
+ # @rbs (MessageReference, timeout: Numeric) -> SyncTimeout
36
+ def database_contention_for(message_reference, timeout:)
37
+ error = SyncTimeout.new(
38
+ timeout:,
39
+ actor_type: message_reference.actor_type,
40
+ actor_id: message_reference.actor_id,
41
+ message_name: "unknown",
42
+ message_id: message_reference.id,
43
+ request_id: message_reference.request_id,
44
+ sequence: message_reference.sequence,
45
+ status: "unknown",
46
+ waiting_on: "database_contention",
47
+ activation: {},
48
+ blocker: nil
49
+ )
50
+ SolidObjects.instrument(
51
+ :"sync.timeout",
52
+ message_id: message_reference.id,
53
+ request_id: message_reference.request_id,
54
+ actor_type: message_reference.actor_type,
55
+ actor_id: message_reference.actor_id,
56
+ sequence: message_reference.sequence,
57
+ status: "unknown",
58
+ waiting_on: "database_contention",
59
+ activation_owner_id: nil,
60
+ activation_generation: nil
61
+ )
62
+ error
63
+ end
64
+
65
+ private
66
+
67
+ # @rbs (Message, timeout: Numeric, status: String, waiting_on: String, activation: Hash[String, untyped], blocker: Hash[String, untyped]?) -> SyncTimeout
68
+ def build_error(message, timeout:, status:, waiting_on:, activation:, blocker:)
13
69
  error = SyncTimeout.new(
14
70
  timeout:,
15
71
  actor_type: message.actor_type,
@@ -21,7 +77,7 @@ module SolidObjects
21
77
  status:,
22
78
  waiting_on:,
23
79
  activation:,
24
- blocker: blocker_details(blocker)
80
+ blocker:
25
81
  )
26
82
  SolidObjects.instrument(
27
83
  :"sync.timeout",
@@ -38,8 +94,6 @@ module SolidObjects
38
94
  error
39
95
  end
40
96
 
41
- private
42
-
43
97
  # @rbs (Message) -> String
44
98
  def message_status(message)
45
99
  return "rejected" if message.rejected?
@@ -22,33 +22,49 @@ module SolidObjects
22
22
  # @rbs (MessageReference, timeout: Numeric) -> untyped
23
23
  def call_before_deadline(message_reference, timeout:)
24
24
  deadline = monotonic_now + SyncDeadline.remaining
25
+ message = nil
25
26
 
26
27
  loop do
27
- message = load_message(message_reference)
28
- return completed_result(message) if message.completed? || message.dead?
29
-
30
- remaining = deadline - monotonic_now
31
- unless remaining.positive?
32
- message = load_message(message_reference)
33
- return completed_result(message) if message.completed? || message.dead?
28
+ unless (deadline - monotonic_now).positive?
29
+ final_message = load_message_at_deadline(message_reference)
30
+ return completed_result(final_message) if final_message&.completed? || final_message&.dead?
34
31
 
35
- raise SyncDiagnostics.new.call(message, timeout:)
32
+ raise final_message ?
33
+ diagnose_timeout(final_message, timeout:) :
34
+ contention_timeout(message, message_reference, timeout:)
36
35
  end
37
36
 
37
+ message = load_message(message_reference)
38
+ return completed_result(message) if message.completed? || message.dead?
39
+
38
40
  processed = assist(message, deadline:)
39
41
  remaining = deadline - monotonic_now
40
42
  wait(remaining) if processed.zero? && remaining.positive?
41
43
  end
42
44
  rescue DatabaseDeadlineExceeded
43
- message = load_message(message_reference)
44
- return completed_result(message) if message.completed? || message.dead?
45
-
46
- raise SyncDiagnostics.new.call(message, timeout:)
45
+ raise message ?
46
+ diagnose_timeout(message, timeout:) :
47
+ contention_timeout(message, message_reference, timeout:)
47
48
  end
48
49
 
49
50
  # @rbs (MessageReference) -> Message
50
51
  def load_message(message_reference)
51
- Message.uncached { Message.find(message_reference.id) }
52
+ SolidObjects.database_adapter.with_lock_retry do
53
+ Message.uncached do
54
+ Message.includes(:dead_letter).find(message_reference.id)
55
+ end
56
+ end
57
+ end
58
+
59
+ # @rbs (MessageReference) -> Message?
60
+ def load_message_at_deadline(message_reference)
61
+ SolidObjects.database_adapter.with_lock_probe do
62
+ Message.uncached do
63
+ Message.includes(:dead_letter).find(message_reference.id)
64
+ end
65
+ end
66
+ rescue DatabaseDeadlineExceeded
67
+ nil
52
68
  end
53
69
 
54
70
  # @rbs (Message) -> untyped
@@ -104,6 +120,22 @@ module SolidObjects
104
120
  )
105
121
  end
106
122
 
123
+ # @rbs (Message, timeout: Numeric) -> SyncTimeout
124
+ def diagnose_timeout(message, timeout:)
125
+ SolidObjects.database_adapter.with_lock_probe do
126
+ SyncDiagnostics.new.call(message, timeout:)
127
+ end
128
+ rescue DatabaseDeadlineExceeded
129
+ SyncDiagnostics.new.database_contention(message, timeout:)
130
+ end
131
+
132
+ # @rbs (Message?, MessageReference, timeout: Numeric) -> SyncTimeout
133
+ def contention_timeout(message, message_reference, timeout:)
134
+ return SyncDiagnostics.new.database_contention(message, timeout:) if message
135
+
136
+ SyncDiagnostics.new.database_contention_for(message_reference, timeout:)
137
+ end
138
+
107
139
  # @rbs () -> Float
108
140
  def monotonic_now
109
141
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
@@ -1,5 +1,5 @@
1
1
  # rbs_inline: enabled
2
2
 
3
3
  module SolidObjects
4
- VERSION = "0.4.2"
4
+ VERSION = "0.4.3"
5
5
  end
@@ -32,5 +32,8 @@ module SolidObjects
32
32
 
33
33
  # @rbs (untyped) -> void
34
34
  def validate_actor_class!: (untyped) -> void
35
+
36
+ # @rbs (Class, Class) -> bool
37
+ def reload_of?: (Class, Class) -> bool
35
38
  end
36
39
  end
@@ -0,0 +1,30 @@
1
+ # Generated from lib/solid_objects/application_actor_loader.rb with RBS::Inline
2
+
3
+ module SolidObjects
4
+ class ApplicationActorLoader
5
+ @application: untyped
6
+
7
+ @autoloader: untyped
8
+
9
+ # @rbs (?application: untyped, ?autoloader: untyped) -> void
10
+ def initialize: (?application: untyped, ?autoloader: untyped) -> void
11
+
12
+ # @rbs () -> void
13
+ def call: () -> void
14
+
15
+ # @rbs () -> void
16
+ def install: () -> void
17
+
18
+ private
19
+
20
+ attr_reader application: untyped
21
+
22
+ attr_reader autoloader: untyped
23
+
24
+ # @rbs () -> Array[String]
25
+ def actor_directories: () -> Array[String]
26
+
27
+ # @rbs () -> Array[Class]
28
+ def current_actor_classes: () -> Array[Class]
29
+ end
30
+ end
@@ -5,10 +5,10 @@ module SolidObjects
5
5
  # @rbs (untyped) -> DatabaseAdapter
6
6
  def self.for: (untyped) -> DatabaseAdapter
7
7
 
8
- @connection_pool: untyped
9
-
10
8
  @fixed_connection: untyped
11
9
 
10
+ @connection_pool: untyped
11
+
12
12
  # @rbs (untyped) -> void
13
13
  def initialize: (untyped) -> void
14
14
 
@@ -24,6 +24,12 @@ module SolidObjects
24
24
  # @rbs () -> Time
25
25
  def database_now: () -> Time
26
26
 
27
+ # @rbs () { () -> untyped } -> untyped
28
+ def with_lock_retry: () { () -> untyped } -> untyped
29
+
30
+ # @rbs () { () -> untyped } -> untyped
31
+ def with_lock_probe: () { () -> untyped } -> untyped
32
+
27
33
  # @rbs () { () -> untyped } -> untyped
28
34
  def transaction: () { () -> untyped } -> untyped
29
35
 
@@ -5,12 +5,22 @@ module SolidObjects
5
5
  class Sqlite < DatabaseAdapter
6
6
  LOCK_RETRY_INTERVAL: ::Float
7
7
 
8
+ LOCK_RETRY_MUTEX: untyped
9
+
10
+ LOCK_RETRY_CONDITION: untyped
11
+
8
12
  # @rbs () -> String
9
13
  def current_time_expression: () -> String
10
14
 
11
15
  # @rbs () { () -> untyped } -> untyped
12
16
  def transaction: () { () -> untyped } -> untyped
13
17
 
18
+ # @rbs () { () -> untyped } -> untyped
19
+ def with_lock_retry: () { () -> untyped } -> untyped
20
+
21
+ # @rbs () { () -> untyped } -> untyped
22
+ def with_lock_probe: () { () -> untyped } -> untyped
23
+
14
24
  private
15
25
 
16
26
  # @rbs (untyped) { () -> untyped } -> untyped
@@ -18,6 +28,9 @@ module SolidObjects
18
28
 
19
29
  # @rbs (Exception) -> bool
20
30
  def deadline_error?: (Exception) -> bool
31
+
32
+ # @rbs () -> void
33
+ def wait_before_retry: () -> void
21
34
  end
22
35
  end
23
36
  end
@@ -5,8 +5,17 @@ module SolidObjects
5
5
  # @rbs (Message, timeout: Numeric) -> SyncTimeout
6
6
  def call: (Message, timeout: Numeric) -> SyncTimeout
7
7
 
8
+ # @rbs (Message, timeout: Numeric) -> SyncTimeout
9
+ def database_contention: (Message, timeout: Numeric) -> SyncTimeout
10
+
11
+ # @rbs (MessageReference, timeout: Numeric) -> SyncTimeout
12
+ def database_contention_for: (MessageReference, timeout: Numeric) -> SyncTimeout
13
+
8
14
  private
9
15
 
16
+ # @rbs (Message, timeout: Numeric, status: String, waiting_on: String, activation: Hash[String, untyped], blocker: Hash[String, untyped]?) -> SyncTimeout
17
+ def build_error: (Message, timeout: Numeric, status: String, waiting_on: String, activation: Hash[String, untyped], blocker: Hash[String, untyped]?) -> SyncTimeout
18
+
10
19
  # @rbs (Message) -> String
11
20
  def message_status: (Message) -> String
12
21
 
@@ -13,6 +13,9 @@ module SolidObjects
13
13
  # @rbs (MessageReference) -> Message
14
14
  def load_message: (MessageReference) -> Message
15
15
 
16
+ # @rbs (MessageReference) -> Message?
17
+ def load_message_at_deadline: (MessageReference) -> Message?
18
+
16
19
  # @rbs (Message) -> untyped
17
20
  def completed_result: (Message) -> untyped
18
21
 
@@ -25,6 +28,12 @@ module SolidObjects
25
28
  # @rbs (Numeric) -> void
26
29
  def wait: (Numeric) -> void
27
30
 
31
+ # @rbs (Message, timeout: Numeric) -> SyncTimeout
32
+ def diagnose_timeout: (Message, timeout: Numeric) -> SyncTimeout
33
+
34
+ # @rbs (Message?, MessageReference, timeout: Numeric) -> SyncTimeout
35
+ def contention_timeout: (Message?, MessageReference, timeout: Numeric) -> SyncTimeout
36
+
28
37
  # @rbs () -> Float
29
38
  def monotonic_now: () -> Float
30
39
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_objects
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Carlson
@@ -346,6 +346,7 @@ files:
346
346
  - lib/solid_objects/actor_registry.rb
347
347
  - lib/solid_objects/actor_snapshot.rb
348
348
  - lib/solid_objects/actor_view.rb
349
+ - lib/solid_objects/application_actor_loader.rb
349
350
  - lib/solid_objects/application_write_guard.rb
350
351
  - lib/solid_objects/broadcast_executor.rb
351
352
  - lib/solid_objects/caller_process.rb
@@ -415,6 +416,7 @@ files:
415
416
  - sig/generated/lib/solid_objects/actor_registry.rbs
416
417
  - sig/generated/lib/solid_objects/actor_snapshot.rbs
417
418
  - sig/generated/lib/solid_objects/actor_view.rbs
419
+ - sig/generated/lib/solid_objects/application_actor_loader.rbs
418
420
  - sig/generated/lib/solid_objects/application_write_guard.rbs
419
421
  - sig/generated/lib/solid_objects/broadcast_executor.rbs
420
422
  - sig/generated/lib/solid_objects/caller_process.rbs