From 45da8f030c3e1a595ea9e7fb31c6bf63b9009be4 Mon Sep 17 00:00:00 2001 From: Volodymyr-Fed Date: Sat, 18 Nov 2017 00:14:15 +0200 Subject: [PATCH 1/2] Fix issue with concurrent logging --- src/WireMock.Net/Owin/AspNetCoreSelfHost.cs | 1 + .../Owin/GlobalExceptionMiddleware.cs | 49 ++++++++++ src/WireMock.Net/Owin/OwinSelfHost.cs | 1 + src/WireMock.Net/Owin/WireMockMiddleware.cs | 3 +- .../Owin/WireMockMiddlewareOptions.cs | 3 +- .../Util/ConcurentObservableCollection.cs | 94 +++++++++++++++++++ .../ObservableLogEntriesTest.cs | 43 ++++++++- 7 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs create mode 100644 src/WireMock.Net/Util/ConcurentObservableCollection.cs diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 1f22034e0..18e645818 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -46,6 +46,7 @@ public Task StartAsync() _host = new WebHostBuilder() .Configure(appBuilder => { + appBuilder.UseMiddleware(); _options.PreWireMockMiddlewareInit?.Invoke(appBuilder); appBuilder.UseMiddleware(_options); _options.PostWireMockMiddlewareInit?.Invoke(appBuilder); diff --git a/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs b/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs new file mode 100644 index 000000000..0769ebff1 --- /dev/null +++ b/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Newtonsoft.Json; +#if !NETSTANDARD +using Microsoft.Owin; +#else +using Microsoft.AspNetCore.Http; +#endif + +namespace WireMock.Owin +{ +#if !NETSTANDARD + internal class GlobalExceptionMiddleware : OwinMiddleware +#else + internal class GlobalExceptionMiddleware +#endif + { +#if !NETSTANDARD + public GlobalExceptionMiddleware(OwinMiddleware next) : base(next) { } +#else + public GlobalExceptionMiddleware(RequestDelegate next) + { + Next = next; + } +#endif + +#if NETSTANDARD + public RequestDelegate Next { get; private set; } +#endif + + private readonly OwinResponseMapper _responseMapper = new OwinResponseMapper(); + +#if !NETSTANDARD + public override async Task Invoke(IOwinContext ctx) +#else + public async Task Invoke(HttpContext ctx) +#endif + { + try + { + await Next?.Invoke(ctx); + } + catch (Exception ex) + { + await _responseMapper.MapAsync(new ResponseMessage { StatusCode = 500, Body = JsonConvert.SerializeObject(ex) }, ctx.Response); + } + } + } +} diff --git a/src/WireMock.Net/Owin/OwinSelfHost.cs b/src/WireMock.Net/Owin/OwinSelfHost.cs index 21b2aca04..b4a36390a 100644 --- a/src/WireMock.Net/Owin/OwinSelfHost.cs +++ b/src/WireMock.Net/Owin/OwinSelfHost.cs @@ -61,6 +61,7 @@ private void StartServers() Action startup = app => { + app.Use(); _options.PreWireMockMiddlewareInit?.Invoke(app); app.Use(_options); _options.PostWireMockMiddlewareInit?.Invoke(app); diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index 3f601bf3d..881458d8b 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -5,6 +5,7 @@ using System.Linq; using WireMock.Matchers; using WireMock.Util; +using Newtonsoft.Json; #if !NETSTANDARD using Microsoft.Owin; #else @@ -124,7 +125,7 @@ public async Task Invoke(HttpContext ctx) } catch (Exception ex) { - response = new ResponseMessage { StatusCode = 500, Body = ex.ToString() }; + response = new ResponseMessage { StatusCode = 500, Body = JsonConvert.SerializeObject(ex) }; } finally { diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs index 464da3ac1..df0e4d3db 100644 --- a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using WireMock.Logging; using WireMock.Matchers; +using WireMock.Utils; #if !NETSTANDARD using Owin; #else @@ -22,7 +23,7 @@ internal class WireMockMiddlewareOptions public IList Mappings { get; set; } = new List(); - public ObservableCollection LogEntries { get; } = new ObservableCollection(); + public ObservableCollection LogEntries { get; } = new ConcurentObservableCollection(); public int? RequestLogExpirationDuration { get; set; } diff --git a/src/WireMock.Net/Util/ConcurentObservableCollection.cs b/src/WireMock.Net/Util/ConcurentObservableCollection.cs new file mode 100644 index 000000000..d7de889e1 --- /dev/null +++ b/src/WireMock.Net/Util/ConcurentObservableCollection.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace WireMock.Utils +{ + /// + /// A special Collection that overrides methods of to make them thread safe + /// + /// The generic type + /// + public class ConcurentObservableCollection : ObservableCollection + { + private readonly object _lockObject = new object(); + + /// + /// Initializes a new instance of the class. + /// + public ConcurentObservableCollection() : base() { } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified list. + /// + /// The list from which the elements are copied. + public ConcurentObservableCollection(List list) : base(list) { } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified collection. + /// + /// The collection from which the elements are copied. + public ConcurentObservableCollection(IEnumerable collection) : base(collection) { } + + /// + /// Removes all items from the collection. + /// + protected override void ClearItems() + { + lock (_lockObject) + { + base.ClearItems(); + } + } + + /// + /// Removes the item at the specified index of the collection. + /// + /// The zero-based index of the element to remove. + protected override void RemoveItem(int index) + { + lock (_lockObject) + { + base.RemoveItem(index); + } + } + + /// + /// Inserts an item into the collection at the specified index. + /// + /// The zero-based index at which item should be inserted. + /// The object to insert. + protected override void InsertItem(int index, T item) + { + lock (_lockObject) + { + base.InsertItem(index, item); + } + } + + /// + /// Replaces the element at the specified index. + /// + /// The zero-based index of the element to replace. + /// The new value for the element at the specified index. + protected override void SetItem(int index, T item) + { + lock (_lockObject) + { + base.SetItem(index, item); + } + } + + /// + /// Moves the item at the specified index to a new location in the collection. + /// + /// The zero-based index specifying the location of the item to be moved. + /// The zero-based index specifying the new location of the item. + protected override void MoveItem(int oldIndex, int newIndex) + { + lock (_lockObject) + { + base.MoveItem(oldIndex, newIndex); + } + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs b/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs index 1bd2f407e..d5292efe1 100644 --- a/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs +++ b/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs @@ -1,5 +1,10 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using NFluent; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; @@ -8,7 +13,7 @@ namespace WireMock.Net.Tests { - public class ObservableLogEntriesTest: IDisposable + public class ObservableLogEntriesTest : IDisposable { private FluentMockServer _server; @@ -35,6 +40,42 @@ public async void Test() Check.That(count).Equals(1); } + [Fact] + public async Task ParallelTest() + { + var expectedCount = 100; + + // Assign + _server = FluentMockServer.Start(); + + _server + .Given(Request.Create() + .WithPath("/foo") + .UsingGet()) + .RespondWith(Response.Create() + .WithDelay(6) + .WithSuccess()); + + int count = 0; + _server.LogEntriesChanged += (sender, args) => count++; + + var http = new HttpClient(); + + // Act + var listOfTasks = new List>(); + for (var i = 0; i < expectedCount; i++) + { + Thread.Sleep(3); + listOfTasks.Add(http.GetAsync(_server.Urls[0] + $"/foo")); + } + var responses = await Task.WhenAll(listOfTasks); + var countResponsesWithStatusNotOk = responses.Where(r => r.StatusCode != HttpStatusCode.OK).Count(); + + // Assert + Check.That(countResponsesWithStatusNotOk).Equals(0); + Check.That(count).Equals(expectedCount); + } + public void Dispose() { _server?.Dispose(); From 8f51d1647535ef6ca0c2a803faca71a0e9cfe8eb Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 18 Nov 2017 12:16:09 +0100 Subject: [PATCH 2/2] Fix namespace + code format/cleanup --- .../Owin/WireMockMiddlewareOptions.cs | 2 +- .../Util/ConcurentObservableCollection.cs | 41 ++++++------------- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs index df0e4d3db..adfd313e1 100644 --- a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs @@ -4,7 +4,7 @@ using System.Collections.ObjectModel; using WireMock.Logging; using WireMock.Matchers; -using WireMock.Utils; +using WireMock.Util; #if !NETSTANDARD using Owin; #else diff --git a/src/WireMock.Net/Util/ConcurentObservableCollection.cs b/src/WireMock.Net/Util/ConcurentObservableCollection.cs index d7de889e1..60d2c90d5 100644 --- a/src/WireMock.Net/Util/ConcurentObservableCollection.cs +++ b/src/WireMock.Net/Util/ConcurentObservableCollection.cs @@ -1,21 +1,21 @@ using System.Collections.Generic; using System.Collections.ObjectModel; -namespace WireMock.Utils +namespace WireMock.Util { /// /// A special Collection that overrides methods of to make them thread safe /// - /// The generic type - /// + /// The type of elements in the collection. + /// public class ConcurentObservableCollection : ObservableCollection { private readonly object _lockObject = new object(); - /// - /// Initializes a new instance of the class. - /// - public ConcurentObservableCollection() : base() { } + /// + /// Initializes a new instance of the class. + /// + public ConcurentObservableCollection() { } /// /// Initializes a new instance of the class that contains elements copied from the specified list. @@ -29,9 +29,7 @@ public ConcurentObservableCollection(List list) : base(list) { } /// The collection from which the elements are copied. public ConcurentObservableCollection(IEnumerable collection) : base(collection) { } - /// - /// Removes all items from the collection. - /// + /// protected override void ClearItems() { lock (_lockObject) @@ -40,10 +38,7 @@ protected override void ClearItems() } } - /// - /// Removes the item at the specified index of the collection. - /// - /// The zero-based index of the element to remove. + /// protected override void RemoveItem(int index) { lock (_lockObject) @@ -52,11 +47,7 @@ protected override void RemoveItem(int index) } } - /// - /// Inserts an item into the collection at the specified index. - /// - /// The zero-based index at which item should be inserted. - /// The object to insert. + /// protected override void InsertItem(int index, T item) { lock (_lockObject) @@ -65,11 +56,7 @@ protected override void InsertItem(int index, T item) } } - /// - /// Replaces the element at the specified index. - /// - /// The zero-based index of the element to replace. - /// The new value for the element at the specified index. + /// protected override void SetItem(int index, T item) { lock (_lockObject) @@ -78,11 +65,7 @@ protected override void SetItem(int index, T item) } } - /// - /// Moves the item at the specified index to a new location in the collection. - /// - /// The zero-based index specifying the location of the item to be moved. - /// The zero-based index specifying the new location of the item. + /// protected override void MoveItem(int oldIndex, int newIndex) { lock (_lockObject)