Skip to content

HttpSys request headers Keys and Count both allocate #44627

@Tratcher

Description

@Tratcher

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Offline report:

Found a strange allocation stack. Source code:

        public static bool TryExtractSingleValueHeader(
            IHeaderDictionary requestHeaders,
            string headerName,
            out string headerValue)
        {
            headerValue = string.Empty; 

            if (requestHeaders == null || requestHeaders.Keys.Count == 0)
            {
                return false;
            }

            var incomingHeaderValues = requestHeaders[headerName];

            // There may be multiple values, we will pick the first one.
            if (incomingHeaderValues.Count > 0)
            {
                headerValue = incomingHeaderValues[0];
                return true;
            }
            return false;
        }

So where is the LINQ expression? It’s here:

// Microsoft.AspNetCore.HttpSys.Internal.RequestHeaders
public ICollection<string> Keys
{
    get
    {
        return this.PropertiesKeys().Concat(this.Extra.Keys).ToArray<string>();
    }
}

For classes like Dictionary<K, V>, getting key collection is cheap because you because you just need to pay for a small key collection object once per dictionary (it will be reused forever). But this here has a super expensive implementation, so do not call it unless you’re willing and able to pay for it.

The strange thing is string allocation is the biggest here:

image

We do not need to get into the details why strings need to be allocated, because we can just remove the check.

It’s worse than that, the entire Extra headers collection is lazily loaded.

if (_extra == null)
{
var newDict = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
GetUnknownHeaders(newDict);
Interlocked.CompareExchange(ref _extra, newDict, null);
}
return _extra;

IDictionary.Count is also bad

public int Count
{
get { return PropertiesKeys().Count() + Extra.Count; }
}

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

No response

Anything else?

No response

Metadata

Metadata

Assignees

Labels

area-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractionsarea-perfPerformance infrastructure issuesfeature-httpsyshelp wantedUp for grabs. We would accept a PR to help resolve this issue

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions