diff --git a/docs/portal/build/workflows-activities.md b/docs/portal/build/workflows-activities.md index 64c390ce..4db697b9 100644 --- a/docs/portal/build/workflows-activities.md +++ b/docs/portal/build/workflows-activities.md @@ -51,6 +51,20 @@ $worker->registerWorkflow( The sequence, type, and details of context operations are durable. Changing them while old histories are still running can raise `NonDeterministicWorkflow`. Deploy replay-compatible code or drain incompatible histories before removing an old branch. +## Keep invocation state local + +Create execution-local variables and temporary mutable objects inside the +workflow method. Workflow replay reconstructs durable values through `WorkflowContext`. +Apply the same rule to queries and updates. + +The released SDK shallow-clones attributed workflow handlers without rerunning +their constructors. Constructor-created nested objects and referenced +properties can remain shared across workflows and replay attempts. Do not use +them, injected services, static properties or captured shared objects for +execution-local mutable state. This handler-lifetime limitation remains open in +[#16](https://github.com/durable-workflow/sdk-php/issues/16); the proposed +fresh-construction correction is not released. + ## Call a registered service This authoring API is introduced in the PHP SDK 2.3 prerelease. Install it with diff --git a/docs/protocol.md b/docs/protocol.md index e9c58025..718b3853 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -100,13 +100,18 @@ interleaved workflow executions cannot consume one another's execution state. Generator-returning workflow handlers are not part of the 2.0 service-mode contract. -Attribute-discovered workflow entry points, queries, and updates receive a fresh +Attribute-discovered workflow entry points, queries, and updates receive a shallow clone of the registered workflow handler template for every invocation. -The template retains constructor-injected collaborators, but mutable properties -on the workflow handler always begin at their registration-time values. Activity -handlers are different: the worker invokes the resolved activity service -instance directly across tasks. Low-level callable registrations likewise keep -the lifetime and captured state of the callable supplied by the application. +The SDK does not rerun the constructor. Nested objects and referenced properties can remain +shared under [PHP's cloning rules](https://www.php.net/manual/en/language.oop5.cloning.php), +including constructor-created state and injected collaborators. This does not +provide complete workflow-state isolation. Keep execution-local state in method +variables and construct temporary objects inside each invocation. The remaining +handler-lifetime defect is tracked in [#16](https://github.com/durable-workflow/sdk-php/issues/16). + +Activity handlers use the resolved activity service instance directly across +tasks. Low-level callable registrations keep the lifetime and captured state +of the callable supplied by the application. The runtime fetches all paginated history before replay. Activity handlers can heartbeat and observe cancellation. Query handlers receive immutable committed diff --git a/docs/sdk-reference.md b/docs/sdk-reference.md index 0505e645..fcfb0ea0 100644 --- a/docs/sdk-reference.md +++ b/docs/sdk-reference.md @@ -440,22 +440,25 @@ required constructor arguments are instantiated automatically. Pass any PSR-11 `ContainerInterface` as the third argument to `Worker::create()` when handlers have application dependencies. -Attributed workflow classes have a replay-scoped lifecycle. Registration -captures a clean handler template, then each workflow task replay, query, and -update runs on a fresh shallow clone. Mutable properties on the workflow object -therefore cannot cross workflow IDs, runs, or replay attempts, while -constructor-injected collaborators retain their configured identity. Keep -workflow-local mutable state directly on the handler; injected collaborators -are shared services and must not be used to hold execution-local state. -Workflow handler classes must remain cloneable. +Registration retains a workflow handler template. Each attributed workflow task +replay, query, and update runs on a shallow clone of it. The constructor is not +rerun, and shallow cloning does not automatically isolate nested objects or referenced properties. +Mutating constructor-created object state can therefore affect another workflow +ID, run or replay. This limitation remains open in +[#16](https://github.com/durable-workflow/sdk-php/issues/16). + +Keep workflow-local state in method variables. Create temporary mutable objects +inside each workflow, query or update invocation. During workflow replay, +reconstruct durable values through `WorkflowContext`. Injected collaborators retain their configured +identity and must not hold execution-local state. Workflow handler classes must +remain cloneable. The proposed fresh-construction API in #111 is not released. Activity services have worker-scoped lifetimes instead: their resolved instance is reused for activity tasks, so they can retain clients and other service resources. The explicit `registerWorkflow()`, `registerQuery()`, and `registerUpdate()` low-level methods also invoke the supplied callable as-is; -state captured by such a callable remains owned by the application. Use -attribute-based workflow registration when the SDK should provide replay-state -isolation. +state captured by such a callable remains owned by the application. Neither +registration style makes shared mutable objects safe workflow-local storage. Pass a PSR-3 `LoggerInterface` with the named `logger` argument; lifecycle, retry, shutdown, and handler failures then use the application's normal logging