Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue with concurrent logging #63

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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