Skip to content

docs: say where async waits with no worker - #52

Merged
cardmagic merged 1 commit into
mainfrom
docs/require-run-for-pickup
Aug 24, 2026
Merged

docs: say where async waits with no worker#52
cardmagic merged 1 commit into
mainfrom
docs/require-run-for-pickup

Conversation

@cardmagic

Copy link
Copy Markdown
Owner

The Ruby counterpart of
solid-objects-js#27,
which closes
solid-objects-js#22.

Why

The JS issue came from an external prober who built a two-process harness from
the README, watched ready messages sit unclaimed for 30 seconds, and concluded
messages were stranded. Nothing was wrong with the runtime. The page never said
that installing starts no role.

The gem is in better shape than the Node package was. ## Worker requirements
already carries the feature-by-role table and the exact sentence a worried
reader needs:

A missing worker never makes a durable async message disappear, but it
leaves the message pending indefinitely.

What was missing is the pointer at the point of use. A reader who jumps to
### async from the table of contents reads only that execution is "left to
the worker fleet," which does not tell them a fleet has to exist, or what
happens meanwhile.

What changed

README, in the async section:

async needs a running actor worker. Installing the engine and migrating the
schema starts no role, so a process that only serves web requests leaves the
message ready. Nothing is lost. The message waits until
bundle exec solid_objects start runs the roles. See Worker requirements for
the feature-by-role table.

docs/operations.md, in the runtime section: the same claim, plus the
boundary that makes it make sense. A direct call or an explicit sync needs no
running role, because the caller's own path executes it.

Keeping the sentence honest

test/integration/background_pickup_test.rb pins the documented behavior:

message = MailboxActor.ref("inbox").async.receive

assert_equal "ready", message.status
assert_empty SolidObjects::Instance.find_by!(
  actor_type: "background-pickup-mailbox", actor_id: "inbox"
).state

worker = SolidObjects::Worker.new
worker.run_until_idle

assert_equal "completed", message.status

A test that only ever sees ready proves nothing, so I checked that the first
assertion detects pickup. Running a worker before it turns the test red:

Failure:
BackgroundPickupTest#test_leaves_an_async_message_ready_until_a_worker_runs_the_roles:
Expected: "ready"
  Actual: "completed"

One detail worth recording, because it surprised me while writing the test: the
enqueue does create the instance row, since it locks the instance to allocate
the mailbox sequence. So "nothing ran yet" shows up as empty state, not as a
missing row. The first draft asserted a missing row and failed:

Expected #<SolidObjects::Instance id: 1, actor_type: "background-pickup-mailbox",
actor_id: "inbox", state: {}, state_version: 1, next_message_sequence: 2, ...> to be nil.

Effects

  • API: none.
  • Correctness: none. The test asserts existing behavior.
  • Security, migration, compatibility: none.

docs/roadmap.md is untouched, per AGENTS.md: this edits prose and adds a test
rather than moving what the project claims about itself.

Validation

  • bundle exec rake (test, standard, rubocop, rbs, steep, security): pass.
  • Mutation check on the new test, quoted above.

solid-objects-js#22 reported that its README never said a process claims
nothing until the roles start. An external prober built a two-process
harness from that page and read the unclaimed messages as stranded. The
gem states the rule in Worker requirements, but the async section, which
is where a reader actually is when the question occurs to them, only said
execution is left to the worker fleet.

The async section now says the generator and the migrations start no
role, so an application that serves web requests alone leaves the
message ready until solid_objects start runs the roles, and points at
the feature-by-role table. The runtime section of the operations guide
says the same, with the boundary that makes it make sense: a direct call
or an explicit sync needs no running role.

test/integration/background_pickup_test.rb keeps it honest. The message
reads ready and the actor state stays empty until a worker runs, then
reads completed. Running a worker before the first assertion turns it
red, so the assertion detects pickup rather than asserting a constant.

The enqueue does create the instance row, because it allocates the
mailbox sequence, so the test asserts on empty state rather than on a
missing row.
@cardmagic
cardmagic merged commit 68f340f into main Aug 24, 2026
40 checks passed
@cardmagic
cardmagic deleted the docs/require-run-for-pickup branch August 24, 2026 23:09
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