Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logout Notifications can result in concurrent access to underlying DbContext #1627

Open
mackie1001 opened this issue Nov 15, 2024 · 3 comments
Labels
area/identity-server Related to Identity Server
Milestone

Comments

@mackie1001
Copy link

Which version of Duende IdentityServer are you using?
7.0.7

Which version of .NET are you using?
8.0.8

Describe the bug
We've observed in our logs occasional exceptions being thrown from within DefaultBackChannelLogoutService.SendLogoutNotificationsAsync that relate to multi-threaded access to the operational store DbContext (see stack trace below).
This appears to be due to that method doing a Task.WhenAll for the collection of logout requests and the code that generates the logout token uses the key management implementation with sometimes needs to fetch the key material from the DB. The concurrent executions all share the instance DbContext and thus the error.

To Reproduce
Observed under production load but theoretically:

  • Have a session with multiple clients with back channel endpoints registered
  • Wait until the key management implementation cache expires
  • Execute DefaultBackChannelLogoutService.SendLogoutNotificationsAsync for said session

Expected behavior

The implementation is either changed to await each request in turn or the scoping of the underlying services is revised to avoid this situation when executing requests in parallel.

Log output/exception with stacktrace

System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.\r\n at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable1.AsyncEnumerator.MoveNextAsync()\r\n at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)\r\n at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)\r\n at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToArrayAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)\r\n at Duende.IdentityServer.EntityFramework.Stores.SigningKeyStore.LoadKeysAsync() in //src/EntityFramework.Storage/Stores/SigningKeyStore.cs:line 64\r\n at Duende.IdentityServer.Services.KeyManagement.KeyManager.GetAllKeysFromStoreAsync(Boolean cache) in //src/IdentityServer/Services/Default/KeyManagement/KeyManager.cs:line 428\r\n at Duende.IdentityServer.Services.KeyManagement.KeyManager.GetAllKeysInternalAsync() in //src/IdentityServer/Services/Default/KeyManagement/KeyManager.cs:line 108\r\n at Duende.IdentityServer.Services.KeyManagement.KeyManager.GetCurrentKeysAsync() in //src/IdentityServer/Services/Default/KeyManagement/KeyManager.cs:line 72\r\n at Duende.IdentityServer.Services.KeyManagement.AutomaticKeyManagerKeyStore.GetAllSigningCredentialsAsync() in //src/IdentityServer/Services/Default/KeyManagement/AutomaticKeyManagerKeyStore.cs:line 92\r\n at Duende.IdentityServer.Services.KeyManagement.AutomaticKeyManagerKeyStore.GetSigningCredentialsAsync() in //src/IdentityServer/Services/Default/KeyManagement/AutomaticKeyManagerKeyStore.cs:line 78\r\n at Duende.IdentityServer.Services.DefaultKeyMaterialService.GetSigningCredentialsAsync(IEnumerable1 allowedAlgorithms) in /_/src/IdentityServer/Services/Default/DefaultKeyMaterialService.cs:line 60\r\n at Duende.IdentityServer.Services.DefaultTokenCreationService.CreateJwtAsync(Token token, String payload, Dictionary2 headerElements) in //src/IdentityServer/Services/Default/DefaultTokenCreationService.cs:line 130\r\n at Access.Identity.Services.IdentityServer.TokenCreationService.CreateTokenAsync(Token token) in X:\agent\17\s\src\Access.Identity.Services\IdentityServer\TokenCreationService.cs:line 46\r\n at Access.Identity.Services.IdentityServer.BackChannelLogoutService.CreateTokenAsync(BackChannelLogoutRequest request) in X:\agent\17\s\src\Access.Identity.Services\IdentityServer\BackChannelLogoutService.cs:line 62\r\n at Duende.IdentityServer.Services.DefaultBackChannelLogoutService.CreateFormPostPayloadAsync(BackChannelLogoutRequest request) in //src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs:line 133\r\n at Duende.IdentityServer.Services.DefaultBackChannelLogoutService.SendLogoutNotificationAsync(BackChannelLogoutRequest request) in //src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs:line 111\r\n at Access.Identity.Services.IdentityServer.BackChannelLogoutService.SendLogoutNotificationAsync(BackChannelLogoutRequest request) in X:\agent\17\s\src\Access.Identity.Services\IdentityServer\BackChannelLogoutService.cs:line 71\r\n at Duende.IdentityServer.Services.DefaultBackChannelLogoutService.SendLogoutNotificationsAsync(LogoutNotificationContext context) in //src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs:line 89\r\n at Access.Identity.Services.UserSessionBackChannelLogoutService.DoBackChannelSignoutsForSession(PerformContext context, SessionDescriptor session) in X:\agent\17\s\src\Access.Identity.Services\UserSessionBackChannelLogoutService.cs:line 87\r\n at InvokeStub_TaskAwaiter.GetResult(Object, Object, IntPtr*)\r\n at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

Additional context

We noticed an increased prevalence of this issue since upgrading to .Net 8. We also had a similar bug in our own code that presented itself much more under the same production load since upgrading. We're putting this down to runtime changes increasing the chance of genuine parallelism when using the Task.WhenAll pattern.

@RolandGuijt RolandGuijt self-assigned this Nov 18, 2024
@RolandGuijt RolandGuijt transferred this issue from DuendeSoftware/Support Nov 18, 2024
@RolandGuijt
Copy link

@mackie1001 I've moved this to the IdentityServer repo so that it can be picked up by our team to be fixed.
We'll have to triage it internally first.

@RolandGuijt RolandGuijt removed their assignment Nov 18, 2024
@damianh damianh removed the bug label Nov 27, 2024
@josephdecock josephdecock changed the title Bug whereby DefaultBackChannelLogoutService.SendLogoutNotificationsAsync can result in concurrent access to underlying DbContext and an System.InvalidOperationException DefaultBackChannelLogoutService.SendLogoutNotificationsAsync can result in concurrent access to underlying DbContext and a System.InvalidOperationException Nov 30, 2024
@josephdecock josephdecock changed the title DefaultBackChannelLogoutService.SendLogoutNotificationsAsync can result in concurrent access to underlying DbContext and a System.InvalidOperationException Logout Notifications can result in concurrent access to underlying DbContext Nov 30, 2024
@RolandGuijt
Copy link

@mackie1001 As a short term workaround you could override SendLogoutNotificationsAsync in the DefaultBackChannelLogoutService class and execute the tasks one by one.

@josephdecock josephdecock added this to the is-7.1.0 milestone Dec 2, 2024
@mackie1001
Copy link
Author

@RolandGuijt Thanks, as it stands now our retry mechanism seems to handle it eventually but we'll consider overriding the implementation in the meantime. Thanks for looking into it.

@josephdecock josephdecock modified the milestones: is-7.1.0, is-7.2.0 Dec 6, 2024
@damianh damianh added the area/identity-server Related to Identity Server label Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/identity-server Related to Identity Server
Projects
None yet
Development

No branches or pull requests

4 participants