Skip to content

Commit

Permalink
Fix issue with concurrent logging (#63)
Browse files Browse the repository at this point in the history
* Fix issue with concurrent logging

* Fix namespace + code format/cleanup
  • Loading branch information
volodymyr-fed authored and StefH committed Nov 18, 2017
1 parent 6c38400 commit a15e674
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/WireMock.Net/Owin/AspNetCoreSelfHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public Task StartAsync()
_host = new WebHostBuilder()
.Configure(appBuilder =>
{
appBuilder.UseMiddleware<GlobalExceptionMiddleware>();
_options.PreWireMockMiddlewareInit?.Invoke(appBuilder);
appBuilder.UseMiddleware<WireMockMiddleware>(_options);
_options.PostWireMockMiddlewareInit?.Invoke(appBuilder);
Expand Down
49 changes: 49 additions & 0 deletions src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
1 change: 1 addition & 0 deletions src/WireMock.Net/Owin/OwinSelfHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private void StartServers()

Action<IAppBuilder> startup = app =>
{
app.Use<GlobalExceptionMiddleware>();
_options.PreWireMockMiddlewareInit?.Invoke(app);
app.Use<WireMockMiddleware>(_options);
_options.PostWireMockMiddlewareInit?.Invoke(app);
Expand Down
3 changes: 2 additions & 1 deletion src/WireMock.Net/Owin/WireMockMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using WireMock.Matchers;
using WireMock.Util;
using Newtonsoft.Json;
#if !NETSTANDARD
using Microsoft.Owin;
#else
Expand Down Expand Up @@ -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
{
Expand Down
3 changes: 2 additions & 1 deletion src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.ObjectModel;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Util;
#if !NETSTANDARD
using Owin;
#else
Expand All @@ -22,7 +23,7 @@ internal class WireMockMiddlewareOptions

public IList<Mapping> Mappings { get; set; } = new List<Mapping>();

public ObservableCollection<LogEntry> LogEntries { get; } = new ObservableCollection<LogEntry>();
public ObservableCollection<LogEntry> LogEntries { get; } = new ConcurentObservableCollection<LogEntry>();

public int? RequestLogExpirationDuration { get; set; }

Expand Down
77 changes: 77 additions & 0 deletions src/WireMock.Net/Util/ConcurentObservableCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace WireMock.Util
{
/// <summary>
/// A special Collection that overrides methods of <see cref="ObservableCollection{T}"/> to make them thread safe
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <inheritdoc cref="ObservableCollection{T}" />
public class ConcurentObservableCollection<T> : ObservableCollection<T>
{
private readonly object _lockObject = new object();

/// <summary>
/// Initializes a new instance of the <see cref="T:WireMock.Util.ConcurentObservableCollection`1" /> class.
/// </summary>
public ConcurentObservableCollection() { }

/// <summary>
/// Initializes a new instance of the <see cref="ConcurentObservableCollection{T}"/> class that contains elements copied from the specified list.
/// </summary>
/// <param name="list">The list from which the elements are copied.</param>
public ConcurentObservableCollection(List<T> list) : base(list) { }

/// <summary>
/// Initializes a new instance of the <see cref="ConcurentObservableCollection{T}"/> class that contains elements copied from the specified collection.
/// </summary>
/// <param name="collection">The collection from which the elements are copied.</param>
public ConcurentObservableCollection(IEnumerable<T> collection) : base(collection) { }

/// <inheritdoc cref="ObservableCollection{T}.ClearItems"/>
protected override void ClearItems()
{
lock (_lockObject)
{
base.ClearItems();
}
}

/// <inheritdoc cref="ObservableCollection{T}.RemoveItem"/>
protected override void RemoveItem(int index)
{
lock (_lockObject)
{
base.RemoveItem(index);
}
}

/// <inheritdoc cref="ObservableCollection{T}.InsertItem"/>
protected override void InsertItem(int index, T item)
{
lock (_lockObject)
{
base.InsertItem(index, item);
}
}

/// <inheritdoc cref="ObservableCollection{T}.SetItem"/>
protected override void SetItem(int index, T item)
{
lock (_lockObject)
{
base.SetItem(index, item);
}
}

/// <inheritdoc cref="ObservableCollection{T}.MoveItem"/>
protected override void MoveItem(int oldIndex, int newIndex)
{
lock (_lockObject)
{
base.MoveItem(oldIndex, newIndex);
}
}
}
}
43 changes: 42 additions & 1 deletion test/WireMock.Net.Tests/ObservableLogEntriesTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,7 +13,7 @@

namespace WireMock.Net.Tests
{
public class ObservableLogEntriesTest: IDisposable
public class ObservableLogEntriesTest : IDisposable
{
private FluentMockServer _server;

Expand All @@ -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<Task<HttpResponseMessage>>();
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();
Expand Down

0 comments on commit a15e674

Please sign in to comment.