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

* Respect start timeout setting and expose exception from server startup

* Dispose running servers properly on error happening

* Addressed comments from Stef
This commit is contained in:
Evan Liang
2018-04-05 11:31:10 -07:00
committed by Stef Heyenrath
parent ac72973cc4
commit 2d2a2dd6fc
4 changed files with 101 additions and 44 deletions

View File

@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using WireMock.Http; using WireMock.Http;
using WireMock.HttpsCertificate; using WireMock.HttpsCertificate;
using WireMock.Logging;
using WireMock.Validation; using WireMock.Validation;
namespace WireMock.Owin namespace WireMock.Owin
@@ -18,6 +19,8 @@ namespace WireMock.Owin
private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly WireMockMiddlewareOptions _options; private readonly WireMockMiddlewareOptions _options;
private readonly string[] _urls; private readonly string[] _urls;
private readonly IWireMockLogger _logger;
private Exception _runningException;
private IWebHost _host; private IWebHost _host;
@@ -27,11 +30,15 @@ namespace WireMock.Owin
public List<int> Ports { get; } = new List<int>(); public List<int> Ports { get; } = new List<int>();
public Exception RunningException => _runningException;
public AspNetCoreSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] params string[] uriPrefixes) public AspNetCoreSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] params string[] uriPrefixes)
{ {
Check.NotNull(options, nameof(options)); Check.NotNull(options, nameof(options));
Check.NotNullOrEmpty(uriPrefixes, nameof(uriPrefixes)); Check.NotNullOrEmpty(uriPrefixes, nameof(uriPrefixes));
_logger = options.Logger ?? new WireMockConsoleLogger();
foreach (string uriPrefix in uriPrefixes) foreach (string uriPrefix in uriPrefixes)
{ {
Urls.Add(uriPrefix); Urls.Add(uriPrefix);
@@ -89,20 +96,35 @@ namespace WireMock.Owin
IsStarted = true; IsStarted = true;
#if NETSTANDARD1_3
Console.WriteLine("WireMock.Net server using netstandard1.3");
return Task.Run(() => return Task.Run(() =>
{ {
_host.Run(_cts.Token); StartServers();
}, _cts.Token); }, _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(); _host.Run();
}, _cts.Token);
#endif #endif
}
catch (Exception e)
{
_runningException = e;
_logger.Error(e.ToString());
}
finally
{
IsStarted = false;
}
} }
public Task StopAsync() public Task StopAsync()

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using System;
namespace WireMock.Owin namespace WireMock.Owin
{ {
@@ -29,6 +30,11 @@ namespace WireMock.Owin
/// </value> /// </value>
List<int> Ports { get; } List<int> Ports { get; }
/// <summary>
/// The exception occurred when the host is running
/// </summary>
Exception RunningException { get; }
Task StartAsync(); Task StartAsync();
Task StopAsync(); Task StopAsync();

View File

@@ -1,13 +1,14 @@
#if !NETSTANDARD #if !NETSTANDARD
using JetBrains.Annotations;
using Microsoft.Owin.Hosting;
using Owin;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Validation;
using Owin;
using Microsoft.Owin.Hosting;
using WireMock.Http; using WireMock.Http;
using WireMock.Logging;
using WireMock.Validation;
namespace WireMock.Owin namespace WireMock.Owin
{ {
@@ -15,12 +16,16 @@ namespace WireMock.Owin
{ {
private readonly WireMockMiddlewareOptions _options; private readonly WireMockMiddlewareOptions _options;
private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly IWireMockLogger _logger;
private Exception _runningException;
public OwinSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] params string[] uriPrefixes) public OwinSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] params string[] uriPrefixes)
{ {
Check.NotNull(options, nameof(options)); Check.NotNull(options, nameof(options));
Check.NotNullOrEmpty(uriPrefixes, nameof(uriPrefixes)); Check.NotNullOrEmpty(uriPrefixes, nameof(uriPrefixes));
_logger = options.Logger ?? new WireMockConsoleLogger();
foreach (string uriPrefix in uriPrefixes) foreach (string uriPrefix in uriPrefixes)
{ {
Urls.Add(uriPrefix); Urls.Add(uriPrefix);
@@ -38,6 +43,8 @@ namespace WireMock.Owin
public List<int> Ports { get; } = new List<int>(); public List<int> Ports { get; } = new List<int>();
public Exception RunningException => _runningException;
[PublicAPI] [PublicAPI]
public Task StartAsync() public Task StartAsync()
{ {
@@ -58,37 +65,46 @@ namespace WireMock.Owin
private void StartServers() private void StartServers()
{ {
#if NET46 #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 #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 #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>(); 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();
} }
catch (Exception e)
IsStarted = true;
while (!_cts.IsCancellationRequested)
{ {
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());
} }
finally
IsStarted = false;
foreach (var server in servers)
{ {
server.Dispose(); IsStarted = false;
// Dispose all servers in finally block to make sure clean up allocated resource on error happening
servers.ForEach((s) => s.Dispose());
} }
} }
} }

View File

@@ -1,20 +1,20 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading;
using JetBrains.Annotations;
using Newtonsoft.Json;
using WireMock.Http; using WireMock.Http;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Validation; using WireMock.Validation;
using WireMock.Owin;
using WireMock.ResponseProviders;
namespace WireMock.Server namespace WireMock.Server
{ {
@@ -32,7 +32,7 @@ namespace WireMock.Server
/// Gets a value indicating whether this server is started. /// Gets a value indicating whether this server is started.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public bool IsStarted { get; } public bool IsStarted { get => _httpServer == null ? false : _httpServer.IsStarted; }
/// <summary> /// <summary>
/// Gets the ports. /// Gets the ports.
@@ -186,10 +186,23 @@ namespace WireMock.Server
_httpServer.StartAsync(); _httpServer.StartAsync();
// Fix for 'Bug: Server not listening after Start() returns (on macOS)' using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout))
Task.Delay(ServerStartDelay).Wait(); {
while (!_httpServer.IsStarted)
IsStarted = _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) if (settings.AllowPartialMapping == true)
{ {