-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
This #103008 (comment) from @ManickaP made me double-check the thread-safety of our reading/parsing logic on HttpHeaders.
After #68115, concurrent reads on the header collection are thread-safe. All read operations will see the same values, and won't corrupt the underlying storage.
A minor exception to that is that while they will all see equivalent values, they may not be exactly the same objects. Since some header values are mutable, this could lead to observable differences later.
Simple repro:
while (true)
{
var headers = new ByteArrayContent([]).Headers;
headers.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");
var task = Task.Run(() => headers.ContentType);
MediaTypeHeaderValue contentType = headers.ContentType;
await task;
contentType.MediaType = "foo/bar";
if (headers.ContentType.MediaType != "foo/bar")
{
Console.WriteLine("Race condition");
break;
}
}The fix would be to avoid overwriting the underlying storage with different HeaderStoreItemInfo instances. The simplest way would be via an Interlocked.CompareExchange here:
runtime/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs
Line 739 in 7ffb9a4
| storeValueRef = info = new HeaderStoreItemInfo() { RawValue = value }; |
and here
runtime/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs
Lines 352 to 360 in 7ffb9a4
| if (EntriesAreLiveView) | |
| { | |
| entries[i].Value = info; | |
| } | |
| else | |
| { | |
| Debug.Assert(Contains(entry.Key)); | |
| ((Dictionary<HeaderDescriptor, object>)_headerStore!)[entry.Key] = info; | |
| } |