Skip to content

don't cache failure result#341

Open
Erwinvandervalk wants to merge 1 commit intomainfrom
ev/oauth2/lazy
Open

don't cache failure result#341
Erwinvandervalk wants to merge 1 commit intomainfrom
ev/oauth2/lazy

Conversation

@Erwinvandervalk
Copy link
Contributor

@Erwinvandervalk Erwinvandervalk commented Mar 13, 2026

Summary

  • Fix exception propagation in OAuth2IntrospectionHandler where a faulted shared introspection task (e.g. timeout, cancellation, network error) was propagated to all concurrent requests waiting on the same token
  • Each request now retries once with a fresh direct call to LoadClaimsForToken when the shared task faults, preserving the piggyback-on-success optimization while ensuring faults aren't shared across requests

Fixes #281

Copilot AI review requested due to automatic review settings March 13, 2026 12:38
@Erwinvandervalk Erwinvandervalk self-assigned this Mar 13, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the OAuth2 introspection handler to avoid “poisoning” concurrent requests with a faulted in-flight introspection task, and adds regression tests for the concurrent fault/retry behavior.

Changes:

  • Add fault-handling around the in-flight introspection task so a faulted shared task is evicted and the request retries.
  • Add new tests covering two concurrent requests where the first introspection attempt faults (with both retry-success and persistent-failure cases).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
introspection/src/AspNetCore.Authentication.OAuth2Introspection/OAuth2IntrospectionHandler.cs Adds catch/evict/retry behavior when the shared in-flight introspection task faults.
introspection/test/AspNetCore.Authentication.OAuth2Introspection.Tests/Introspection.cs Adds concurrency tests for faulted introspection + retry semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +116 to +128
try
{
response = await IntrospectionDictionary
.GetOrAdd(token, GetTokenIntrospectionResponseLazy)
.Value;
}
catch
{
// The shared task faulted (e.g. timeout, cancellation, network error).
// Evict the faulted entry so future requests start fresh, then retry
// once with a direct call — bypassing the dictionary — so this request
// is not affected by another request's failure.
IntrospectionDictionary.TryRemove(token, out _);
Comment on lines +190 to +212
// Signal R2 it can proceed, then wait for R2 to be waiting on the dictionary entry
waitForSecondRequestToStart.WaitOne();
waitForFirstIntrospectionToStart.Set();
await Task.Delay(200); // wait for R2 to reach IntrospectionDictionary
throw new HttpRequestException("Simulated network error");
}
// Subsequent calls (retries) succeed normally
};
}, handler);

var client1 = new HttpClient(messageHandler);
var request1 = Task.Run(async () =>
{
client1.SetBearerToken(token);
return await client1.GetAsync("http://test");
});

var client2 = new HttpClient(messageHandler);
var request2 = Task.Run(async () =>
{
waitForSecondRequestToStart.Set();
waitForFirstIntrospectionToStart.WaitOne();
client2.SetBearerToken(token);
Comment on lines +245 to +249
// Signal R2, wait for it to reach the dictionary, then fault
waitForSecondRequestToStart.WaitOne();
waitForFirstIntrospectionToStart.Set();
await Task.Delay(200); // wait for R2 to reach IntrospectionDictionary
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exception thrown from GetTokenIntrospectionResponseLazy is propagated to concurrent requests waiting on IntrospectionDictionary

2 participants