public
Created

Test for (absence of) concurrency bug in fastJson. Fails with 2.0.5

  • Download Gist
Concurrency_bug_in_2_0_5.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
using System;
using System.Diagnostics;
using System.Threading;
using NUnit.Framework;
using fastJSON;
 
namespace UnitTests
{
[TestFixture]
internal class Concurrency_bug_in_2_0_5
{
 
private static void GenerateJsonForAandB(out string jsonA, out string jsonB)
{
Trace.WriteLine("Begin constructing the original objects. Please ignore trace information until I'm done.");
 
// set all parameters to false to produce pure JSON
fastJSON.JSON.Instance.Parameters = new JSONParameters {EnableAnonymousTypes = false, IgnoreCaseOnDeserialize = false, SerializeNullValues = false, ShowReadOnlyProperties = false, UseExtensions = false, UseFastGuid = false, UseOptimizedDatasetSchema = false, UseUTCDateTime = false, UsingGlobalTypes = false};
 
var a = new ConcurrentClassA {PayloadA = new PayloadA()};
var b = new ConcurrentClassB {PayloadB = new PayloadB()};
 
// A is serialized with extensions and global types
jsonA = JSON.Instance.ToJSON(a, new JSONParameters {EnableAnonymousTypes = false, IgnoreCaseOnDeserialize = false, SerializeNullValues = false, ShowReadOnlyProperties = false, UseExtensions = true, UseFastGuid = false, UseOptimizedDatasetSchema = false, UseUTCDateTime = false, UsingGlobalTypes = true});
// B is serialized using the above defaults
jsonB = JSON.Instance.ToJSON(b);
 
Trace.WriteLine("Ok, I'm done constructing the objects. Below is the generated json. Trace messages that follow below are the result of deserialization and critical for understanding the timing.");
Trace.WriteLine(jsonA);
Trace.WriteLine(jsonB);
}
 
[Test]
public void UsingGlobalsBug_singlethread_ok()
{
string jsonA;
string jsonB;
GenerateJsonForAandB(out jsonA, out jsonB);
 
var ax = JSON.Instance.ToObject(jsonA); // A has type information in JSON-extended
var bx = JSON.Instance.ToObject<ConcurrentClassB>(jsonB); // B needs external type info
 
Assert.IsNotNull(ax);
Assert.IsInstanceOf<ConcurrentClassA>(ax);
Assert.IsNotNull(bx);
Assert.IsInstanceOf<ConcurrentClassB>(bx);
}
 
[Test]
public void UsingGlobalsBug_multithread_nok()
{
string jsonA;
string jsonB;
GenerateJsonForAandB(out jsonA, out jsonB);
 
object ax = null;
object bx = null;
 
/*
* Intended timing to force CannotGetType bug in 2.0.5:
* the outer class ConcurrentClassA is deserialized first from json with extensions+global types. It reads the global types and sets _usingglobals to true.
* The constructor contains a sleep to force parallel deserialization of ConcurrentClassB while in A's constructor.
* The deserialization of B sets _usingglobals back to false.
* After B is done, A continues to deserialize its PayloadA. It finds type "2" but since _usingglobals is false now, it fails with "Cannot get type".
*/
 
Exception exception = null;
 
var thread = new Thread(() =>
{
try
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + " A begins deserialization");
ax = JSON.Instance.ToObject(jsonA); // A has type information in JSON-extended
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + " A is done");
}
catch (Exception ex)
{
exception = ex;
}
});
 
thread.Start();
 
Thread.Sleep(500); // wait to allow A to begin deserialization first
 
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + " B begins deserialization");
bx=JSON.Instance.ToObject<ConcurrentClassB>(jsonB); // B needs external type info
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + " B is done");
 
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + " waiting for A to continue");
thread.Join(); // wait for completion of A due to Sleep in A's constructor
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + " threads joined.");
 
Assert.IsNull(exception, exception==null?"":exception.Message+" "+exception.StackTrace);
 
Assert.IsNotNull(ax);
Assert.IsInstanceOf<ConcurrentClassA>(ax);
Assert.IsNotNull(bx);
Assert.IsInstanceOf<ConcurrentClassB>(bx);
}
}
 
 
public class ConcurrentClassA
{
public ConcurrentClassA()
{
Trace.WriteLine("ctor ConcurrentClassA. I will sleep for 2 seconds.");
Thread.Sleep(2000);
Thread.MemoryBarrier(); // just to be sure the caches on multi-core processors do not hide the bug. For me, the bug is present without the memory barrier, too.
Trace.WriteLine("ctor ConcurrentClassA. I am done sleeping.");
}
 
public PayloadA PayloadA { get; set; }
}
 
public class ConcurrentClassB
{
public ConcurrentClassB()
{
Trace.WriteLine("ctor ConcurrentClassB.");
}
 
public PayloadB PayloadB { get; set; }
}
 
public class PayloadA
{
public PayloadA()
{
Trace.WriteLine("ctor PayLoadA.");
}
}
 
public class PayloadB
{
public PayloadB()
{
Trace.WriteLine("ctor PayLoadB.");
}
}
 
 
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.