Open Closed

AbpDbConcurrencyException in AbpOpenIddictTokenStore.UpdateAsync/CreateAsync, ~100+/day at fixed minute marks (ABP 10.4.1) #10925


User avatar
0
mohmmd.khalaf@gmail.com created

Exception message and full stack trace:

ABP 10.4.1, .NET 10, EF Core 10 (SQL Server), ABP React UI. The OpenIddict token store throws AbpDbConcurrencyException about 50 times a day. Nothing from our application code is in the stack trace.

[ERR] The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded.
Volo.Abp.Data.AbpDbConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); ...
---> Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: (same message)
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(...)
   ...
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(...)
   at Volo.Abp.EntityFrameworkCore.AbpDbContext`1.SaveChangesAsync(...)
   at Volo.Abp.Domain.Repositories.EntityFrameworkCore.EfCoreRepository`2.UpdateAsync(TEntity entity, Boolean autoSave, ...)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](...)
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.OpenIddict.Tokens.AbpOpenIddictTokenStore.UpdateAsync(OpenIddictTokenModel token, ...)

Related failed bulk statement at 00:13:52, which looks like the revoke-by-authorization query:

UPDATE [o] SET [o].[Status] = @p
FROM [OpenIddictTokens] AS [o]
WHERE [o].[AuthorizationId] = @id

Observed in one day of production logs (2026-10-18, 00:00–09:31 +03:00):

  • 50 errors from UpdateAsync, 2 from CreateAsync, 3–6 per hour, steady through the night.
  • They recur at the same minute and second each hour (hh:22:48, hh:24:18, about hh:05:27, hh:36:04). That suggests a scheduled client, or an ABP background worker, not user traffic.
  • At least once (07:29:56) the exception surfaced as an unhandled request exception.

Steps to reproduce the issue:

I can't reproduce it on demand. It appears only in production, at the fixed times above. [Add anything you find.] Suggested attempt: send two parallel grant_type=refresh_token requests with the same refresh token to /connect/token.

Expected: on a concurrent update, the store reports the conflict to OpenIddict as OpenIddictExceptions.ConcurrencyException, which OpenIddict turns into invalid_grant. Actual: AbpDbConcurrencyException escapes and is logged as an error.

Questions:

  1. Is this a known issue in 10.4.x? Is there a fix or workaround?
  2. Should AbpOpenIddictTokenStore.UpdateAsync/CreateAsync convert AbpDbConcurrencyException into OpenIddictExceptions.ConcurrencyException?
  3. Could the token cleanup worker or refresh-token reuse handling explain the hourly pattern? Which options control them?
Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

3 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • What you’re seeing is a real EF Core optimistic-concurrency race inside the OpenIddict token persistence flow (typically: two parallel operations touching the same token(s), e.g., refresh-token reuse/redeem + revoke-by-authorization). EF Core then throws DbUpdateConcurrencyException, and ABP wraps it as AbpDbConcurrencyException.
      The problematic part in your case is that the ABP exception is not being converted back into OpenIddict’s expected OpenIddictExceptions.ConcurrencyException, so it can bubble up and be logged as an application error (and sometimes surface as unhandled if it escapes the /connect/* pipeline).

    • There is a closely related, confirmed ABP issue in the /connect pipeline where the request UOW could complete after the response is flushed, causing timing/race problems around OpenIddict/Identity Session persistence; ABP fixed that by completing the request UOW before flushing /connect responses (included in later versions; PR below). While that specific ticket is about AbpSessions, it demonstrates ABP has been actively fixing /connect-pipeline timing/race behaviors, and your symptoms (fixed “minute marks”, steady background traffic, and concurrency exceptions in OpenIddict store) fit the same class of problems: background/automated clients + token rotation/revocation + concurrent writes.

    1) Known issue / fix / workaround

    Verified fix exists for /connect request timing (commit-before-flush) and is merged here:

    • https://github.com/abpframework/abp/pull/26017 (validated)

    If you can, the most definitive mitigation is:

    1. Upgrade ABP to a version that includes that PR (post-10.5.0, per the linked support thread).
    2. After upgrade, remove any custom workaround you add for commit timing.

    If you cannot upgrade immediately, the practical workaround pattern (already validated in ABP support) is to force the relevant OpenIddict work to be committed inside a new UnitOfWork earlier in the OpenIddict event pipeline (so the DB state is consistent/visible before other concurrent operations proceed). The support ticket shows this pattern for identity sessions; you can apply the same “commit now” idea to the part of the pipeline that creates/updates tokens in your scenario.

    A second (often necessary) mitigation is at the client/API gateway level:

    • Ensure your clients never send parallel refresh-token requests using the same refresh token (serialize refresh calls per user/session), because refresh token rotation + parallel refresh is the canonical way to produce token-store concurrency conflicts.

    2) Should AbpOpenIddictTokenStore.UpdateAsync/CreateAsync translate to OpenIddictExceptions.ConcurrencyException?

    Yes: OpenIddict’s server pipeline expects concurrency failures from the store to be surfaced as OpenIddictExceptions.ConcurrencyException so it can translate them into protocol-appropriate responses (typically invalid_grant for refresh flows). If ABP lets AbpDbConcurrencyException escape, it will be logged as an infrastructure error and can leak as 500/unhandled depending on where it’s caught.

    I couldn’t verify the exact AbpOpenIddictTokenStore implementation in source because the code-search tool backend was unavailable in this environment, so I can’t point to the exact catch/translate location in ABP 10.4.1. However, based on the observed behavior and OpenIddict conventions, this translation is the correct behavior and aligns with how OpenIddict stores are expected to report concurrency issues.

    3) Could cleanup worker / refresh-token reuse explain hourly fixed pattern? Which options control them?

    The fixed minute marks strongly suggest a scheduled/non-human client (mobile app token refresh loop, kiosk, integration service, health-check identity probe, etc.) rather than ABP’s own token pruning:

    • OpenIddict’s built-in pruning typically runs on an interval, but your timestamps repeating at very specific offsets each hour is more characteristic of one or more clients refreshing tokens on a schedule.
    • The SQL you captured (UPDATE ... WHERE AuthorizationId = @id) matches a “revoke tokens by authorization” style operation, which is commonly triggered by refresh token rotation/reuse detection or authorization revocation flows.

    What to do operationally:

    1. Correlate the errors with client_id / subject / authorization_id involved at those times (enable higher OpenIddict/EF logging around failures).
    2. Identify the scheduled actor (job, integration, device fleet).
    3. Enforce single-flight refresh per subject/client (mutex in client; or server-side distributed lock keyed by (client_id, subject) for refresh endpoint).

    Sources

    • https://abp.io/support/questions/10853 (validated)
    • https://github.com/abpframework/abp/pull/26017 (validated)
    • https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work (validated)
    • https://abp.io/docs/10.0/release-info/migration-guides/abp-10-0 (validated)

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    mohmmd.khalaf@gmail.com created

    Thanks. To confirm: PR 26017 / thread 10853 concern AbpSessions, not OpenIddictTokens. Could you confirm whether the change affects AbpOpenIddictTokenStore at all?

    The remaining question is the one you could not verify. Does AbpOpenIddictTokenStore.UpdateAsync/CreateAsync catch AbpDbConcurrencyException and rethrow OpenIddictExceptions.ConcurrencyException in 10.4.1 or in any later version? If not, can this be treated as a bug and tracked in the ABP repo?

    We will add single-flight refresh on the client side and log client_id / authorization_id at the failure points, then report back.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    ABP 10.4.1 has a bug here. When a token is created or updated, ABP also updates the ConcurrencyStamp of the related OpenIddictAuthorization if it's loaded in the same DbContext. So concurrent token requests under the same authorization can conflict and throw AbpDbConcurrencyException.

    It's fixed in 10.6.0: https://github.com/abpframework/abp/pull/25824

    For 10.4.1, you can add the same change to ConfigureServices of the module of your app that handles /connect/token:

    using Volo.Abp.Domain.Entities.Events;
    using Volo.Abp.OpenIddict.Authorizations;
    
    Configure<AbpEntityChangeOptions>(options =>
    {
        options.IgnoredNavigationEntitySelectors.Add("DisableOpenIddictAuthorization", type => type == typeof(OpenIddictAuthorization));
    });
    

    You can remove it after upgrading to 10.6.0 or later.

    AbpOpenIddictTokenStore.UpdateAsync already converts the exception to OpenIddictExceptions.ConcurrencyException, but it logs the original error first. So if two requests really update the same token at the same time, you'll still see this error in the logs. That's expected.

    If you still get unhandled exceptions after this change, please share the full exception and the request path of one of them.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on October 16, 2026, 14:19
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.