{"record":{"id":"86947b99916365ea","repo":"OrchardCMS/OrchardCore","slug":"the-token-was-concurrently-updated-and-cannot-be-persisted","errorCode":null,"errorMessage":"The token was concurrently updated and cannot be persisted in its current state.\nReload the token from the database and retry the operation.","messagePattern":"The token was concurrently updated and cannot be persisted in its current state\\.\nReload the token from the database and retry the operation\\.","errorType":"exception","errorClass":"OpenIddictExceptions.ConcurrencyException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore/OrchardCore.OpenId.Core/YesSql/Stores/OpenIdTokenStore.cs","lineNumber":687,"sourceCode":"        return default;\n    }\n\n    /// <inheritdoc/>\n    public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(token);\n\n        cancellationToken.ThrowIfCancellationRequested();\n\n        await _session.SaveAsync(token, checkConcurrency: true, collection: OpenIdCollection, cancellationToken: cancellationToken);\n\n        try\n        {\n            await _session.FlushAsync(cancellationToken);\n        }\n        catch (ConcurrencyException exception)\n        {\n            throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()\n                .AppendLine(\"The token was concurrently updated and cannot be persisted in its current state.\")\n                .Append(\"Reload the token from the database and retry the operation.\")\n                .ToString(), exception);\n        }\n    }\n}\n","sourceCodeStart":669,"sourceCodeEnd":694,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.OpenId.Core/YesSql/Stores/OpenIdTokenStore.cs#L669-L694","documentation":"OpenIddict's token store wraps YesSql optimistic-concurrency failures (ConcurrencyException from ISession.FlushAsync) into an OpenIddictExceptions.ConcurrencyException. It means another request or process modified the same token document between this store's read and write, so YesSql rejected the update to protect consistency. The operation is intentionally not retried automatically; the caller is expected to reload the token and redo the change.","triggerScenarios":"Calling OpenIdTokenStore.UpdateAsync (typically indirectly through OpenIddict's token manager during token revocation, redemption, or extension) while a concurrent request updates the same token row, causing _session.FlushAsync to throw ConcurrencyException at commit time.","commonSituations":"Two simultaneous requests redeeming or revoking the same refresh token; multiple tenants/instances behind a load balancer touching the same token; background token-pruning or cleanup racing with an active authorization flow; long-running flows holding a session open while another request commits first.","solutions":["Retry the operation: reload the token via the OpenIddict token manager (or TryRevokeAsync/TryRedeemAsync, which already tolerate concurrency) and re-apply the update.","Prefer OpenIddict manager-level APIs (ITokenManager.TryRevokeAsync/TryRedeemAsync/TryPruneAsync) instead of raw store updates; they catch ConcurrencyException and return false instead of throwing.","Reduce concurrent writes to the same token by avoiding redundant revocation/redemption calls and shortening request lifetime.","If it happens persistently, check for duplicate token issuance (e.g., same refresh token used from multiple clients/instances) and enable single-use refresh tokens consistently."],"exampleFix":"// before: raw update that throws on concurrent writes\nvar token = await tokenManager.FindByReferenceIdAsync(refreshToken);\nawait tokenManager.TryRevokeAsync(token); // may surface ConcurrencyException\n\n// after: atomic tolerant call\nvar token = await tokenManager.FindByReferenceIdAsync(refreshToken);\nif (!await tokenManager.TryRevokeAsync(token))\n{\n    // token was concurrently revoked/redeemed; reload and decide\n}","handlingStrategy":"retry","validationCode":"// No pre-call validation possible; verify the token's current state right before writing:\nvar token = await tokenManager.FindAsync(tokenId, ct);\nif (token == null) { /* already gone; skip update */ }","typeGuard":null,"tryCatchPattern":"try\n{\n    await tokenManager.UpdateAsync(token, ct);\n}\ncatch (OpenIddictExceptions.ConcurrencyException)\n{\n    token = await tokenManager.FindAsync(tokenId, ct); // reload\n    // re-apply change or give up; do not blindly retry forever\n}","preventionTips":["Use ITokenManager.TryRevokeAsync/TryRedeemAsync which swallow concurrency conflicts.","Keep sessions short-lived; avoid holding a token across long operations before saving.","Avoid updating the same token from multiple parallel requests.","Monitor logs for frequent conflicts — they usually indicate duplicate token usage by clients."],"tags":["concurrency","database","openid","optimistic-locking"],"backgroundTag":"database-write-failed","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}