From 6d08aad1366853631d880aa876ccd0ac47a79ca9 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 2 Mar 2020 13:43:59 +0100 Subject: [PATCH 1/9] Do not lock on string in Uri --- .../System.Private.Uri/src/System/Uri.cs | 8 +++-- .../tests/FunctionalTests/UriTests.cs | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index c49ef8f400030..9985869b8e746 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -9,6 +9,7 @@ using System.Runtime.Serialization; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Threading; namespace System { @@ -260,7 +261,7 @@ private static bool StaticInFact(Flags allFlags, Flags checkFlags) private UriInfo EnsureUriInfo() { Flags cF = _flags; - if ((_flags & Flags.MinimalUriInfoSet) == 0) + if ((cF & Flags.MinimalUriInfoSet) == 0) { CreateUriInfo(cF); } @@ -2376,11 +2377,12 @@ private unsafe void CreateUriInfo(Flags cF) Done: cF |= Flags.MinimalUriInfoSet; info.DnsSafeHost = _dnsSafeHost; - lock (_string) + + Interlocked.CompareExchange(ref _info, info, null!); + lock (_info) { if ((_flags & Flags.MinimalUriInfoSet) == 0) { - _info = info; _flags = (_flags & ~Flags.IndexMask) | cF; } } diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriTests.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriTests.cs index 3fabaf441f6c4..725a8380502fc 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriTests.cs +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriTests.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace System.PrivateUri.Tests @@ -974,5 +976,34 @@ public static void Uri_EmptyPort_UsesDefaultPort() Assert.Equal($"http://www.contoso.com/", u.AbsoluteUri); Assert.Equal(80, u.Port); } + + [Fact] + public static void Uri_DoesNotLockOnString() + { + // Don't intern the string we lock on + string uriString = "*http://www.contoso.com".Substring(1); + + bool timedOut = false; + + var enteredLockMre = new ManualResetEvent(false); + var finishedParsingMre = new ManualResetEvent(false); + + Task.Factory.StartNew(() => + { + lock (uriString) + { + enteredLockMre.Set(); + timedOut = !finishedParsingMre.WaitOne(TimeSpan.FromSeconds(10)); + } + }, TaskCreationOptions.LongRunning); + + enteredLockMre.WaitOne(); + int port = new Uri(uriString).Port; + finishedParsingMre.Set(); + Assert.Equal(80, port); + + Assert.True(Monitor.TryEnter(uriString, TimeSpan.FromSeconds(10))); + Assert.False(timedOut); + } } } From 5077075274b8f61897e98f023bbe37757661e823 Mon Sep 17 00:00:00 2001 From: MihaZupan Date: Tue, 3 Mar 2020 14:07:18 +0100 Subject: [PATCH 2/9] Make Uri thread-safe without locks *A lock may still be used for custom parsers --- .../src/Resources/Strings.resx | 6 +- .../System.Private.Uri/src/System/Uri.cs | 238 +++++++++--------- .../System.Private.Uri/src/System/UriExt.cs | 39 ++- .../src/System/UriScheme.cs | 22 ++ .../src/System/UriSyntax.cs | 7 + .../tests/FunctionalTests/UriParserTest.cs | 49 +++- 6 files changed, 222 insertions(+), 139 deletions(-) diff --git a/src/libraries/System.Private.Uri/src/Resources/Strings.resx b/src/libraries/System.Private.Uri/src/Resources/Strings.resx index 655f2820692e2..734c4c449a6d4 100644 --- a/src/libraries/System.Private.Uri/src/Resources/Strings.resx +++ b/src/libraries/System.Private.Uri/src/Resources/Strings.resx @@ -1,4 +1,5 @@ - + +