Skip to content

Fix lease renewal and preserve claimed fences - #89

Merged
cardmagic merged 2 commits into
mainfrom
fix/activation-lease-recovery
Oct 12, 2026
Merged

cardmagic merged 2 commits into
mainfrom
fix/activation-lease-recovery

Conversation

@cardmagic

@cardmagic cardmagic commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Two activation-lease defects were independently found and reported, prompting a parity audit of the Ruby runtime. Ruby already renews retained activations across worker passes and can recover claims after a cleared expiry, but a drain pass still relied on the renewal thread and Lease#release could clear a fence while its message claim remained.

Renew between turns when due, preserving the existing renewal thread for long handlers. Do not release a matching activation while it has a claimed message; the next pass acquires a new generation and recovers the expired turn. Keep all existing owner, token, generation, and expiry checks. Avoid an extra database-clock read before the first turn, preserving the synchronous invocation query-count contract.

No schema, setting, default, or public API changes. Prepares version 0.17.4 in lib/solid_objects/version.rb and Gemfile.lock, with dated changelog notes. Tag v0.17.4 after merging; no tag or publication is included. Reclaiming continues to increment attempt_count from 1 to 2; the recovered message can complete with max_attempts: 1. Lease loss alone does not create a dead letter, and later messages stay behind the recovered message. Real handler errors retain the existing attempt-limit behavior. Existing Ruby worker.error logging and recovery.reclaimed instrumentation remain unchanged.

Regression evidence, collected before the fix and again with the fix removed:

Regression Before After
34 short worker passes spanning five lease durations Pass Pass
34 short turns in a drain without a renewal thread LostActivation: activation lease expired Pass; one generation and advancing expiry
Retain the expired fence while its claim remains Expected nil to not be nil (expiry cleared) Pass; next pass recovers in order
Same-worker recovery and mailbox order with max_attempts: 1 Pass Pass
Second worker takeover before stale worker returns Pass Pass; stale worker cannot release replacement lease

The tests advance the adapter clock deterministically; the draining test uses the real Activation without starting a renewal thread. The takeover runs the replacement worker on another thread so the original actor's application-write guard does not apply to that worker.

Validation:

  • Release preparation: reviewed docs/virtual-actors.md and docs/agents.md against the release; no requirement or guarantee corrections needed. Re-ran bundle exec rake TESTOPTS=--seed=18761 (926 tests, 3725 assertions, zero failures/errors, 28 conditional skips, all static/security gates passed) and gem build solid_objects.gemspec (version 0.17.4).

  • Five new tests, 211 assertions, passed on SQLite, PostgreSQL, mysql2, and Trilogy; zero skips in these focused runs.

  • bundle exec rake TESTOPTS=--seed=18761: 926 tests / 3725 assertions, zero failures or errors, 28 conditional skips; Standard, RuboCop, RBS generation/validation, Steep, and Brakeman passed, with zero security warnings.

  • An existing SQLite timeout test also raises SQLite3::CantOpenException while reopening WAL mode on the unchanged baseline; its independent reproduction is recorded in the local review evidence.

  • Inspected effect executor, reminder scheduler, and broadcast executor: no cached actor renewal deadline or activation-expiry recovery predicate. Their claims use item state and process/claim staleness, so these two patterns require no changes there.

JavaScript parity: cardmagic/solid-objects-js#72. The Ruby changelog and roadmap are updated.

Apply the parity repair for independently reported lease defects. Renew
between drain turns when due, without relying on the renewal thread, and
keep the activation fence while a message claim remains.

Before the fix, regressions reported an expired lease during draining and
a cleared expiry with an outstanding claim. Confirm renewal, recovery
with max_attempts: 1, and replacement-owner fencing on SQLite,
PostgreSQL, mysql2, and Trilogy. Preserve existing cross-pass renewal and
NULL-expiry claim recovery behavior.
@context7

context7 Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Docs7 for cardmagic/solid-objects-ruby

Result Status Action
Deployment ➖ Not used —
Content review ✅ Passed. No problems found. View findings

Commit 6db0de8

@greptile-apps

greptile-apps Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High impact] The changes since the previous review appear safe to merge.

Summary

The PR renews activation leases between turns and keeps the lease fence while a message claim remains.

  • Drains renew leases between turns when renewal is due.
  • An outstanding message claim keeps its activation fence in place.
  • The gem and lockfile identify version 0.17.4.

Reviews (2) · Last reviewed commit: "chore: prepare version 0.17.4" · Reviewed by Greptile

Align the gem version and lockfile and date the activation lease release
notes. Review the consumer guides and validate the complete suite and
built gem before preparing the release for tagging after merge.

See #89
@cardmagic

Copy link
Copy Markdown
Owner Author

@greptileai Please review current head 6db0de8, which prepares version 0.17.4. The gem version, lockfile, and dated changelog match. Consumer guides were reviewed against the release. Full bundle exec rake validation passed (926 tests, 3725 assertions, no failures/errors), and gem build produced version 0.17.4. No tag or publication has been made.

@cardmagic
cardmagic merged commit f5f6242 into main Oct 12, 2026
44 checks passed
@cardmagic
cardmagic deleted the fix/activation-lease-recovery branch October 12, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant