dotnet / dotnet/aspnetcore

OpenAPI document generation retains collectible endpoint contract assemblies

Open
#68,564 3 comments 0 reactions 0 assignees View on GitHub
area-minimal feature-openapi Needs: Author Feedback Needs: Repro
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Version

- .NET SDK 10.0.300
- .NET runtime/reference pack 10.0.8
- Microsoft.AspNetCore.OpenApi 10.0.10
- Microsoft.OpenApi 2.7.5

### Description

Generating an OpenAPI document for a `RouteEndpoint` whose `IAcceptsMetadata` / `IProducesResponseTypeMetadata` names types from a collectible `AssemblyLoadContext` keeps that load context, assembly, and contract type alive after the endpoint source and service provider have been released.

The endpoint's request delegate and description method are stable host-owned members. The only collectible artifacts are the request/response contract types in standard endpoint metadata. A control using identical host-owned contract types collects successfully.

### Minimal reproduction

The standalone reproduction and platform receipts are here:

https://github.com/elsa-workflows/elsa-foundation/tree/codex/1392-unload-safe-openapi-boundary/docs/reports/repros/openapi-collectible-contract-retention

Run:

```bash
dotnet run --project Repro.csproj
```

It performs two bounded cycles:

1. stable host-owned request/response metadata;
2. request/response metadata from a collectible contract assembly.

Observed on macOS arm64 and Linux amd64 (`mcr.microsoft.com/dotnet/sdk:10.0.300`):

```text
Stable metadata: CollectionResult { Collected = True, LoadContextAlive = False, AssemblyAlive = False, ContractTypeAlive = False, DelegateAlive = False, ProviderAlive = False }
Collectible metadata: CollectionResult { Collected = False, LoadContextAlive = True, AssemblyAlive = True, ContractTypeAlive = True, DelegateAlive = False, ProviderAlive = False }
```

### Expected behavior

After the endpoint source and OpenAPI service provider are released, no process-global or longer-lived OpenAPI state should strongly retain endpoint contract types or their collectible load context.

### Actual behavior

The provider and endpoint delegate collect, but the collectible load context, assembly, and contract type remain alive through repeated compacting collections.

Inspection of the installed implementation points to document-lifetime caches that retain API-description operation contexts and schema type identities. There does not appear to be a public generation/eviction seam that a dynamic endpoint host can use when a documented endpoint generation is retired.

### Requested direction

A supported generation/lifetime boundary would be preferable to application code clearing private caches. Possible shapes include:

- generation-aware or evictable operation-context and schema caches when endpoint descriptions change;
- avoiding strong retention of `Type`, `MethodInfo`, endpoint metadata, delegates, and service providers beyond the document generation that requires them;
- a public document-source/provider replacement seam used by mapped OpenAPI endpoints as well as direct consumers.

The reproduction intentionally does not mutate private framework caches, use GC delays as an eviction mechanism, or omit real OpenAPI generation.

Contributor guide

Open the contributing guide

Research direction

Start by running the standalone reproduction with `dotnet run --project Repro.csproj` and compare its stable and collectible metadata cycles. Then inspect the installed OpenAPI implementation's document-lifetime caches, operation-description contexts, and schema type identities; done means a supported generation or lifetime boundary lets the collectible load context, assembly, and contract type collect after the endpoint source and provider are released.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, openapi
Domain
api, backend-api-design
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.