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

HeaderPropagation: reset AsyncLocal per request #18300

Merged

Conversation

alefranz
Copy link
Contributor

As Kestrel can bleed the AsyncLocal across requests, see #13991.

Backported from #15435, this change will not be needed on master, as the underlying Kestrel issue will be probably fixed with #14146.

Addresses #15384

/cc @anurse

As Kestrel can bleed the AsyncLocal across requests,
see dotnet#13991.
@jkotalik
Copy link
Contributor

@anurse assigning you to champion for shiproom/update the PR.

From what I can tell, the PR looks good.

@analogrelay
Copy link
Contributor

From what I can tell, the PR looks good.

It's easier to get approvals if we have review signoffs from folks, so @Tratcher @davidfowl @halter73 please also take a look.

@analogrelay analogrelay added the Servicing-consider Shiproom approval is required for the issue label Jan 14, 2020
@analogrelay analogrelay added this to the 3.1.x milestone Jan 14, 2020
@@ -33,7 +33,8 @@ public HeaderPropagationMiddleware(RequestDelegate next, IOptions<HeaderPropagat
_values = values ?? throw new ArgumentNullException(nameof(values));
}

public Task Invoke(HttpContext context)
// This needs to be async as otherwise the AsyncLocal could bleed across requests, see https://github.com/aspnet/AspNetCore/issues/13991.
public async Task Invoke(HttpContext context)
{
// We need to intialize the headers because the message handler will use this to detect misconfiguration.
var headers = _values.Headers ??= new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
Copy link
Member

Choose a reason for hiding this comment

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

Can we make this new up one every time as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could but it would be a breaking change. Any particular reason?

Copy link
Member

@Tratcher Tratcher Jan 15, 2020

Choose a reason for hiding this comment

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

I'm not sure why there's even a null check here? Why isn't this always initialized here? Who else would be initializing Headers before this component? If you had multiple instances of the middleware?

Copy link
Member

Choose a reason for hiding this comment

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

Right. We should just change it to create a new one every time. I'm not sure why this is being stored in the first place (that's what caused the original bug).

Copy link
Member

Choose a reason for hiding this comment

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

HeaderPropagationValues is the async local that's consumed later, so it does need to be set. I'm just not sure why it bothers to null check before setting it.

Copy link
Member

@halter73 halter73 Jan 15, 2020

Choose a reason for hiding this comment

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

Are you asking for this to be changed to the following @davidfowl?

var headers = _values.Headers = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);

So _values.Headers is always set to an empty dictionary at the start of the middleware? @alefranz is correct that this is breaking. Here's an example of the code this would break. Not sure how realistic/common this is, but we probably want to be careful in a patch.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHeaderPropagation(options =>
    {
        options.Headers.Add("User-Agent");
    });
})

// ...

public void Configure(IApplicationBuilder app, HeaderPropagationValues headerPropagationValues)
{
    app.Use((_, next) =>
    {
        // Always "propagate" a "Foo: Bar" header.
        headerPropagationValues.Headers = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
        {
            { "Foo", "Bar" }
        };

        return next();
    });

    app.UseHeaderPropagation();

    app.Run(async context =>
    {
        context.Response.Headers["Content-Type"] = "text/plain";

        foreach (var entry in headerPropagationValues.Headers)
        {
            await context.Response.WriteAsync($"{entry.Key}: {entry.Value}\r\n");
        }
    });
}

Today, this would print "Foo: Bar" to the response in addition to the propagated User-Agent. After newing up the dictionary every time, it would only include the propogated User-Agent.

Unless you mean the following:

var headers = _values.Headers = _values.Headers != null ?
    new Dictionary<string, StringValues>(_values.Headers, StringComparer.OrdinalIgnoreCase) :
    new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);

That makes some sense. It would allow my sample to use a static "Foo: Bar" dictionary instead of newing one up each time since it would have to worry about HeaderPropagationMiddleware mutating it. Technically if someone was relying on the mutation, that could be breaking too, but I think that's less likely to be an issue.

@halter73
Copy link
Member

@davidfowl I'm assuming you consider #14146 as too risky for a patch. Would you consider taking a patch with something like #14027 instead? That would also fix the AsyncLocal issue for people who aren't using HeaderPropagation.

@analogrelay
Copy link
Contributor

I'm assuming you consider #14146 as too risky for a patch.

I'd consider it risky for a patch too :). It's also a much more impactful change whereas this focuses on the area in which we have a direct customer report of a problem.

@analogrelay analogrelay removed the Servicing-consider Shiproom approval is required for the issue label Jan 16, 2020
@analogrelay
Copy link
Contributor

@davidfowl @halter73 can we land the question of which kind of change we want for 3.1 so I can take something clear to shiproom? ;)

Removing servicing-consider for now.

@halter73
Copy link
Member

I'm fine with the change as it is now. It is an incremental improvement.

@analogrelay analogrelay added the Servicing-consider Shiproom approval is required for the issue label Jan 16, 2020
Copy link
Member

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

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

LGTM

@BrennanConroy BrennanConroy modified the milestones: 3.1.x, 3.1.0-preview1 Jan 17, 2020
@analogrelay analogrelay modified the milestones: 3.1.0-preview1, 3.1.x Jan 17, 2020
@leecow leecow added Servicing-approved Shiproom has approved the issue and removed Servicing-consider Shiproom approval is required for the issue labels Feb 6, 2020
@jkotalik jkotalik modified the milestones: 3.1.x, 3.1.3 Feb 7, 2020
@jkotalik
Copy link
Contributor

jkotalik commented Feb 7, 2020

@anurse make sure you get this into 3.1.3 😄

@analogrelay
Copy link
Contributor

@anurse make sure you get this into 3.1.3 😄

Yep, branches aren't open but it's on my list :)

@analogrelay analogrelay added this to Approved by Shiproom in Servicing Status Feb 13, 2020
@analogrelay analogrelay merged commit d05c9f4 into dotnet:release/3.1 Feb 14, 2020
@ghost ghost moved this from Approved by Shiproom to PR Open in Servicing Status Feb 14, 2020
@ghost ghost moved this from PR Open to Merged in Servicing Status Feb 14, 2020
@amcasey amcasey added the area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlesware label Jun 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlesware Servicing-approved Shiproom has approved the issue
Projects
No open projects
Servicing Status
  
Merged
Development

Successfully merging this pull request may close these issues.

None yet

10 participants