Add JsonObject\JsonArray XML doc and tests regarding thread safety (#52010)
authorSteve Harter <steveharter@users.noreply.github.com>
Mon, 3 May 2021 17:33:15 +0000 (12:33 -0500)
committerGitHub <noreply@github.com>
Mon, 3 May 2021 17:33:15 +0000 (12:33 -0500)
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonObjectTests.cs

index 75279122f55a6fa78aab662d33d2b25ffa746bac..7ef3c7d17d901ac2d1b492dfecb61230c95eef12 100644 (file)
@@ -11,6 +11,10 @@ namespace System.Text.Json.Node
     /// <summary>
     ///   Represents a mutable JSON array.
     /// </summary>
+    /// <remarks>
+    /// It is safe to perform multiple concurrent read operations on a <see cref="JsonArray"/>,
+    /// but issues can occur if the collection is modified while it's being read.
+    /// </remarks>
     [DebuggerDisplay("JsonArray[{List.Count}]")]
     [DebuggerTypeProxy(typeof(DebugView))]
     public sealed partial class JsonArray : JsonNode
index 49f4a00fcfdceeb93dffcbd2881a6eb70251630f..ad91b47004f9b82794104852c2f21b1a94465a23 100644 (file)
@@ -11,6 +11,10 @@ namespace System.Text.Json.Node
     /// <summary>
     ///   Represents a mutable JSON object.
     /// </summary>
+    /// <remarks>
+    /// It's safe to perform multiple concurrent read operations on a <see cref="JsonObject"/>,
+    /// but issues can occur if the collection is modified while it's being read.
+    /// </remarks>
     [DebuggerDisplay("JsonObject[{Count}]")]
     [DebuggerTypeProxy(typeof(DebugView))]
     public sealed partial class JsonObject : JsonNode
index 2b48986b0c28fd36b5381b217df73738c740e932..e5483ed62bbe4af6c68d727d747ee21faf71900e 100644 (file)
@@ -755,15 +755,25 @@ namespace System.Text.Json.Node.Tests
         public static IEnumerable<object[]> JObjectCollectionData()
         {
             // Ensure that the list-to-dictionary threshold is hit (currently 9).
-            for (int i = 0; i < 20; i++)
+            for (int i = 0; i < 25; i++)
             {
                 yield return CreateArray(i);
+                yield return CreateArray_JsonElement(i);
             }
 
             yield return CreateArray(123);
-            yield return CreateArray(1000);
+            yield return CreateArray_JsonElement(122);
+
+            yield return CreateArray(300);
+            yield return CreateArray_JsonElement(299);
 
             object[] CreateArray(int count)
+            {
+                JsonObject jObject = CreateJsonObject(count);
+                return new object[] { jObject, count };
+            }
+
+            JsonObject CreateJsonObject(int count)
             {
                 var jObject = new JsonObject();
 
@@ -772,8 +782,143 @@ namespace System.Text.Json.Node.Tests
                     jObject[i.ToString()] = i;
                 }
 
+                return jObject;
+            }
+
+            object[] CreateArray_JsonElement(int count)
+            {
+                JsonObject jObject = CreateJsonObject(count);
+                string json = jObject.ToJsonString();
+                jObject = JsonNode.Parse(json).AsObject();
                 return new object[] { jObject, count };
             }
         }
+
+        [Theory]
+        [MemberData(nameof(JObjectCollectionData))]
+        public static void ChangeCollectionWhileEnumeratingFails(JsonObject jObject, int count)
+        {
+            if (count == 0)
+            {
+                return;
+            }
+
+            Assert.Equal(count, jObject.Count);
+
+            int index = 0;
+
+            // Exception string sample: "Collection was modified; enumeration operation may not execute"
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach(KeyValuePair<string, JsonNode?> node in jObject)
+                {
+                    index++;
+                    jObject.Add("New_A", index);
+                }
+            });
+            Assert.Equal(1, index);
+
+            index = 0;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (KeyValuePair<string, JsonNode?> node in jObject)
+                {
+                    index++;
+                    jObject.Remove(node.Key);
+                }
+            });
+            Assert.Equal(1, index);
+
+            index = 0;
+            IEnumerable iEnumerable = jObject;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (KeyValuePair<string, JsonNode?> node in iEnumerable)
+                {
+                    index++;
+                    jObject.Add("New_B", index);
+                }
+            });
+            Assert.Equal(1, index);
+
+            index = 0;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (KeyValuePair<string, JsonNode?> node in iEnumerable)
+                {
+                    index++;
+                    jObject.Remove(node.Key);
+                }
+            });
+            Assert.Equal(1, index);
+
+            IDictionary<string, JsonNode?> iDictionary = jObject;
+
+            index = 0;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (string str in iDictionary.Keys)
+                {
+                    index++;
+                    jObject.Add("New_C", index);
+                }
+            });
+            Assert.Equal(1, index);
+
+            index = 0;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (string str in (IEnumerable)iDictionary.Keys)
+                {
+                    index++;
+                    jObject.Add("New_D", index);
+                }
+            });
+            Assert.Equal(1, index);
+
+            index = 0;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (string str in iDictionary.Keys)
+                {
+                    index++;
+                    jObject.Remove(str);
+                }
+            });
+            Assert.Equal(1, index);
+
+            index = 0;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (JsonNode node in iDictionary.Values)
+                {
+                    index++;
+                    jObject.Add("New_E", index);
+                }
+            });
+            Assert.Equal(1, index);
+
+            index = 0;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (JsonNode node in (IEnumerable)iDictionary.Values)
+                {
+                    index++;
+                    jObject.Add("New_F", index);
+                }
+            });
+            Assert.Equal(1, index);
+
+            index = 0;
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                foreach (JsonNode node in iDictionary.Values)
+                {
+                    index++;
+                    jObject.Clear();
+                }
+            });
+            Assert.Equal(1, index);
+        }
     }
 }