Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Threading;
using NServiceBus.AcceptanceTesting.Customization;
using NServiceBus.Logging;
using NUnit.Framework;

/// <summary>
Expand All @@ -16,6 +17,8 @@ public abstract partial class NServiceBusAcceptanceTest
[SetUp]
public void SetUp()
{
LogManager.Use<DefaultFactory>(); // Ensures that every test the log manager is 'reset' as log manager can otherwise point to disposed resources. For example, when a test uses NServiceBus hosting

#if NETFRAMEWORK
// Hack: prevents SerializationException ... Type 'x' in assembly 'y' is not marked as serializable.
// https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/mitigation-deserialization-of-objects-across-app-domains
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public async Task CanRunMaintenanceMode()
{
var bootstrapper = new MaintenanceBootstrapper(settings);

var host = bootstrapper.HostBuilder.Build();
using var host = bootstrapper.HostBuilder.Build();

await host.StartAsync();
await host.StopAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ async Task InitializeServiceControl(ScenarioContext context)
hostBuilderCustomization(bootstrapper.HostBuilder);

host = bootstrapper.HostBuilder.Build();
await host.Services.GetRequiredService<Persistence.IPersistenceLifecycle>().Initialize();
await host.StartAsync();
DomainEvents = host.Services.GetService<IDomainEvents>();
}
Expand Down Expand Up @@ -209,6 +210,7 @@ public override async Task Stop()
await host.StopAsync();
HttpClient.Dispose();
Handler.Dispose();
host.Dispose();
DirectoryDeleter.Delete(Settings.PersisterSpecificSettings.DatabasePath);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public override async Task Stop()
await host.StopAsync();
HttpClient.Dispose();
handler.Dispose();
host.Dispose();
}

bootstrapper = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public override async Task Stop()
await host.StopAsync();
HttpClient.Dispose();
Handler.Dispose();
host.Dispose();
}

bootstrapper = null;
Expand Down
13 changes: 8 additions & 5 deletions src/ServiceControl.Monitoring/Hosting/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@ namespace ServiceControl.Monitoring

class RunCommand : AbstractCommand
{
public override Task Execute(Settings settings)
public override async Task Execute(Settings settings)
{
//RunAsWindowsService can't be a property on Settings class because it
//would be exposed as app.config configurable option and break ATT approvals
var runAsWindowsService = !Environment.UserInteractive && !settings.Portable;
var configuration = new EndpointConfiguration(settings.ServiceName);

var host = new Bootstrapper(_ => { }, settings, configuration).HostBuilder;
var hostBuilder = new Bootstrapper(_ => { }, settings, configuration).HostBuilder;

if (runAsWindowsService)
{
host.UseWindowsService();
hostBuilder.UseWindowsService();
}
else
{
host.UseConsoleLifetime();
hostBuilder.UseConsoleLifetime();
}

return host.Build().RunAsync();
using (var host = hostBuilder.Build())
{
await host.RunAsync();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ServiceControl.MultiInstance.AcceptanceTests.TestSupport
using System.Threading.Tasks;
using AcceptanceTesting;
using Audit.Infrastructure.OWIN;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Owin.Builder;
using Newtonsoft.Json;
Expand Down Expand Up @@ -193,6 +194,7 @@ async Task InitializeServiceControl(ScenarioContext context, int instancePort, i
};

host = bootstrapper.HostBuilder.Build();
await host.Services.GetRequiredService<Persistence.IPersistenceLifecycle>().Initialize();
await host.StartAsync();
hosts[instanceName] = host;
}
Expand Down Expand Up @@ -367,7 +369,9 @@ public override async Task Stop()
{
if (hosts.ContainsKey(instanceName))
{
await hosts[instanceName].StopAsync();
var host = hosts[instanceName];
await host.StopAsync();
host.Dispose();
}
HttpClients[instanceName].Dispose();
handlers[instanceName].Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
using Raven.Client.Embedded;
using ServiceControl.Persistence;

class RavenDbPersistenceLifecycle : IPersistenceLifecycle
class RavenDbPersistenceLifecycle : IPersistenceLifecycle, IDisposable
{
public RavenDbPersistenceLifecycle(RavenStartup ravenStartup, EmbeddableDocumentStore documentStore)
{
this.ravenStartup = ravenStartup;
this.documentStore = documentStore;
}

public async Task Start(CancellationToken cancellationToken)
public async Task Initialize(CancellationToken cancellationToken)
{
Logger.Info("Database initialization starting");
documentStore.Initialize();
Expand All @@ -28,12 +28,6 @@ public async Task Start(CancellationToken cancellationToken)
await TestAllIndexesAndResetIfException(documentStore);
}

public Task Stop(CancellationToken cancellationToken)
{
documentStore.Dispose();
return Task.CompletedTask;
}

static async Task TestAllIndexesAndResetIfException(IDocumentStore store)
{
foreach (var index in store.DatabaseCommands.GetStatistics().Indexes)
Expand All @@ -54,6 +48,11 @@ static async Task TestAllIndexesAndResetIfException(IDocumentStore store)
}
}

public void Dispose()
{
documentStore?.Dispose();
}

readonly RavenStartup ravenStartup;
readonly EmbeddableDocumentStore documentStore;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using Raven.Client.Documents;
using ServiceControl.Persistence;

class RavenDbEmbeddedPersistenceLifecycle : IPersistenceLifecycle
class RavenDbEmbeddedPersistenceLifecycle : IPersistenceLifecycle, IDisposable
{
public RavenDbEmbeddedPersistenceLifecycle(RavenDBPersisterSettings databaseConfiguration)
{
Expand All @@ -17,25 +17,22 @@ public IDocumentStore GetDocumentStore()
{
if (documentStore == null)
{
throw new InvalidOperationException("Document store is not available until the persistence have been started");
throw new InvalidOperationException("Document store is not available. Ensure `IPersistenceLifecycle.Initialize` is invoked");
}

return documentStore;
}

public async Task Start(CancellationToken cancellationToken)
public async Task Initialize(CancellationToken cancellationToken)
{
database = EmbeddedDatabase.Start(databaseConfiguration);

documentStore = await database.Connect(cancellationToken);
}

public Task Stop(CancellationToken cancellationToken)
public void Dispose()
{
documentStore?.Dispose();
database?.Dispose();

return Task.CompletedTask;
}

IDocumentStore documentStore;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Raven.Client.Documents.Conventions;
using ServiceControl.Persistence;

class RavenDbExternalPersistenceLifecycle : IPersistenceLifecycle
class RavenDbExternalPersistenceLifecycle : IPersistenceLifecycle, IDisposable
{
public RavenDbExternalPersistenceLifecycle(RavenDBPersisterSettings settings)
{
Expand All @@ -18,13 +18,13 @@ public IDocumentStore GetDocumentStore()
{
if (documentStore == null)
{
throw new InvalidOperationException("Document store is not available until the persistence have been started");
throw new InvalidOperationException("Document store is not available. Ensure `IPersistenceLifecycle.Initialize` is invoked");
}

return documentStore;
}

public async Task Start(CancellationToken cancellationToken)
public async Task Initialize(CancellationToken cancellationToken)
{
var store = new DocumentStore
{
Expand All @@ -50,11 +50,9 @@ public async Task Start(CancellationToken cancellationToken)
await databaseSetup.Execute(store, cancellationToken).ConfigureAwait(false);
}

public Task Stop(CancellationToken cancellationToken)
public void Dispose()
{
documentStore?.Dispose();

return Task.CompletedTask;
}

IDocumentStore documentStore;
Expand Down
13 changes: 8 additions & 5 deletions src/ServiceControl.Persistence.RavenDb5/RavenDbInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using ServiceControl.Persistence;

class RavenDbInstaller : IPersistenceInstaller
{
public RavenDbInstaller(IPersistenceLifecycle lifecycle)
public RavenDbInstaller(ServiceCollection services)
{
this.lifecycle = lifecycle;
this.services = services;
}

public async Task Install(CancellationToken cancellationToken)
{
await lifecycle.Start(cancellationToken);
await lifecycle.Stop(cancellationToken);
using var serviceProvider = services.BuildServiceProvider();

var lifecycle = serviceProvider.GetRequiredService<IPersistenceLifecycle>();
await lifecycle.Initialize(cancellationToken);
}

readonly IPersistenceLifecycle lifecycle;
readonly ServiceCollection services;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,7 @@ public IPersistenceInstaller CreateInstaller()
var serviceCollection = new ServiceCollection();

serviceCollection.AddSingleton(settings);
ConfigureLifecycle(serviceCollection);

var lifecycle = serviceCollection.BuildServiceProvider().GetRequiredService<IPersistenceLifecycle>();

return new RavenDbInstaller(lifecycle);
return new RavenDbInstaller(serviceCollection);
}

readonly RavenDBPersisterSettings settings;
Expand Down
13 changes: 11 additions & 2 deletions src/ServiceControl.Persistence.Tests/BaseHostTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NUnit.Framework;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using ServiceControl.Persistence;

[Parallelizable(ParallelScope.All)]
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
Expand All @@ -13,10 +14,18 @@ public abstract class BaseHostTest
protected BaseHostTest() => testHost = CreateHostBuilder().Build();

[SetUp]
public async Task SetUp() => await testHost.StartAsync();
public async Task SetUp()
{
await GetRequiredService<IPersistenceLifecycle>().Initialize();
await testHost.StartAsync();
}

[TearDown]
public async Task TearDown() => await testHost.StopAsync();
public async Task TearDown()
{
await testHost.StopAsync();
testHost.Dispose();
}

protected T GetRequiredService<T>() => testHost.Services.GetRequiredService<T>();

Expand Down
2 changes: 1 addition & 1 deletion src/ServiceControl.Persistence/IPersistenceInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ public interface IPersistenceInstaller
{
Task Install(CancellationToken cancellationToken = default);
}
}
}
7 changes: 3 additions & 4 deletions src/ServiceControl.Persistence/IPersistenceLifecycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
using System.Threading;
using System.Threading.Tasks;

public interface IPersistenceLifecycle // TODO: We don't need this, implementations should just implement IHostedService
public interface IPersistenceLifecycle
{
Task Start(CancellationToken cancellationToken = default);
Task Stop(CancellationToken cancellationToken = default);
Task Initialize(CancellationToken cancellationToken = default);
}
}
}
38 changes: 22 additions & 16 deletions src/ServiceControl/Hosting/Commands/ImportFailedErrorsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Particular.ServiceControl;
using Particular.ServiceControl.Commands;
using Particular.ServiceControl.Hosting;
using Persistence;
using ServiceBus.Management.Infrastructure.Settings;

class ImportFailedErrorsCommand : AbstractCommand
Expand All @@ -24,26 +25,31 @@ public override async Task Execute(HostArguments args, Settings settings)

var loggingSettings = new LoggingSettings(settings.ServiceName, LogLevel.Info, LogLevel.Info);
var bootstrapper = new Bootstrapper(settings, busConfiguration, loggingSettings);
var host = bootstrapper.HostBuilder.Build();
await host.StartAsync(CancellationToken.None);
using (var host = bootstrapper.HostBuilder.Build())
{
var lifeCycle = host.Services.GetRequiredService<IPersistenceLifecycle>();
await lifeCycle.Initialize(); // Initialized IDocumentStore, this is needed as many hosted services have (indirect) dependencies on it.

var importFailedErrors = host.Services.GetRequiredService<ImportFailedErrors>();
await host.StartAsync(CancellationToken.None);

using (var tokenSource = new CancellationTokenSource())
{
Console.CancelKeyPress += (sender, eventArgs) => { tokenSource.Cancel(); };
var importFailedErrors = host.Services.GetRequiredService<ImportFailedErrors>();

try
{
await importFailedErrors.Run(tokenSource.Token);
}
catch (OperationCanceledException)
using (var tokenSource = new CancellationTokenSource())
{
// no-op
}
finally
{
await host.StopAsync(CancellationToken.None);
Console.CancelKeyPress += (sender, eventArgs) => { tokenSource.Cancel(); };

try
{
await importFailedErrors.Run(tokenSource.Token);
}
catch (OperationCanceledException)
{
// no-op
}
finally
{
await host.StopAsync(CancellationToken.None);
}
}
}
}
Expand Down
Loading