Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
interface IRavenPersistenceLifecycle
{
Task Initialize(CancellationToken cancellationToken = default);
Task Stop(CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public async Task Initialize(CancellationToken cancellationToken = default)
}
}

public Task Stop(CancellationToken cancellationToken = default) => database!.Stop(cancellationToken);

public void Dispose()
{
documentStore?.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public async Task Initialize(CancellationToken cancellationToken = default)
}
}

public Task Stop(CancellationToken cancellationToken = default) => Task.CompletedTask; // We are not stopping an external instance

public void Dispose() => documentStore?.Dispose();

IDocumentStore? documentStore;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ sealed class RavenPersistenceLifecycleHostedService(IRavenPersistenceLifecycle l
{
public Task StartAsync(CancellationToken cancellationToken) => lifecycle.Initialize(cancellationToken);

public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => lifecycle.Stop(cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ namespace ServiceControl.Persistence.RavenDB
interface IRavenPersistenceLifecycle
{
Task Initialize(CancellationToken cancellationToken = default);
Task Stop(CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ public async Task Initialize(CancellationToken cancellationToken)
}
}

public async Task Stop(CancellationToken cancellationToken)
{
if (database != null)
{
await database.Stop(cancellationToken);
}
}

public void Dispose()
{
documentStore?.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public async Task Initialize(CancellationToken cancellationToken)
}
}

public Task Stop(CancellationToken cancellationToken) => Task.CompletedTask;

public void Dispose() => documentStore?.Dispose();

IDocumentStore? documentStore;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ class RavenPersistenceLifecycleHostedService(IRavenPersistenceLifecycle persiste
{
public Task StartAsync(CancellationToken cancellationToken) => persistenceLifecycle.Initialize(cancellationToken);

public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => persistenceLifecycle.Stop(cancellationToken);
}
}
92 changes: 87 additions & 5 deletions src/ServiceControl.RavenDB/EmbeddedDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace ServiceControl.RavenDB
using Raven.Client.Documents.Conventions;
using Raven.Client.ServerWide.Operations;
using Raven.Embedded;
using Sparrow.Logging;

public sealed class EmbeddedDatabase : IDisposable
{
Expand Down Expand Up @@ -52,6 +53,20 @@ public static EmbeddedDatabase Start(EmbeddedDatabaseConfiguration databaseConfi

var nugetPackagesPath = Path.Combine(databaseConfiguration.DbPath, "Packages", "NuGet");

var logMode = Enum.Parse<LogMode>(databaseConfiguration.LogsMode);

if (logMode == LogMode.Information) // Most verbose
{
LoggingSource.Instance.EnableConsoleLogging();
LoggingSource.Instance.SetupLogMode(
logMode,
Path.Combine(databaseConfiguration.LogPath, "Raven.Embedded"),
retentionTime: TimeSpan.FromDays(14),
retentionSize: 1024 * 1024 * 10,
compress: false
);
}

Logger.InfoFormat("Loading RavenDB license from {0}", licenseFileNameAndServerDirectory.LicenseFileName);
var serverOptions = new ServerOptions
{
Expand Down Expand Up @@ -85,6 +100,7 @@ public static EmbeddedDatabase Start(EmbeddedDatabaseConfiguration databaseConfi

void Start(ServerOptions serverOptions)
{
this.serverOptions = serverOptions;
EmbeddedServer.Instance.ServerProcessExited += OnServerProcessExited;
EmbeddedServer.Instance.StartServer(serverOptions);

Expand Down Expand Up @@ -162,23 +178,87 @@ public async Task<IDocumentStore> Connect(CancellationToken cancellationToken)

public async Task DeleteDatabase(string dbName)
{
using var store = await EmbeddedServer.Instance.GetDocumentStoreAsync(new DatabaseOptions(dbName) { SkipCreatingDatabase = true });
using var store = await EmbeddedServer.Instance.GetDocumentStoreAsync(new DatabaseOptions(dbName)
{
SkipCreatingDatabase = true
});
await store.Maintenance.Server.SendAsync(new DeleteDatabasesOperation(dbName, true));
}

public async Task Stop(CancellationToken cancellationToken)
{
Logger.Debug("Stopping RavenDB server");
EmbeddedServer.Instance.ServerProcessExited -= OnServerProcessExited;

await shutdownTokenSource.CancelAsync();

// EmbeddedServer does not have have an async Stop method, the Dispose operation blocks and waits
// until its GracefulShutdownTimeout is reached and then does a Process.Kill
//
// This logic gets the child process ID uses a Task.Delay with infinite.
//
// a. The Task.Delay gets cancelled first
// b. The EmbeddedServer.Dispose completes first
//
// If the Task.Delay gets cancelled first this means Dispose is still running and
// then we kill the process

serverOptions!.GracefulShutdownTimeout = TimeSpan.FromHours(1); // During Stop/Dispose we manually control this

var processId = await EmbeddedServer.Instance.GetServerProcessIdAsync(cancellationToken);

// Dispose always need to be invoked, even when already cancelled
var disposeTask = Task.Run(() => EmbeddedServer.Instance.Dispose(), CancellationToken.None);

var waitForCancellationTask = Task.Delay(Timeout.Infinite, cancellationToken);

var delayIsCancelled = waitForCancellationTask == await Task.WhenAny(disposeTask, waitForCancellationTask);

if (delayIsCancelled)
{
try
{
Logger.WarnFormat("Killing RavenDB server PID {0} because host cancelled", processId);
using var ravenChildProcess = Process.GetProcessById(processId);
ravenChildProcess.Kill(entireProcessTree: true);
// Kill only signals
Logger.WarnFormat("Waiting for RavenDB server PID {0} to exit... ", processId);
await ravenChildProcess.WaitForExitAsync(CancellationToken.None);
}
catch (Exception e)
{
Logger.ErrorFormat("Failed to kill RavenDB server PID {0} shutdown\n{1}", processId, e);
}
}

serverOptions = null!;
Logger.Debug("Stopped RavenDB server");
}

public void Dispose()
{
if (disposed)
{
return;
}

EmbeddedServer.Instance.ServerProcessExited -= OnServerProcessExited;
if (serverOptions != null)
{
EmbeddedServer.Instance.ServerProcessExited -= OnServerProcessExited;
}

shutdownTokenSource.Cancel();
Logger.Debug("Disposing RavenDB server");
EmbeddedServer.Instance.Dispose();
Logger.Debug("Dispose RavenDB server");

if (serverOptions != null)
{
// Set GracefulShutdownTimeout to Zero and exit ASAP, under normal operation instance would already
// have been allowed to gracefully stop during "Stop" method.
serverOptions!.GracefulShutdownTimeout = TimeSpan.Zero;
Logger.Debug("Disposing RavenDB server");
EmbeddedServer.Instance.Dispose();
Logger.Debug("Disposed RavenDB server");
}

shutdownTokenSource.Dispose();
applicationStoppingRegistration.Dispose();

Expand Down Expand Up @@ -213,6 +293,7 @@ static long DataSize(EmbeddedDatabaseConfiguration configuration)
{
return -1;
}

return info.Length;
}
catch
Expand Down Expand Up @@ -262,6 +343,7 @@ static long DirSize(DirectoryInfo d)
readonly EmbeddedDatabaseConfiguration configuration;
readonly CancellationToken shutdownCancellationToken;
readonly CancellationTokenRegistration applicationStoppingRegistration;
ServerOptions? serverOptions;

static TimeSpan delayBetweenRestarts = TimeSpan.FromSeconds(60);
static readonly ILog Logger = LogManager.GetLogger<EmbeddedDatabase>();
Expand Down