public async Task GetRequiredService_BiggerObjectGraphWithOpenGenerics_NoDeadlock() { // Test is similar to GetRequiredService_UsesSingletonAndLazyLocks_NoDeadlock (but for open generics and a larger object graph) using (var mreForThread1 = new ManualResetEvent(false)) using (var mreForThread2 = new ManualResetEvent(false)) { // Arrange List <IFakeOpenGenericService <Thing4> > constrainedThing4Services = null; List <IFakeOpenGenericService <Thing5> > constrainedThing5Services = null; Thing3 thing3 = null; IServiceProvider sp = null; var sb = new StringBuilder(); var services = new ServiceCollection(); services.AddSingleton <Thing0>(); services.AddSingleton <Thing1>(); services.AddSingleton <Thing2>(); services.AddSingleton <Thing3>(); services.AddTransient(typeof(IFakeOpenGenericService <>), typeof(FakeOpenGenericService <>)); var lazy = new Lazy <Thing4>(() => { sb.Append("3"); mreForThread2.Set(); // Now that thread 1 holds lazy lock, allow thread 2 to continue thing3 = sp.GetRequiredService <Thing3>(); return(new Thing4(thing3)); }); services.AddTransient(sp => { if (ThreadId == 2) { sb.Append("1"); mreForThread1.Set(); // [b] Allow thread 1 to continue execution and take the lazy lock mreForThread2.WaitOne(); // [c] Wait until thread 1 takes the lazy lock sb.Append("4"); } // Let Thread 1 over take Thread 2 Thing4 value = lazy.Value; return(value); }); services.AddSingleton <Thing5>(); sp = services.BuildServiceProvider(); // Act var t1 = Task.Run(() => { ThreadId = 1; using var scope1 = sp.CreateScope(); mreForThread1.WaitOne(); // Waits until thread 2 reaches the transient call to ensure it holds Thing4 singleton lock sb.Append("2"); constrainedThing4Services = sp.GetServices <IFakeOpenGenericService <Thing4> >().ToList(); }); var t2 = Task.Run(() => { ThreadId = 2; using var scope2 = sp.CreateScope(); constrainedThing5Services = sp.GetServices <IFakeOpenGenericService <Thing5> >().ToList(); }); // Act await t1; await t2; Assert.Equal("1234", sb.ToString()); // Expected order of execution var thing4 = sp.GetRequiredService <Thing4>(); var thing5 = sp.GetRequiredService <Thing5>(); // Assert Assert.NotNull(thing3); Assert.NotNull(thing4); Assert.NotNull(thing5); Assert.Equal(1, constrainedThing4Services.Count); Assert.Equal(1, constrainedThing5Services.Count); Assert.Same(thing4, constrainedThing4Services[0].Value); Assert.Same(thing5, constrainedThing5Services[0].Value); } }
public Thing5(Thing4 thing) { }
public void ConcurrentMap() { var definitions = new MapDefinitionCollection(() => new IMapDefinition[] { new MapperDefinition1(), new MapperDefinition3(), }); var mapper = new UmbracoMapper(definitions, _scopeProvider); // the mapper currently has a map from Thing1 to Thing2 // because Thing3 inherits from Thing1, it will map a Thing3 instance, // and register a new map from Thing3 to Thing2, // thus modifying its internal dictionaries // if timing is good, and mapper does have non-concurrent dictionaries, it fails // practically, to reproduce, one needs to add a 1s sleep in the mapper's loop // hence, this test is explicit var thing3 = new Thing3 { Value = "value" }; var thing4 = new Thing4(); Exception caught = null; void ThreadLoop() { // keep failing at mapping - and looping through the maps for (var i = 0; i < 10; i++) { try { mapper.Map <Thing2>(thing4); } catch (Exception e) { caught = e; Console.WriteLine($"{e.GetType().Name} {e.Message}"); } } Console.WriteLine("done"); } var thread = new Thread(ThreadLoop); thread.Start(); Thread.Sleep(1000); try { Console.WriteLine($"{DateTime.Now:O} mapping"); var thing2 = mapper.Map <Thing2>(thing3); Console.WriteLine($"{DateTime.Now:O} mapped"); Assert.IsNotNull(thing2); Assert.AreEqual("value", thing2.Value); } finally { thread.Join(); } }