@@ -347,17 +347,14 @@ private IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumeratorCore
347347 // during enumeration so that we can parse the raw value in order to a) return
348348 // the correct set of parsed values, and b) update the instance for subsequent enumerations
349349 // to reflect that parsing.
350- info = new HeaderStoreItemInfo ( ) { RawValue = entry . Value } ;
351350
352- if ( EntriesAreLiveView )
353- {
354- entries [ i ] . Value = info ;
355- }
356- else
357- {
358- Debug . Assert ( Contains ( entry . Key ) ) ;
359- ( ( Dictionary < HeaderDescriptor , object > ) _headerStore ! ) [ entry . Key ] = info ;
360- }
351+ #nullable disable // https://github.com/dotnet/roslyn/issues/73928
352+ ref object storeValueRef = ref EntriesAreLiveView
353+ ? ref entries [ i ] . Value
354+ : ref CollectionsMarshal . GetValueRefOrNullRef ( ( Dictionary < HeaderDescriptor , object > ) _headerStore , entry . Key ) ;
355+
356+ info = ReplaceWithHeaderStoreItemInfo ( ref storeValueRef , entry . Value ) ;
357+ #nullable restore
361358 }
362359
363360 // Make sure we parse all raw values before returning the result. Note that this has to be
@@ -729,15 +726,10 @@ private bool TryGetAndParseHeaderInfo(HeaderDescriptor key, [NotNullWhen(true)]
729726 if ( ! Unsafe . IsNullRef ( ref storeValueRef ) )
730727 {
731728 object value = storeValueRef ;
732- if ( value is HeaderStoreItemInfo hsi )
733- {
734- info = hsi ;
735- }
736- else
737- {
738- Debug . Assert ( value is string ) ;
739- storeValueRef = info = new HeaderStoreItemInfo ( ) { RawValue = value } ;
740- }
729+
730+ info = value is HeaderStoreItemInfo hsi
731+ ? hsi
732+ : ReplaceWithHeaderStoreItemInfo ( ref storeValueRef , value ) ;
741733
742734 ParseRawHeaderValues ( key , info ) ;
743735 return true ;
@@ -747,6 +739,31 @@ private bool TryGetAndParseHeaderInfo(HeaderDescriptor key, [NotNullWhen(true)]
747739 return false ;
748740 }
749741
742+ /// <summary>
743+ /// Replaces <paramref name="storeValueRef"/> with a new <see cref="HeaderStoreItemInfo"/>,
744+ /// or returns the existing <see cref="HeaderStoreItemInfo"/> if a different thread beat us to it.
745+ /// </summary>
746+ /// <remarks>
747+ /// This helper should be used any time we're upgrading a storage slot from an unparsed string to a HeaderStoreItemInfo *while reading*.
748+ /// Concurrent writes to the header collection are UB, so we don't need to worry about race conditions when doing the replacement there.
749+ /// </remarks>
750+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
751+ private static HeaderStoreItemInfo ReplaceWithHeaderStoreItemInfo ( ref object storeValueRef , object value )
752+ {
753+ Debug . Assert ( value is string ) ;
754+
755+ var info = new HeaderStoreItemInfo ( ) { RawValue = value } ;
756+ object previousValue = Interlocked . CompareExchange ( ref storeValueRef , info , value ) ;
757+
758+ if ( ReferenceEquals ( previousValue , value ) )
759+ {
760+ return info ;
761+ }
762+
763+ // Rare race condition: Another thread replaced the value with a HeaderStoreItemInfo.
764+ return ( HeaderStoreItemInfo ) previousValue ;
765+ }
766+
750767 private static void ParseRawHeaderValues ( HeaderDescriptor descriptor , HeaderStoreItemInfo info )
751768 {
752769 // Unlike TryGetHeaderInfo() this method tries to parse all non-validated header values (if any)
0 commit comments