Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f06ee99
Add server quiesce and stop-listening primitives for graceful shutdown
yuseok-kim-edushare May 26, 2026
ec0efde
Quiesce RESP sessions and pub/sub during graceful shutdown
yuseok-kim-edushare May 26, 2026
7da3613
Add GarnetServer.ShutdownAsync with configurable shutdown timeout
yuseok-kim-edushare May 26, 2026
4adc1b5
Wire graceful shutdown into hosts and add tests
yuseok-kim-edushare May 26, 2026
b7209ec
formmating
yuseok-kim-edushare May 26, 2026
a25810c
Mark as Explicit test: ShutdownDataConsistencyTests
yuseok-kim-edushare May 27, 2026
02dddf8
Fix by Copilot's suggestion
yuseok-kim-edushare May 27, 2026
7975961
Fix finalizeData
yuseok-kim-edushare May 27, 2026
45a8a5f
Fix resource leak on Test codes by copilot
yuseok-kim-edushare May 27, 2026
2c49daf
Fix Test: StoplisteningDuringActiveConnectionAttempts
yuseok-kim-edushare May 27, 2026
a13a12c
Update ShutdownAsync use configured timeout : to reflect copilot sugg…
yuseok-kim-edushare May 27, 2026
7ba6acf
Centralize shutdown data-finalization timeout and add forced-shutdown…
yuseok-kim-edushare May 27, 2026
a58b370
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 3, 2026
3fcf5f7
Fix Conflict with #2145
yuseok-kim-edushare Oct 3, 2026
ad069d1
Add Test that reflect StoreApi.TakeCheckpointAsync related change
yuseok-kim-edushare Oct 3, 2026
42f9920
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 8, 2026
80164b3
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 9, 2026
5707e05
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 12, 2026
9f9f29b
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 12, 2026
bfdd961
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 14, 2026
d93f373
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 15, 2026
6613219
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 18, 2026
2ff74b9
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 21, 2026
c10741e
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 26, 2026
6d7e69d
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 28, 2026
b611f15
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Oct 29, 2026
2946109
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Nov 2, 2026
bb18ee4
Merge branch 'main' into yuseok-kim/graceful_shutdown-v2
yuseok-kim-edushare Nov 5, 2026
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
40 changes: 39 additions & 1 deletion hosting/Windows/Garnet.worker/Program.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

class Program
{
// Extra host budget beyond connection drain + data finalization (quiesce, stop listening, etc.).
private const int HostShutdownMarginSeconds = 5;

static void Main(string[] args)
{
// Pre-parse shutdown-related arguments so we can configure both the host shutdown budget
// and the Worker's connection-drain timeout before the generic host is built.
var shutdownTimeoutSeconds = ParseIntOption(args, "--shutdown-timeout", "-shutdown-timeout", GarnetServerOptions.DefaultShutdownTimeoutSeconds);
var dataFinalizationTimeoutSeconds = ParseIntOption(args, "--data-finalization-timeout", "-data-finalization-timeout", GarnetServerOptions.DefaultDataFinalizationTimeoutSeconds);
var shutdownTimeout = TimeSpan.FromSeconds(shutdownTimeoutSeconds);

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService(_ => new Worker(args));

// Tell the .NET host (and the Windows SCM via WindowsServiceLifetime) how long to wait
// before forcibly killing the process: drain + data finalization + margin.
builder.Services.Configure<HostOptions>(opts =>
{
opts.ShutdownTimeout = shutdownTimeout
+ TimeSpan.FromSeconds(dataFinalizationTimeoutSeconds)
+ TimeSpan.FromSeconds(HostShutdownMarginSeconds);
});

builder.Services.AddHostedService(_ => new Worker(args, shutdownTimeout));

builder.Services.AddWindowsService(options =>
{
Expand All @@ -20,4 +41,21 @@ static void Main(string[] args)
var host = builder.Build();
host.Run();
}

/// <summary>
/// Scans <paramref name="args"/> for <paramref name="longName"/> (or <paramref name="shortName"/>) and returns
/// the parsed positive integer, or <paramref name="defaultSeconds"/> if the argument is absent or invalid.
/// </summary>
private static int ParseIntOption(string[] args, string longName, string shortName, int defaultSeconds)
{
for (var i = 0; i < args.Length - 1; i++)
{
if (args[i] is var name && (name == longName || name == shortName) &&
int.TryParse(args[i + 1], out var value) && value > 0)
{
return value;
}
}
return defaultSeconds;
}
}
34 changes: 31 additions & 3 deletions hosting/Windows/Garnet.worker/Worker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ public class Worker : BackgroundService
{
private bool _isDisposed = false;
private readonly string[] args;
private readonly TimeSpan _shutdownTimeout;

private GarnetServer server;

public Worker(string[] args)
/// <param name="args">Command line arguments forwarded to <see cref="GarnetServer"/>.</param>
/// <param name="shutdownTimeout">
/// How long to wait for active connections to drain during graceful shutdown.
/// Must fit within the host <see cref="Microsoft.Extensions.Hosting.HostOptions.ShutdownTimeout"/>,
/// which also budgets for data finalization and a small margin (see <c>Program.cs</c>).
/// </param>
public Worker(string[] args, TimeSpan shutdownTimeout)
{
this.args = args;
_shutdownTimeout = shutdownTimeout;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
Expand All @@ -43,8 +51,26 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
public override async Task StopAsync(CancellationToken cancellationToken)
{
Dispose();
await base.StopAsync(cancellationToken).ConfigureAwait(false);
try
{
if (server != null)
{
// If cancellation is requested, we will skip the graceful shutdown and proceed to dispose immediately
bool isForceShutdown = cancellationToken.IsCancellationRequested;
// Perform graceful shutdown with AOF commit and checkpoint when not forced Shutdown From OS.
await server.ShutdownAsync(timeout: _shutdownTimeout, noSave: isForceShutdown, token: cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// Force shutdown requested - proceed to dispose
}
finally
{
// Ensure base class cleanup although cancellationToken is cancelled
await base.StopAsync(CancellationToken.None).ConfigureAwait(false);
Comment thread
yuseok-kim-edushare marked this conversation as resolved.
Resolved
Dispose();
}
}

public override void Dispose()
Expand All @@ -55,6 +81,8 @@ public override void Dispose()
}
server?.Dispose();
_isDisposed = true;
base.Dispose();
GC.SuppressFinalize(this);
}
}
}
11 changes: 11 additions & 0 deletions libs/host/Configuration/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,15 @@ internal sealed class Options : ICloneable
[Option("network-connection-limit", Required = false, HelpText = "Maximum number of simultaneously active network connections across all listeners, or -1 for unlimited. Equivalent to the Redis maxclients parameter, and settable at runtime with CONFIG SET maxclients.")]
public int NetworkConnectionLimit { get; set; }

[IntRangeValidation(1, int.MaxValue)]
[Option("shutdown-timeout", Required = false, HelpText = "Timeout in seconds to wait for active connections to drain during graceful shutdown. " +
"The Windows SCM default pre-kill wait is 5 seconds, so values below 5 are not recommended when running as a Windows service.")]
public int ShutdownTimeoutSeconds { get; set; }

[IntRangeValidation(1, int.MaxValue)]
[Option("data-finalization-timeout", Required = false, HelpText = "Timeout in seconds for AOF commit and checkpoint during graceful shutdown data finalization.")]
public int DataFinalizationTimeoutSeconds { get; set; }

[OptionValidation]
[Option("use-azure-storage", Required = false, HelpText = "Use Azure Page Blobs for storage instead of local storage.")]
public bool? UseAzureStorage { get; set; }
Expand Down Expand Up @@ -1015,6 +1024,8 @@ public GarnetServerOptions GetServerOptions(ILogger logger = null)
ThreadPoolMinIOCompletionThreads = ThreadPoolMinIOCompletionThreads,
ThreadPoolMaxIOCompletionThreads = ThreadPoolMaxIOCompletionThreads,
NetworkConnectionLimit = NetworkConnectionLimit,
ShutdownTimeoutSeconds = ShutdownTimeoutSeconds,
DataFinalizationTimeoutSeconds = DataFinalizationTimeoutSeconds,
DeviceFactoryCreator = deviceType == DeviceType.AzureStorage ? azureFactoryCreator()
: new LocalStorageNamedDeviceFactoryCreator(
deviceType: deviceType,
Expand Down
Loading
Loading