OrchardCMS/OrchardCore · error · OpenIddictExceptions.ConcurrencyException
The token was concurrently updated and cannot be persisted…
Error message
The token was concurrently updated and cannot be persisted in its current state. Reload the token from the database and retry the operation.
What it means
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.
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.
Example fix
// before: raw update that throws on concurrent writes
var token = await tokenManager.FindByReferenceIdAsync(refreshToken);
await tokenManager.TryRevokeAsync(token); // may surface ConcurrencyException
// after: atomic tolerant call
var token = await tokenManager.FindByReferenceIdAsync(refreshToken);
if (!await tokenManager.TryRevokeAsync(token))
{
// token was concurrently revoked/redeemed; reload and decide
} Defensive patterns
Strategy: retry
Validate before calling
// No pre-call validation possible; verify the token's current state right before writing:
var token = await tokenManager.FindAsync(tokenId, ct);
if (token == null) { /* already gone; skip update */ } Try / catch
try
{
await tokenManager.UpdateAsync(token, ct);
}
catch (OpenIddictExceptions.ConcurrencyException)
{
token = await tokenManager.FindAsync(tokenId, ct); // reload
// re-apply change or give up; do not blindly retry forever
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- The ' ' could not be persisted and cached as it has been…
- Invalid serial number for shell descriptor
- The application was concurrently updated and cannot be…
- The authorization was concurrently updated and cannot be…
- The scope was concurrently updated and cannot be persisted…
AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13).
Data as JSON: /api/errors/86947b99916365ea.
Report an issue: GitHub.
Appendix: source
Thrown at src/OrchardCore/OrchardCore.OpenId.Core/YesSql/Stores/OpenIdTokenStore.cs:687
return default;
}
/// <inheritdoc/>
public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(token);
cancellationToken.ThrowIfCancellationRequested();
await _session.SaveAsync(token, checkConcurrency: true, collection: OpenIdCollection, cancellationToken: cancellationToken);
try
{
await _session.FlushAsync(cancellationToken);
}
catch (ConcurrencyException exception)
{
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
.AppendLine("The token was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the token from the database and retry the operation.")
.ToString(), exception);
}
}
}
View on GitHub (pinned to 4306c0717f)