Skip to content

Claim DO retry tokens after actor construction - #7497

Merged
apeacock1991 merged 1 commit into
mainfrom
apeacock/actor-claim-after-construction
Sep 24, 2026
Merged

apeacock1991 merged 1 commit into
mainfrom
apeacock/actor-claim-after-construction

Conversation

@apeacock1991

@apeacock1991 apeacock1991 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Durable Object fetch and JSRPC calls claimed their retry token before the event was delivered, so the claim ran before the actor constructor. This moves the claim to after construction, immediately before the handler or method is looked up. A later @retryable decorator on a fetch handler or RPC method can then skip the claim.

Fetch

The claim now runs inside the context.run() lambda in WorkerEntrypoint::requestImpl(), after getExportedHandler() and before the handler. It uses the incoming request's own metrics. IoContext::getMetrics() returns the front incoming request, which can be a different request to the same actor.

Claim and predecessor rejections no longer go through logUncaughtExceptionAsync, and an output-gate failure no longer replaces them. Predecessor rejections stay marked as not delivered.

JSRPC

JsRpcTargetBase has a new maybeClaimRetryToken() hook. callImpl() calls it after getTargetInfo() and before tryGetProperty(), so no getter runs before the claim. EntrypointJsRpcTarget claims once per session. If the claim is rejected, later top-level calls in that session rethrow the same rejection. Stubs returned from a call don't claim.

The old claim in JsRpcSessionCustomEvent::run() is gone.

JsRpcSessionCustomEvent::failed() turns a claim rejection into a disconnect, so calls pipelined on the rejected call don't see it as an application error. An in-process rejection now fails the call rather than the session, so maybeClaimRetryToken() applies the same translation. Both share disconnectRetryClaimRejection().

Behaviour changes

  • A JSRPC session that makes no calls no longer claims. The constructor may still run.
  • Once JSRPC enforcement is on, a rejected duplicate that lands on a fresh instance runs the constructor before it is rejected.
  • An in-process JSRPC claim rejection now also carries REQUEST_DELIVERED_TO_ACTOR_DETAIL_ID. The sender checks ACTOR_RETRY_CLAIM_REJECTED_DETAIL_ID first, so retry decisions don't change.

@apeacock1991
apeacock1991 requested review from a team as code owners September 24, 2026 14:46
@ask-bonk

ask-bonk Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

LGTM

github run

@apeacock1991
apeacock1991 force-pushed the apeacock/actor-claim-after-construction branch 2 times, most recently from e081705 to eb51a8d Compare September 24, 2026 15:50
Durable Object fetch and JSRPC claimed the retry token before
delivering the event, so the claim ran before the actor constructor.
The claim now runs after construction and immediately before handler
or method lookup. A later @retryable decorator can then inspect the
constructed actor's handler or method and skip the claim.

Fetch claims inside the context.run() lambda in
WorkerEntrypoint::requestImpl(), after getExportedHandler() and before
the handler runs. It uses the incoming request's own metrics.
IoContext::getMetrics() returns the front incoming request, which can
be a different request to the same actor. The plan put this claim in
global-scope.c++. requestImpl() is simpler and has the right metrics.

JSRPC claims in the callImpl() dispatch lambda, after getTargetInfo()
and before tryGetProperty(), through a new
JsRpcTargetBase::maybeClaimRetryToken() hook. EntrypointJsRpcTarget
claims once per session. A rejection is sticky, so a later top-level
call rethrows it. Stubs returned by a call do not claim.

JsRpcSessionCustomEvent::failed() turns a claim rejection into a
disconnect, so calls pipelined on the rejected call don't surface it
as an application error. An in-process rejection now fails the call
instead of the session, so maybeClaimRetryToken() applies the same
translation. Both use disconnectRetryClaimRejection().

Claim and predecessor rejections skip logUncaughtExceptionAsync, and
an output-gate failure no longer replaces them. Predecessor rejections
stay marked as not delivered.

Behaviour changes:

- A JSRPC session with no calls no longer claims. The constructor may
  still run, but no requested method does.
- While JSRPC claims are only observed, callers see no difference.
  Once enforcement is on, a rejected duplicate that lands on a fresh
  instance runs the constructor before it is rejected.
- An in-process JSRPC claim rejection now also carries
  REQUEST_DELIVERED_TO_ACTOR_DETAIL_ID. The sender checks
  ACTOR_RETRY_CLAIM_REJECTED_DETAIL_ID first, so retries are
  unaffected.
@apeacock1991
apeacock1991 force-pushed the apeacock/actor-claim-after-construction branch from eb51a8d to 354a44c Compare September 24, 2026 18:43
@apeacock1991
apeacock1991 merged commit 84338b9 into main Sep 24, 2026
23 checks passed
@apeacock1991
apeacock1991 deleted the apeacock/actor-claim-after-construction branch September 24, 2026 19:53
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.

2 participants