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

Respect start timeout setting and expose exception from server startup #117

Merged
merged 3 commits into from
Apr 5, 2018
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
36 changes: 29 additions & 7 deletions src/WireMock.Net/Owin/AspNetCoreSelfHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.AspNetCore.Hosting;
using WireMock.Http;
using WireMock.HttpsCertificate;
using WireMock.Logging;
using WireMock.Validation;

namespace WireMock.Owin
Expand All @@ -18,6 +19,8 @@ internal class AspNetCoreSelfHost : IOwinSelfHost
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly WireMockMiddlewareOptions _options;
private readonly string[] _urls;
private readonly IWireMockLogger _logger;
private Exception _runningException;

private IWebHost _host;

Expand All @@ -27,11 +30,15 @@ internal class AspNetCoreSelfHost : IOwinSelfHost

public List<int> Ports { get; } = new List<int>();

public Exception RunningException => _runningException;

public AspNetCoreSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] params string[] uriPrefixes)
{
Check.NotNull(options, nameof(options));
Check.NotNullOrEmpty(uriPrefixes, nameof(uriPrefixes));

_logger = options.Logger ?? new WireMockConsoleLogger();

foreach (string uriPrefix in uriPrefixes)
{
Urls.Add(uriPrefix);
Expand Down Expand Up @@ -89,20 +96,35 @@ public Task StartAsync()

IsStarted = true;

#if NETSTANDARD1_3
Console.WriteLine("WireMock.Net server using netstandard1.3");
return Task.Run(() =>
{
_host.Run(_cts.Token);
StartServers();
}, _cts.Token);
#else
System.Console.WriteLine("WireMock.Net server using netstandard2.0");
}

return Task.Run(() =>
private void StartServers()
{
try
{
IsStarted = true;
#if NETSTANDARD1_3
_logger.Info("WireMock.Net server using netstandard1.3");
_host.Run(_cts.Token);
#else
_logger.Info("WireMock.Net server using netstandard2.0");
_host.Run();
}, _cts.Token);
#endif

}
catch (Exception e)
{
_runningException = e;
_logger.Error(e.ToString());
}
finally
{
IsStarted = false;
}
}

public Task StopAsync()
Expand Down
6 changes: 6 additions & 0 deletions src/WireMock.Net/Owin/IOwinSelfHost.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System;

namespace WireMock.Owin
{
Expand Down Expand Up @@ -29,6 +30,11 @@ interface IOwinSelfHost
/// </value>
List<int> Ports { get; }

/// <summary>
/// The exception occurred when the host is running
/// </summary>
Exception RunningException { get; }

Task StartAsync();

Task StopAsync();
Expand Down
70 changes: 43 additions & 27 deletions src/WireMock.Net/Owin/OwinSelfHost.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
#if !NETSTANDARD
using JetBrains.Annotations;
using Microsoft.Owin.Hosting;
using Owin;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Validation;
using Owin;
using Microsoft.Owin.Hosting;
using WireMock.Http;
using WireMock.Logging;
using WireMock.Validation;

namespace WireMock.Owin
{
internal class OwinSelfHost : IOwinSelfHost
{
private readonly WireMockMiddlewareOptions _options;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly IWireMockLogger _logger;
private Exception _runningException;

public OwinSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] params string[] uriPrefixes)
{
Check.NotNull(options, nameof(options));
Check.NotNullOrEmpty(uriPrefixes, nameof(uriPrefixes));

_logger = options.Logger ?? new WireMockConsoleLogger();

foreach (string uriPrefix in uriPrefixes)
{
Urls.Add(uriPrefix);
Expand All @@ -38,6 +43,8 @@ public OwinSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] param

public List<int> Ports { get; } = new List<int>();

public Exception RunningException => _runningException;

[PublicAPI]
public Task StartAsync()
{
Expand All @@ -58,37 +65,46 @@ public Task StopAsync()
private void StartServers()
{
#if NET46
Console.WriteLine("WireMock.Net server using .net 4.6.x or higher");
_logger.Info("WireMock.Net server using .net 4.6.x or higher");
#else
Console.WriteLine("WireMock.Net server using .net 4.5.x or higher");
_logger.Info("WireMock.Net server using .net 4.5.x or higher");
#endif

Action<IAppBuilder> startup = app =>
{
app.Use<GlobalExceptionMiddleware>(_options);
_options.PreWireMockMiddlewareInit?.Invoke(app);
app.Use<WireMockMiddleware>(_options);
_options.PostWireMockMiddlewareInit?.Invoke(app);
};

var servers = new List<IDisposable>();
foreach (var url in Urls)

try
{
servers.Add(WebApp.Start(url, startup));
Action<IAppBuilder> startup = app =>
{
app.Use<GlobalExceptionMiddleware>(_options);
_options.PreWireMockMiddlewareInit?.Invoke(app);
app.Use<WireMockMiddleware>(_options);
_options.PostWireMockMiddlewareInit?.Invoke(app);
};

foreach (var url in Urls)
{
servers.Add(WebApp.Start(url, startup));
}

IsStarted = true;

// WaitHandle is signaled when the token is cancelled,
// which will be more efficent than Thread.Sleep in while loop
_cts.Token.WaitHandle.WaitOne();
}

IsStarted = true;

while (!_cts.IsCancellationRequested)
catch (Exception e)
{
Thread.Sleep(30000);
// Expose exception of starting host, otherwise it's hard to be troubleshooting if keeping quiet
// For example, WebApp.Start will fail with System.MissingMemberException if Microsoft.Owin.Host.HttpListener.dll is being located
// https://stackoverflow.com/questions/25090211/owin-httplistener-not-located/31369857
_runningException = e;
_logger.Error(e.ToString());
}

IsStarted = false;

foreach (var server in servers)
finally
{
server.Dispose();
IsStarted = false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No foreach (var server in servers) is needed in the finally section ???

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

foreach (var server in servers) is moved into finally block. The reason of moving disposing server in finally is to make sure allocated resources(like handles in kernel mode) to be cleaned up immediately rather than wait until application is terminated.

// Dispose all servers in finally block to make sure clean up allocated resource on error happening
servers.ForEach((s) => s.Dispose());

// Dispose all servers in finally block to make sure clean up allocated resource on error happening
servers.ForEach((s) => s.Dispose());
}
}
}
Expand Down
33 changes: 23 additions & 10 deletions src/WireMock.Net/Server/FluentMockServer.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json;
using System.Threading;
using WireMock.Http;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Settings;
using WireMock.Validation;
using WireMock.Owin;
using WireMock.ResponseProviders;

namespace WireMock.Server
{
Expand All @@ -32,7 +32,7 @@ public partial class FluentMockServer : IDisposable
/// Gets a value indicating whether this server is started.
/// </summary>
[PublicAPI]
public bool IsStarted { get; }
public bool IsStarted { get => _httpServer == null ? false : _httpServer.IsStarted; }

/// <summary>
/// Gets the ports.
Expand Down Expand Up @@ -186,10 +186,23 @@ private FluentMockServer(IFluentMockServerSettings settings)

_httpServer.StartAsync();

// Fix for 'Bug: Server not listening after Start() returns (on macOS)'
Task.Delay(ServerStartDelay).Wait();

IsStarted = _httpServer.IsStarted;
using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout))
{
while (!_httpServer.IsStarted)
{
// Throw out exception if service start fails
if (_httpServer.RunningException != null)
{
throw new Exception($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException);
}
// Respect start timeout setting by throwing TimeoutException
if (ctsStartTimeout.IsCancellationRequested)
{
throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}");
}
ctsStartTimeout.Token.WaitHandle.WaitOne(100);
}
}

if (settings.AllowPartialMapping == true)
{
Expand Down