diff --git a/Directory.Packages.props b/Directory.Packages.props
index 0719a40ed8..bc41f0c970 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -9,6 +9,8 @@
+
+
diff --git a/benchmark/BDN.benchmark/Embedded/EmbeddedRespServer.cs b/benchmark/BDN.benchmark/Embedded/EmbeddedRespServer.cs
index c94867b102..d0989eb3bc 100644
--- a/benchmark/BDN.benchmark/Embedded/EmbeddedRespServer.cs
+++ b/benchmark/BDN.benchmark/Embedded/EmbeddedRespServer.cs
@@ -4,6 +4,7 @@
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
namespace Embedded.server
{
@@ -18,9 +19,15 @@ internal sealed class EmbeddedRespServer : GarnetServer
/// Creates an EmbeddedRespServer instance
///
/// Server options to configure the base GarnetServer instance
- /// Logger factory to configure the base GarnetServer instance
+ /// Logger factory to configure the base GarnetServer instance
/// Server network
- public EmbeddedRespServer(GarnetServerOptions opts, ILoggerFactory loggerFactory = null, GarnetServerEmbedded server = null) : base(opts, loggerFactory, server)
+ /// Server network
+ public EmbeddedRespServer(
+ IOptions opts,
+ ILogger logger,
+ GarnetServerEmbedded server,
+ StoreWrapper store)
+ : base(opts, logger, server, store)
{
this.garnetServerEmbedded = server;
}
diff --git a/benchmark/BDN.benchmark/Lua/LuaRunnerOperations.cs b/benchmark/BDN.benchmark/Lua/LuaRunnerOperations.cs
index 07d28e641b..4cb2c7f2a3 100644
--- a/benchmark/BDN.benchmark/Lua/LuaRunnerOperations.cs
+++ b/benchmark/BDN.benchmark/Lua/LuaRunnerOperations.cs
@@ -3,6 +3,7 @@
using BenchmarkDotNet.Attributes;
using Embedded.server;
+using Garnet.host;
using Garnet.server;
namespace BDN.benchmark.Lua
@@ -154,6 +155,14 @@ public IEnumerable LuaParamsProvider()
[GlobalSetup]
public void GlobalSetup()
{
+ var builder = GarnetApplication.CreateHostBuilder([], new GarnetServerOptions()
+ {
+ EnableLua = true,
+ QuietMode = true
+ });
+
+ var app = builder.Build();
+
server = new EmbeddedRespServer(new GarnetServerOptions() { EnableLua = true, QuietMode = true });
session = server.GetRespSession();
diff --git a/hosting/Windows/Garnet.worker/Program.cs b/hosting/Windows/Garnet.worker/Program.cs
index 8418da8671..a4253e7b79 100644
--- a/hosting/Windows/Garnet.worker/Program.cs
+++ b/hosting/Windows/Garnet.worker/Program.cs
@@ -5,19 +5,13 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-class Program
-{
- static void Main(string[] args)
- {
- var builder = Host.CreateApplicationBuilder(args);
- builder.Services.AddHostedService(_ => new Worker(args));
+var builder = Host.CreateApplicationBuilder(args);
+builder.Services.AddHostedService(_ => new Worker(args));
- builder.Services.AddWindowsService(options =>
- {
- options.ServiceName = "Microsoft Garnet Server";
- });
+builder.Services.AddWindowsService(options =>
+{
+ options.ServiceName = "Microsoft Garnet Server";
+});
- var host = builder.Build();
- host.Run();
- }
-}
\ No newline at end of file
+var host = builder.Build();
+host.Run();
\ No newline at end of file
diff --git a/hosting/Windows/Garnet.worker/Worker.cs b/hosting/Windows/Garnet.worker/Worker.cs
index d69adb7e3c..e4e6dd5997 100644
--- a/hosting/Windows/Garnet.worker/Worker.cs
+++ b/hosting/Windows/Garnet.worker/Worker.cs
@@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using Garnet.host;
using Microsoft.Extensions.Hosting;
namespace Garnet
@@ -13,7 +14,7 @@ public class Worker : BackgroundService
private bool _isDisposed = false;
private readonly string[] args;
- private GarnetServer server;
+ private GarnetApplication server;
public Worker(string[] args)
{
@@ -22,19 +23,11 @@ public Worker(string[] args)
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
- try
- {
- server = new GarnetServer(args);
+ var builder = GarnetApplication.CreateHostBuilder(args);
- // Start the server
- server.Start();
+ var app = builder.Build();
- await Task.Delay(Timeout.Infinite, stoppingToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Unable to initialize server due to exception: {ex.Message}");
- }
+ await app.RunAsync(stoppingToken);
}
///
diff --git a/libs/cluster/ClusterFactory.cs b/libs/cluster/ClusterFactory.cs
index 8a8ef903e6..f4e85a3c21 100644
--- a/libs/cluster/ClusterFactory.cs
+++ b/libs/cluster/ClusterFactory.cs
@@ -5,19 +5,25 @@
using Microsoft.Extensions.Logging;
using Tsavorite.core;
-namespace Garnet.cluster
+namespace Garnet.cluster;
+
+///
+/// Cluster factory
+///
+public class ClusterFactory : IClusterFactory
{
- ///
- /// Cluster factory
- ///
- public class ClusterFactory : IClusterFactory
+ private readonly ILogger logger;
+
+ public ClusterFactory(ILogger logger)
{
- ///
- public DeviceLogCommitCheckpointManager CreateCheckpointManager(INamedDeviceFactory deviceFactory, ICheckpointNamingScheme checkpointNamingScheme, bool isMainStore, ILogger logger = default)
- => new ReplicationLogCheckpointManager(deviceFactory, checkpointNamingScheme, isMainStore, logger: logger);
-
- ///
- public IClusterProvider CreateClusterProvider(StoreWrapper store)
- => new ClusterProvider(store);
+ this.logger = logger;
}
+
+ ///
+ public DeviceLogCommitCheckpointManager CreateCheckpointManager(INamedDeviceFactory deviceFactory, ICheckpointNamingScheme checkpointNamingScheme, bool isMainStore)
+ => new ReplicationLogCheckpointManager(deviceFactory, checkpointNamingScheme, isMainStore, logger: logger);
+
+ ///
+ public IClusterProvider CreateClusterProvider(StoreWrapper store)
+ => new ClusterProvider(store);
}
\ No newline at end of file
diff --git a/libs/cluster/Server/ClusterProvider.cs b/libs/cluster/Server/ClusterProvider.cs
index b1e9abc1ee..bc42c86baf 100644
--- a/libs/cluster/Server/ClusterProvider.cs
+++ b/libs/cluster/Server/ClusterProvider.cs
@@ -31,7 +31,6 @@ public class ClusterProvider : IClusterProvider
internal readonly ReplicationManager replicationManager;
internal readonly FailoverManager failoverManager;
internal readonly MigrationManager migrationManager;
- internal readonly ILoggerFactory loggerFactory;
internal readonly StoreWrapper storeWrapper;
internal readonly GarnetServerOptions serverOptions;
internal long GarnetCurrentEpoch = 1;
@@ -47,6 +46,8 @@ public class ClusterProvider : IClusterProvider
///
public string ClusterPassword => authContainer.ClusterPassword;
+ public ILogger logger;
+
///
/// Create new cluster provider
///
@@ -54,7 +55,7 @@ public ClusterProvider(StoreWrapper storeWrapper)
{
this.storeWrapper = storeWrapper;
this.serverOptions = storeWrapper.serverOptions;
- this.loggerFactory = storeWrapper.loggerFactory;
+ this.logger = storeWrapper.logger;
authContainer = new ClusterAuthContainer
{
@@ -67,11 +68,11 @@ public ClusterProvider(StoreWrapper storeWrapper)
throw new Exception("Gossip sample fraction should be in range [0,100]");
}
- this.clusterManager = new ClusterManager(this, logger: loggerFactory?.CreateLogger("ClusterManager"));
- this.replicationManager = new ReplicationManager(this, logger: loggerFactory?.CreateLogger("ReplicationManager"));
+ this.clusterManager = new ClusterManager(this, logger: logger);
+ this.replicationManager = new ReplicationManager(this, logger: logger);
- this.failoverManager = new FailoverManager(this, logger: loggerFactory?.CreateLogger("FailoverManager"));
- this.migrationManager = new MigrationManager(this, logger: loggerFactory?.CreateLogger("MigrationManager"));
+ this.failoverManager = new FailoverManager(this, logger: logger);
+ this.migrationManager = new MigrationManager(this, logger: logger);
}
///
diff --git a/libs/cluster/Server/Migration/MigrateSession.cs b/libs/cluster/Server/Migration/MigrateSession.cs
index 04612e8360..e8b5648350 100644
--- a/libs/cluster/Server/Migration/MigrateSession.cs
+++ b/libs/cluster/Server/Migration/MigrateSession.cs
@@ -127,7 +127,8 @@ internal MigrateSession(
MigratingKeysWorkingSet keys,
TransferOption transferOption)
{
- this.logger = clusterProvider.loggerFactory.CreateLogger($"MigrateSession - {GetHashCode()}"); ;
+ //this.logger = clusterProvider.loggerFactory.CreateLogger($"MigrateSession - {GetHashCode()}"); ;
+ this.logger = clusterProvider.logger;
this.clusterSession = clusterSession;
this.clusterProvider = clusterProvider;
this._targetAddress = _targetAddress;
diff --git a/libs/host/Garnet.host.csproj b/libs/host/Garnet.host.csproj
index 1c1259fec3..c8d401b6b6 100644
--- a/libs/host/Garnet.host.csproj
+++ b/libs/host/Garnet.host.csproj
@@ -20,6 +20,9 @@
+
+
+
@@ -43,5 +46,4 @@
-
diff --git a/libs/host/GarnetApplication.cs b/libs/host/GarnetApplication.cs
new file mode 100644
index 0000000000..a9ea80bf12
--- /dev/null
+++ b/libs/host/GarnetApplication.cs
@@ -0,0 +1,106 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Garnet.common;
+using Garnet.server;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+
+namespace Garnet.host;
+
+///
+///
+///
+public class GarnetApplication : IHost
+{
+ ///
+ /// Metrics API
+ ///
+ public MetricsApi Metrics;
+
+ ///
+ /// Command registration API
+ ///
+ public RegisterApi Register;
+
+ ///
+ /// Store API
+ ///
+ public StoreApi Store;
+
+ internal GarnetProvider Provider;
+
+ private readonly IHost host;
+
+ public GarnetApplication(IHost host)
+ {
+ this.host = host;
+
+ Metrics = host.Services.GetRequiredService();
+ Register = host.Services.GetService();
+ Store = host.Services.GetService();
+
+ Provider = host.Services.GetRequiredService();
+ }
+
+ public IServiceProvider Services => host.Services;
+
+ public Task StartAsync(CancellationToken cancellationToken = default)
+ => host.StartAsync(cancellationToken);
+
+ public Task StopAsync(CancellationToken cancellationToken = default)
+ => host.StopAsync(cancellationToken);
+
+ public void Dispose()
+ {
+ host.Dispose();
+ }
+
+ public void Run()
+ {
+ HostingAbstractionsHostExtensions.Run(this);
+ }
+
+ public Task RunAsync(CancellationToken cancellationToken = default)
+ {
+ HostingAbstractionsHostExtensions.RunAsync(this, cancellationToken);
+ return Task.CompletedTask;
+ }
+
+ public static GarnetApplicationBuilder CreateHostBuilder(string[] args)
+ {
+ MemoryLogger initLogger;
+
+ // Set up an initial memory logger to log messages from configuration parser into memory.
+ using (var memLogProvider = new MemoryLoggerProvider())
+ {
+ initLogger = (MemoryLogger)memLogProvider.CreateLogger("ArgParser");
+ }
+
+ if (!ServerSettingsManager.TryParseCommandLineArguments(args, out var serverSettings, out _,
+ out var exitGracefully, initLogger))
+ {
+ if (exitGracefully)
+ System.Environment.Exit(0);
+
+ // Flush logs from memory logger
+ //initLogger.FlushLogger(logger);
+
+ throw new GarnetException(
+ "Encountered an error when initializing Garnet server. Please see log messages above for more details.");
+ }
+
+ var garnetServerOptions = serverSettings.GetServerOptions(null);
+
+ return new (new GarnetApplicationOptions {Args = args}, garnetServerOptions);
+ }
+
+ public static GarnetApplicationBuilder CreateHostBuilder(string[] args, GarnetServerOptions options)
+ {
+ return new (new GarnetApplicationOptions {Args = args}, options);
+ }
+}
\ No newline at end of file
diff --git a/libs/host/GarnetApplicationBuilder.cs b/libs/host/GarnetApplicationBuilder.cs
new file mode 100644
index 0000000000..45c0f3e941
--- /dev/null
+++ b/libs/host/GarnetApplicationBuilder.cs
@@ -0,0 +1,203 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Garnet.cluster;
+using Garnet.common;
+using Garnet.server;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.Metrics;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Tsavorite.core;
+
+namespace Garnet.host;
+
+public class GarnetApplicationBuilder : IHostApplicationBuilder
+{
+ private readonly HostApplicationBuilder hostApplicationBuilder;
+
+ ///
+ /// Resp protocol version
+ ///
+ private readonly string redisProtocolVersion = "7.2.5";
+
+ internal GarnetApplicationBuilder(GarnetApplicationOptions options, GarnetServerOptions garnetServerOptions)
+ {
+ var configuration = new ConfigurationManager();
+
+ configuration.AddEnvironmentVariables(prefix: "GARNET_");
+
+ hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings
+ {
+ Args = options.Args,
+ ApplicationName = options.ApplicationName,
+ EnvironmentName = options.EnvironmentName,
+ Configuration = configuration
+ });
+
+ hostApplicationBuilder.Logging.ClearProviders();
+ hostApplicationBuilder.Logging.AddSimpleConsole(simpleConsoleFormatterOptions =>
+ {
+ simpleConsoleFormatterOptions.SingleLine = true;
+ simpleConsoleFormatterOptions.TimestampFormat = "hh::mm::ss ";
+ });
+
+ hostApplicationBuilder.Services.AddOptions();
+
+ hostApplicationBuilder.Services.AddSingleton();
+
+ var garnetServerOptionsWrapped = Microsoft.Extensions.Options.Options.Create(garnetServerOptions);
+ hostApplicationBuilder.Services.AddSingleton(garnetServerOptionsWrapped);
+
+ hostApplicationBuilder.Services.AddSingleton();
+ hostApplicationBuilder.Services.AddHostedService();
+
+ hostApplicationBuilder.Services.AddSingleton();
+ hostApplicationBuilder.Services.AddSingleton();
+
+ hostApplicationBuilder.Services.AddSingleton(sp =>
+ {
+ var server = sp.GetRequiredService();
+ var opts = sp.GetRequiredService>();
+ var customCommandManager = sp.GetRequiredService();
+ var logger = sp.GetRequiredService>();
+ var clusterFactory = opts.Value.EnableCluster ? sp.GetRequiredService() : null;
+ var storeFactory = sp.GetRequiredService();
+
+ var version = GetVersion();
+
+ var store = storeFactory.CreateMainStore(out var checkpointDir);
+ var objectStore = storeFactory.CreateObjectStore(checkpointDir, out var objectStoreSizeTracker);
+
+ TsavoriteLog appendOnlyFile = null;
+
+ if (opts.Value.EnableAOF)
+ {
+ if (opts.Value.MainMemoryReplication && opts.Value.CommitFrequencyMs != -1)
+ throw new Exception(
+ "Need to set CommitFrequencyMs to -1 (manual commits) with MainMemoryReplication");
+
+ opts.Value.GetAofSettings(out var aofSettings);
+
+ var tsavoriteLogLogger = sp.GetRequiredService>();
+
+ appendOnlyFile = new TsavoriteLog(aofSettings, tsavoriteLogLogger);
+
+ if (opts.Value.CommitFrequencyMs < 0 && opts.Value.WaitForCommit)
+ throw new Exception("Cannot use CommitWait with manual commits");
+ }
+ else
+ {
+ if (opts.Value.CommitFrequencyMs != 0 || opts.Value.WaitForCommit)
+ throw new Exception("Cannot use CommitFrequencyMs or CommitWait without EnableAOF");
+ }
+
+ var configMemoryLimit = (store.IndexSize * 64) + store.Log.MaxMemorySizeBytes +
+ (store.ReadCache?.MaxMemorySizeBytes ?? 0) +
+ (appendOnlyFile?.MaxMemorySizeBytes ?? 0);
+ if (objectStore != null)
+ configMemoryLimit += objectStore.IndexSize * 64 + objectStore.Log.MaxMemorySizeBytes +
+ (objectStore.ReadCache?.MaxMemorySizeBytes ?? 0) +
+ (objectStoreSizeTracker?.TargetSize ?? 0) +
+ (objectStoreSizeTracker?.ReadCacheTargetSize ?? 0);
+ logger.LogInformation("Total configured memory limit: {configMemoryLimit}", configMemoryLimit);
+
+ LoadModules(customCommandManager, opts.Value, logger);
+
+ return new StoreWrapper(version, redisProtocolVersion, server, store, objectStore, objectStoreSizeTracker,
+ customCommandManager, appendOnlyFile, opts.Value, logger, clusterFactory: clusterFactory);
+ });
+
+ if (!garnetServerOptions.DisablePubSub)
+ {
+ hostApplicationBuilder.Services
+ .AddSingleton(sp =>
+ {
+ var opts = sp.GetRequiredService>();
+ return SubscribeBroker>.Create(opts);
+ });
+ }
+
+ hostApplicationBuilder.Services.AddSingleton(sp =>
+ {
+ var storeWrapper = sp.GetRequiredService();
+ var subscriberBroker = sp.GetService>>();
+
+ return new GarnetProvider(storeWrapper, subscriberBroker);
+ });
+
+ hostApplicationBuilder.Services.AddSingleton();
+ hostApplicationBuilder.Services.AddSingleton();
+ hostApplicationBuilder.Services.AddSingleton();
+ }
+
+ public GarnetApplication Build()
+ {
+ var host = hostApplicationBuilder.Build();
+ return new GarnetApplication(host);
+ }
+
+ public void ConfigureContainer(IServiceProviderFactory factory,
+ Action configure = null)
+ where TContainerBuilder : notnull
+ {
+ hostApplicationBuilder.ConfigureContainer(factory, configure);
+ }
+
+ public IDictionary