Skip to content

Concurrency Issue in OpenApiSchemaStore #58845

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

Closed
1 task done
xC0dex opened this issue Nov 8, 2024 · 2 comments
Closed
1 task done

Concurrency Issue in OpenApiSchemaStore #58845

xC0dex opened this issue Nov 8, 2024 · 2 comments
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi

Comments

@xC0dex
Copy link
Contributor

xC0dex commented Nov 8, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

It appears that the current implementation of OpenApiSchemaStore does not handle concurrent requests as expected. While I thought that I resolved the issue in #57972, the problem persists under certain conditions.

Observed Behavior:

When running the service in a Linux-based Docker container, the issue occurs almost every time on the first request.
However, when I test it locally on my Windows machine, I am unable to reproduce this issue. That's why I thought it's fixed.

Expected Behavior

The OpenApiSchemaStore should handle concurrent requests without issues.

Steps To Reproduce

  • Use the Scalar.AspNetCore package.
  • Start the API in a Docker container (In my case Linux-based).
  • Trigger concurrent requests by opening /scalar/v1 in the browser (Scalar initiates two parallel requests on startup, reported here).

Exceptions (if any)

Relevant log output:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
api-1    |       Connection id "0HN805CSINAPJ", Request id "0HN805CSINAPJ:00000003": An unhandled exception was thrown by the application.                                       
api-1    |       System.ArgumentException: An item with the same key has already been added. Key: OpenApiSchemaKey { Type = Scalar.AspNetCore.Playground.Books.Book[], ParameterInfo =  }                                                                                                                                                                         
api-1    |          at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
api-1    |          at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)                                                                                       
api-1    |          at Microsoft.AspNetCore.OpenApi.OpenApiSchemaStore.GetOrAdd(OpenApiSchemaKey key, Func`2 valueFactory)
api-1    |          at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.GetOrCreateSchemaAsync(Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription parameterDescription, Boolean captureSchemaByRef, CancellationToken cancellationToken)                                                 
api-1    |          at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetResponseAsync(ApiDescription apiDescription, Int32 statusCode, ApiResponseType apiResponseType, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)                                                        
api-1    |          at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetResponsesAsync(ApiDescription description, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)                                                                                                             
api-1    |          at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationAsync(ApiDescription description, HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
api-1    |          at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationsAsync(IGrouping`2 descriptions, HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)                                
api-1    |          at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)                                                        
api-1    |          at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, CancellationToken cancellationToken)
api-1    |          at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<<MapOpenApi>b__0>d.MoveNext()                                    
api-1    |       --- End of stack trace from previous location ---
api-1    |          at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F56B68D2B55B5B7B373BA2E4796D897848BC0F04A969B1AF6260183E8B9E0BAF2__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass2_0.<<MapGet0>g__RequestHandler|5>d.MoveNext()                                                                                              
api-1    |       --- End of stack trace from previous location ---
api-1    |          at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)                      
api-1    |          at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
api-1    |          at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)                                                                  
api-1    |          at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)  

.NET Version

9.0.100-rc.2.24474.11

Microsoft.AspNetCore.OpenApi version 9.0.0-rc.2.24474.3

Anything else?

Happy to help with resolving this issue!

@ghost ghost added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Nov 8, 2024
@martincostello martincostello added feature-openapi area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Nov 8, 2024
@BrennanConroy
Copy link
Member

The change is in 9.0.0 GA, not 9.0.0-rc2. A good hint that you don't have the change is the usage of at System.Collections.Generic.Dictionary``2.Add(TKey key, TValue value) in the stack trace. It should be a ConcurrentDictionary after your PR.

@xC0dex
Copy link
Contributor Author

xC0dex commented Nov 8, 2024

@BrennanConroy ups 👀 you're so right, thanks! How could I have missed that..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi
Projects
None yet
Development

No branches or pull requests

3 participants