diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c21e7..92b679d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +- State where `async` waits when no worker runs. The `async` section of the + README and the runtime section of `docs/operations.md` now say that the + generator and the migrations start no role, so an application that serves + web requests alone leaves the message ready until + `bundle exec solid_objects start` runs the roles. The message is durable + and waits; it is not lost. `test/integration/background_pickup_test.rb` + pins it: the message reads `ready` and the actor state stays empty until a + worker runs. This matches solid-objects-js#22, which reported the same gap + for `runtime.run(signal)` in the Node package. + - Add `examples/at_least_once` and `bundle exec rake at_least_once`, an executable proof that the at-least-once clause fires and that the documented remedy absorbs it. One actor turn stages an effect that writes diff --git a/README.md b/README.md index 6748081..69050f1 100644 --- a/README.md +++ b/README.md @@ -662,6 +662,12 @@ message = order.async( ).submit ``` +`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](#worker-requirements) for the feature-by-role table. + Use `available_at:` to spread bulk work or delay one message: ```ruby diff --git a/docs/operations.md b/docs/operations.md index 30d7e90..f1dd8dc 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -31,6 +31,13 @@ Start all configured roles: bundle exec solid_objects start ``` +The generator and the migrations prepare the database and start nothing. A +process claims ready messages only after this command starts its roles, so an +application that serves web requests alone leaves every `async` message ready. +The message is durable and waits for the first process that runs the roles. A +direct call or an explicit `sync` needs no running role, because the caller's +own path executes it. + The command loads the host application's `app/actors` directories before starting any runtime role, even when Rails eager loading is disabled. Actors in the conventional directory do not need initializer references. The targeted diff --git a/test/integration/background_pickup_test.rb b/test/integration/background_pickup_test.rb new file mode 100644 index 0000000..1668244 --- /dev/null +++ b/test/integration/background_pickup_test.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "database_test_helper" + +class BackgroundPickupTest < ActiveSupport::TestCase + class MailboxActor < SolidObjects::Actor + actor_type "background-pickup-mailbox" + + attribute :delivered, default: 0 + + def receive + self.delivered += 1 + end + end + + test "leaves an async message ready until a worker runs the roles" do + 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 + begin + worker.run_until_idle + ensure + worker.stop + end + + assert_equal "completed", message.status + assert_equal( + { "delivered" => 1 }, + SolidObjects::Instance.find_by!(actor_type: "background-pickup-mailbox", actor_id: "inbox").state + ) + end +end