public static Result <DistributedCacheServiceRunningTracker> Create( OperationContext context, IClock clock, IAbsFileSystem fileSystem, DistributedCacheServiceConfiguration configuration, TimeSpan startUpTime) { return(context.PerformOperation(Tracer, () => { var logIntervalSeconds = configuration.DistributedContentSettings.ServiceRunningLogInSeconds; if (logIntervalSeconds == null) { context.TraceInfo(ServiceStartedTrace(startUpTime)); return new Result <DistributedCacheServiceRunningTracker>($"{nameof(DistributedCacheServiceRunningTracker)} is disabled"); } var logFilePath = configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName) / FileName; var serviceTracker = new DistributedCacheServiceRunningTracker(context, clock, fileSystem, logIntervalSeconds.Value, logFilePath); serviceTracker.Start(context, startUpTime).ThrowIfFailure(); return new Result <DistributedCacheServiceRunningTracker>(serviceTracker); })); }
/// <summary> /// Creates and runs a distributed cache service /// </summary> /// <exception cref="CacheException">thrown when cache startup fails</exception> public static Task RunWithConfigurationAsync( ILogger logger, IDistributedCacheServiceHost host, HostInfo hostInfo, ITelemetryFieldsProvider telemetryFieldsProvider, DistributedCacheServiceConfiguration config, CancellationToken token, string keyspace = null) { logger.Info($"CAS log severity set to {config.MinimumLogSeverity}"); var arguments = new DistributedCacheServiceArguments( logger: logger, copier: null, copyRequester: null, host: host, hostInfo: hostInfo, cancellation: token, dataRootPath: config.DataRootPath, configuration: config, keyspace: keyspace) { TelemetryFieldsProvider = telemetryFieldsProvider, BuildCopyInfrastructure = logger => BuildCopyInfrastructure(logger, config), }; return(RunAsync(arguments)); }
internal DistributedCacheServiceArguments CreateDistributedCacheServiceArguments( IRemoteFileCopier copier, IContentCommunicationManager copyRequester, DistributedContentSettings dcs, HostInfo host, string cacheName, string cacheRootPath, uint grpcPort, int maxSizeQuotaMB, string dataRootPath, CancellationToken ct, int?bufferSizeForGrpcCopies, int?gzipBarrierSizeForGrpcCopies, LoggingSettings loggingSettings, ITelemetryFieldsProvider telemetryFieldsProvider) { var distributedCacheServiceHost = new EnvironmentVariableHost(); var localCasSettings = LocalCasSettings.Default( maxSizeQuotaMB: maxSizeQuotaMB, cacheRootPath: cacheRootPath, cacheName: cacheName, grpcPort: grpcPort, grpcPortFileName: _scenario); localCasSettings.PreferredCacheDrive = new AbsolutePath(cacheRootPath).GetPathRoot(); localCasSettings.ServiceSettings = new LocalCasServiceSettings(60, scenarioName: _scenario, grpcPort: grpcPort, grpcPortFileName: _scenario, bufferSizeForGrpcCopies: bufferSizeForGrpcCopies, gzipBarrierSizeForGrpcCopies: gzipBarrierSizeForGrpcCopies); var config = new DistributedCacheServiceConfiguration(localCasSettings, dcs, loggingSettings); return(new DistributedCacheServiceArguments(_logger, copier, copyRequester, distributedCacheServiceHost, host, ct, dataRootPath, config, null) { TelemetryFieldsProvider = telemetryFieldsProvider, }); }
private bool TryCreateLauncherIfSpecified(DistributedCacheServiceConfiguration cacheConfig, out DeploymentLauncher launcher) { var launcherSettings = cacheConfig.DistributedContentSettings.LauncherSettings; if (launcherSettings != null) { var deploymentParams = launcherSettings.DeploymentParameters; deploymentParams.Stamp ??= _arguments.TelemetryFieldsProvider?.Stamp; deploymentParams.Machine ??= Environment.MachineName; deploymentParams.MachineFunction ??= _arguments.TelemetryFieldsProvider?.APMachineFunction; deploymentParams.Ring ??= _arguments.TelemetryFieldsProvider?.Ring; deploymentParams.AuthorizationSecret ??= _arguments.Host.GetPlainSecretAsync(deploymentParams.AuthorizationSecretName, _arguments.Cancellation).GetAwaiter().GetResult(); launcher = new DeploymentLauncher( launcherSettings, _fileSystem); return(true); } else { launcher = null; return(false); } }
internal DistributedCacheServiceArguments CreateDistributedCacheServiceArguments( IAbsolutePathFileCopier copier, IAbsolutePathTransformer pathTransformer, HostInfo host, string cacheName, string cacheRootPath, uint grpcPort, int maxSizeQuotaMB, string dataRootPath, CancellationToken ct, int?bufferSizeForGrpcCopies = null) { var distributedCacheServiceHost = new TestHost(); var localCasSettings = LocalCasSettings.Default( maxSizeQuotaMB: maxSizeQuotaMB, cacheRootPath: cacheRootPath, cacheName: cacheName, grpcPort: grpcPort, grpcPortFileName: _scenario); localCasSettings.PreferredCacheDrive = Path.GetPathRoot(cacheRootPath); localCasSettings.ServiceSettings = new LocalCasServiceSettings(60, scenarioName: _scenario, grpcPort: grpcPort, grpcPortFileName: _scenario, bufferSizeForGrpcCopies: bufferSizeForGrpcCopies); var redisConnectionString = Environment.GetEnvironmentVariable(EnvironmentConnectionStringProvider.RedisConnectionStringEnvironmentVariable); var distributedContentSettings = DistributedContentSettings.CreateEnabled(new Dictionary <string, string>() { { host.StampId, redisConnectionString } }); var config = new DistributedCacheServiceConfiguration(localCasSettings, distributedContentSettings); return(new DistributedCacheServiceArguments(_logger, copier, pathTransformer, distributedCacheServiceHost, host, ct, dataRootPath, config, null)); }
internal DistributedCacheServiceArguments CreateDistributedCacheServiceArguments( IAbsolutePathFileCopier copier, IAbsolutePathTransformer pathTransformer, DistributedContentSettings dcs, HostInfo host, string cacheName, string cacheRootPath, uint grpcPort, int maxSizeQuotaMB, string dataRootPath, CancellationToken ct, int?bufferSizeForGrpcCopies = null, int?gzipBarrierSizeForGrpcCopies = null) { var distributedCacheServiceHost = new EnvironmentVariableHost(); var localCasSettings = LocalCasSettings.Default( maxSizeQuotaMB: maxSizeQuotaMB, cacheRootPath: cacheRootPath, cacheName: cacheName, grpcPort: grpcPort, grpcPortFileName: _scenario); localCasSettings.PreferredCacheDrive = Path.GetPathRoot(cacheRootPath); localCasSettings.ServiceSettings = new LocalCasServiceSettings(60, scenarioName: _scenario, grpcPort: grpcPort, grpcPortFileName: _scenario, bufferSizeForGrpcCopies: bufferSizeForGrpcCopies, gzipBarrierSizeForGrpcCopies: gzipBarrierSizeForGrpcCopies); var config = new DistributedCacheServiceConfiguration(localCasSettings, dcs); return(new DistributedCacheServiceArguments(_logger, copier, pathTransformer, distributedCacheServiceHost, host, ct, dataRootPath, config, null)); }
/// <summary> /// Constructs the service host and takes command line arguments because /// ASP.Net core application host is used to parse command line. /// </summary> public ServiceHost(string[] commandLineArgs, DistributedCacheServiceConfiguration configuration, HostParameters hostParameters) { HostParameters = hostParameters; ServiceConfiguration = configuration; WebHostBuilder = Host.CreateDefaultBuilder(commandLineArgs) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup <CacheServiceStartup>(); }); WebHostBuilder.ConfigureHostConfiguration(cb => cb.Add(ConfigurationSource)); }
public static Result <ServiceOfflineDurationTracker> Create( OperationContext context, IClock clock, IAbsFileSystem fileSystem, DistributedCacheServiceConfiguration configuration) { var logIntervalSeconds = configuration.DistributedContentSettings.ServiceRunningLogInSeconds; var logFilePath = configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName) / FileName; return(Create(context, clock, fileSystem, logIntervalSeconds, logFilePath)); }
private async Task <DeploymentLauncher> CreateLauncherAsync(DistributedCacheServiceConfiguration cacheConfig) { var launcherSettings = cacheConfig.DistributedContentSettings.LauncherSettings; Contract.Assert(launcherSettings is not null); var deploymentParams = launcherSettings.DeploymentParameters; deploymentParams.ApplyFromTelemetryProviderIfNeeded(_arguments.TelemetryFieldsProvider); deploymentParams.AuthorizationSecret ??= await _arguments.Host.GetPlainSecretAsync(deploymentParams.AuthorizationSecretName, _arguments.Cancellation); return(new DeploymentLauncher(launcherSettings, _fileSystem)); }
private static string ComputeKeySpace(HostInfo hostInfo, DistributedCacheServiceConfiguration configuration, string keyspace) { string keySpaceString = keyspace; if (!string.IsNullOrWhiteSpace(configuration.DistributedContentSettings.KeySpacePrefix)) { keySpaceString = configuration.DistributedContentSettings.KeySpacePrefix + keySpaceString; } if (configuration.UseStampBasedIsolation) { keySpaceString = hostInfo.StampId + keySpaceString; } return(keySpaceString); }
/// <summary> /// Constructs the service host and takes command line arguments because /// ASP.Net core application host is used to parse command line. /// </summary> public ServiceHost(string[] commandLineArgs, DistributedCacheServiceConfiguration configuration, HostParameters hostParameters, Context context, CrossProcessSecretsCommunicationKind secretsCommunicationKind = CrossProcessSecretsCommunicationKind.Environment) : base(context, secretsCommunicationKind) { HostParameters = hostParameters; ServiceConfiguration = configuration; WebHostBuilder = Host.CreateDefaultBuilder(commandLineArgs) .ConfigureWebHostDefaults(webBuilder => { if (UseGrpc) { webBuilder.ConfigureLogging(l => { l.ClearProviders(); // This is left for future reference if ASP.NET logging needs to be enabled //l.AddProvider(new LoggingAdapter("ASPNET", context)); }); webBuilder.ConfigureKestrel(o => { int?port = null; var proxyConfiguration = WebHost.Services.GetService <ProxyServiceConfiguration>(); if (UseGrpc) { port = (int)ServiceConfiguration.LocalCasSettings.ServiceSettings.GrpcPort; } else if (proxyConfiguration == null) { port = proxyConfiguration.Port; } o.ConfigureEndpointDefaults(listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; }); if (port.HasValue) { o.ListenAnyIP(port.Value); } }); } webBuilder.UseStartup <CacheServiceStartup>(); }); }
private static async Task ReportStartingServiceAsync( OperationContext context, IDistributedCacheServiceHost host, DistributedCacheServiceConfiguration configuration) { var logIntervalSeconds = configuration.DistributedContentSettings.ServiceRunningLogInSeconds; var logInterval = logIntervalSeconds != null ? (TimeSpan?)TimeSpan.FromSeconds(logIntervalSeconds.Value) : null; var logFilePath = configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName); LifetimeTracker.ServiceStarting(context, logInterval, logFilePath); if (host is IDistributedCacheServiceHostInternal hostInternal) { await hostInternal.OnStartingServiceAsync(context); } await host.OnStartingServiceAsync(); }
/// <inheritdoc /> public DistributedCacheServiceArguments( ILogger logger, IAbsolutePathFileCopier copier, IAbsolutePathTransformer pathTransformer, IDistributedCacheServiceHost host, HostInfo hostInfo, CancellationToken cancellation, string dataRootPath, DistributedCacheServiceConfiguration configuration, string keyspace) { Logger = logger; Copier = copier; PathTransformer = pathTransformer; Host = host; HostInfo = hostInfo; Cancellation = cancellation; DataRootPath = dataRootPath; Configuration = configuration; Keyspace = ComputeKeySpace(hostInfo, configuration, keyspace); }
public static Result <ServiceOfflineDurationTracker> Create( OperationContext context, IClock clock, IAbsFileSystem fileSystem, DistributedCacheServiceConfiguration configuration) { return(context.PerformOperation(Tracer, () => { var logIntervalSeconds = configuration.DistributedContentSettings.ServiceRunningLogInSeconds; if (logIntervalSeconds == null) { return new Result <ServiceOfflineDurationTracker>($"{nameof(ServiceOfflineDurationTracker)} is disabled"); } var logFilePath = configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName) / FileName; var serviceTracker = new ServiceOfflineDurationTracker(context, clock, fileSystem, logIntervalSeconds.Value, logFilePath); serviceTracker.LogCurrentTimeStampToFile(context); return new Result <ServiceOfflineDurationTracker>(serviceTracker); })); }
private static AbsolutePath GetPathForLifetimeTracking(DistributedCacheServiceConfiguration configuration) => configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName);
private bool IsOutOfProcCacheEnabled(DistributedCacheServiceConfiguration cacheConfig) => cacheConfig.DistributedContentSettings.RunCacheOutOfProc == true;
static AbsolutePath getRootPath(DistributedCacheServiceConfiguration configuration) => configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName);
private static (IRemoteFileCopier copier, IContentCommunicationManager copyRequester) BuildCopyInfrastructure(ILogger logger, DistributedCacheServiceConfiguration config) { var grpcFileCopierConfiguration = GrpcFileCopierConfiguration.FromDistributedContentSettings( config.DistributedContentSettings, (int)config.LocalCasSettings.ServiceSettings.GrpcPort); var grpcFileCopier = new GrpcFileCopier(new Context(logger), grpcFileCopierConfiguration); return(copier : grpcFileCopier, copyRequester : grpcFileCopier); }
private bool IsLauncherEnabled(DistributedCacheServiceConfiguration cacheConfig) => cacheConfig.DistributedContentSettings.LauncherSettings != null;
internal void Service ( [Description("Cache names")] string[] names, [Description("Cache root paths")] string[] paths, [DefaultValue(DefaultMaxConnections), Description(MaxConnectionsDescription)] uint maxConnections, [DefaultValue(DefaultGracefulShutdownSeconds), Description(GracefulShutdownSecondsDescription)] uint gracefulShutdownSeconds, [DefaultValue(ServiceConfiguration.GrpcDisabledPort), Description(GrpcPortDescription)] uint grpcPort, [Description("Name of the memory mapped file used to share GRPC port. 'CASaaS GRPC port' if not specified.")] string grpcPortFileName, [DefaultValue(null), Description("Writable directory for service operations (use CWD if null)")] string dataRootPath, [DefaultValue(null), Description("Duration of inactivity after which a session will be timed out.")] double?unusedSessionTimeoutSeconds, [DefaultValue(null), Description("Duration of inactivity after which a session with a heartbeat will be timed out.")] double?unusedSessionHeartbeatTimeoutSeconds, [DefaultValue(false), Description("Stop running service")] bool stop, [DefaultValue(Constants.OneGBInMB), Description("Max size quota in MB")] int maxSizeQuotaMB, [DefaultValue(ServiceConfiguration.GrpcDisabledPort), Description(RemoteGrpcPortDescription)] uint backingGrpcPort, [DefaultValue(null), Description("Name of scenario for backing CAS service")] string backingScenario, [DefaultValue("None"), Description("Ring Id. Used only for telemetry.")] string ringId, [DefaultValue("None"), Description("Stamp Id. Used only for telemetry.")] string stampId, [DefaultValue(null), Description("nLog configuration path. If empty, it is disabled")] string nLogConfigurationPath, [DefaultValue(null), Description("Whether to use Azure Blob logging or not")] string nLogToBlobStorageSecretName, [DefaultValue(null), Description("If using Azure Blob logging, where to temporarily store logs")] string nLogToBlobStorageWorkspacePath, [DefaultValue(false), Description("Enable metadata")] bool enableMetadata ) { Initialize(); if (stop) { IpcUtilities.SetShutdown(_scenario); return; } if (names == null || paths == null) { throw new CacheException("At least one cache name/path is required."); } if (names.Length != paths.Length) { throw new CacheException("Mismatching lengths of names/paths arguments."); } var serverDataRootPath = !string.IsNullOrWhiteSpace(dataRootPath) ? new AbsolutePath(dataRootPath) : new AbsolutePath(Environment.CurrentDirectory); var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken); if (_scenario != null) { _logger.Debug($"scenario=[{_scenario}]"); } using var exitEvent = GetExitEvent(); var localCasSettings = LocalCasSettings.Default(maxSizeQuotaMB, paths[0], names[0], grpcPort, grpcPortFileName); for (int i = 1; i < names.Length; i++) { localCasSettings.AddNamedCache(names[i], paths[i]); } localCasSettings.ServiceSettings.ScenarioName = _scenario; if (unusedSessionTimeoutSeconds != null) { localCasSettings.ServiceSettings.UnusedSessionTimeoutMinutes = TimeSpan.FromSeconds(unusedSessionTimeoutSeconds.Value).TotalMinutes; } if (unusedSessionHeartbeatTimeoutSeconds != null) { localCasSettings.ServiceSettings.UnusedSessionHeartbeatTimeoutMinutes = TimeSpan.FromSeconds(unusedSessionHeartbeatTimeoutSeconds.Value).TotalMinutes; } var distributedContentSettings = DistributedContentSettings.CreateDisabled(); if (backingGrpcPort != ServiceConfiguration.GrpcDisabledPort) { distributedContentSettings.BackingGrpcPort = (int)backingGrpcPort; distributedContentSettings.BackingScenario = backingScenario; } if (enableMetadata) { distributedContentSettings.EnableMetadataStore = true; } LoggingSettings loggingSettings = null; if (!string.IsNullOrEmpty(nLogConfigurationPath)) { loggingSettings = new LoggingSettings() { NLogConfigurationPath = nLogConfigurationPath, Configuration = new AzureBlobStorageLogPublicConfiguration() { SecretName = nLogToBlobStorageSecretName, WorkspaceFolderPath = nLogToBlobStorageWorkspacePath, } }; } var distributedCacheServiceConfiguration = new DistributedCacheServiceConfiguration(localCasSettings, distributedContentSettings, loggingSettings); // Ensure the computed keyspace is computed based on the hostInfo's StampId distributedCacheServiceConfiguration.UseStampBasedIsolation = false; var distributedCacheServiceArguments = new DistributedCacheServiceArguments( logger: _logger, copier: new DistributedCopier(), copyRequester: null, host: new EnvironmentVariableHost(), hostInfo: new HostInfo(null, null, new List <string>()), cancellation: cancellationTokenSource.Token, dataRootPath: serverDataRootPath.Path, configuration: distributedCacheServiceConfiguration, keyspace: null) { TelemetryFieldsProvider = new TelemetryFieldsProvider(ringId, stampId, serviceName: "Service"), }; var runTask = Task.Run(() => DistributedCacheServiceFacade.RunAsync(distributedCacheServiceArguments)); // Because the facade completes immediately and named wait handles don't exist in CORECLR, // completion here is gated on Control+C. In the future, this can be redone with another option, // such as a MemoryMappedFile or GRPC heartbeat. This is just intended to be functional. int completedIndex = WaitHandle.WaitAny(new WaitHandle[] { cancellationTokenSource.Token.WaitHandle, exitEvent }); var source = completedIndex == 0 ? "control-C" : "exit event"; _logger.Always($"Shutdown by {source}."); if (completedIndex == 1) { cancellationTokenSource.Cancel(); } runTask.GetAwaiter().GetResult(); }
internal void Service ( [Description("Cache names")] string[] names, [Description("Cache root paths")] string[] paths, [DefaultValue(DefaultMaxConnections), Description(MaxConnectionsDescription)] uint maxConnections, [DefaultValue(DefaultGracefulShutdownSeconds), Description(GracefulShutdownSecondsDescription)] uint gracefulShutdownSeconds, [DefaultValue(ServiceConfiguration.GrpcDisabledPort), Description(GrpcPortDescription)] int grpcPort, [Description("Name of the memory mapped file used to share GRPC port. 'CASaaS GRPC port' if not specified.")] string grpcPortFileName, [DefaultValue(null), Description("Writable directory for service operations (use CWD if null)")] string dataRootPath, [DefaultValue(null), Description("Duration of inactivity after which a session will be timed out.")] double?unusedSessionTimeoutSeconds, [DefaultValue(null), Description("Duration of inactivity after which a session with a heartbeat will be timed out.")] double?unusedSessionHeartbeatTimeoutSeconds, [DefaultValue(false), Description("Stop running service")] bool stop, [DefaultValue(Constants.OneMB), Description("Max size quota in MB")] int maxSizeQuotaMB ) { Initialize(); if (stop) { IpcUtilities.SetShutdown(_scenario); return; } if (names == null || paths == null) { throw new CacheException("At least one cache name/path is required."); } if (names.Length != paths.Length) { throw new CacheException("Mismatching lengths of names/paths arguments."); } var caches = new Dictionary <string, string>(); for (var i = 0; i < names.Length; i++) { caches.Add(names[i], paths[i]); } var serverDataRootPath = !string.IsNullOrWhiteSpace(dataRootPath) ? new AbsolutePath(dataRootPath) : new AbsolutePath(Environment.CurrentDirectory); var cancellationTokenSource = new CancellationTokenSource(); #if !FEATURE_CORECLR var configuration = new ServiceConfiguration(caches, serverDataRootPath, maxConnections, gracefulShutdownSeconds, grpcPort, grpcPortFileName); if (!configuration.IsValid) { throw new CacheException($"Invalid service configuration, error=[{configuration.Error}]"); } var localContentServerConfiguration = new LocalServerConfiguration(configuration); if (unusedSessionTimeoutSeconds != null) { localContentServerConfiguration.UnusedSessionTimeout = TimeSpan.FromSeconds(unusedSessionTimeoutSeconds.Value); } if (unusedSessionHeartbeatTimeoutSeconds != null) { localContentServerConfiguration.UnusedSessionHeartbeatTimeout = TimeSpan.FromSeconds(unusedSessionHeartbeatTimeoutSeconds.Value); } if (_scenario != null) { _logger.Debug($"scenario=[{_scenario}]"); } var exitSignal = new ManualResetEvent(false); Console.CancelKeyPress += (sender, args) => { exitSignal.Set(); args.Cancel = true; }; using (var exitEvent = IpcUtilities.GetShutdownWaitHandle(_scenario)) { var server = new LocalContentServer( _fileSystem, _logger, _scenario, path => new FileSystemContentStore( _fileSystem, SystemClock.Instance, path, new ConfigurationModel(inProcessConfiguration: ContentStoreConfiguration.CreateWithMaxSizeQuotaMB((uint)maxSizeQuotaMB))), localContentServerConfiguration); using (server) { var context = new Context(_logger); try { var result = server.StartupAsync(context).Result; if (!result.Succeeded) { throw new CacheException(result.ErrorMessage); } int completedIndex = WaitHandle.WaitAny(new WaitHandle[] { exitSignal, exitEvent }); var source = completedIndex == 0 ? "control-C" : "exit event"; _tracer.Always(context, $"Shutdown by {source}."); } finally { var result = server.ShutdownAsync(context).Result; if (!result.Succeeded) { _tracer.Warning(context, $"Failed to shutdown store: {result.ErrorMessage}"); } } } } #else Console.CancelKeyPress += (sender, args) => { cancellationTokenSource.Cancel(); args.Cancel = true; }; var localCasSettings = LocalCasSettings.Default(maxSizeQuotaMB, serverDataRootPath.Path, names[0], (uint)grpcPort); var distributedContentSettings = DistributedContentSettings.CreateDisabled(); var distributedCacheServiceConfiguration = new DistributedCacheServiceConfiguration(localCasSettings, distributedContentSettings); // Ensure the computed keyspace is computed based on the hostInfo's StampId distributedCacheServiceConfiguration.UseStampBasedIsolation = false; var distributedCacheServiceArguments = new DistributedCacheServiceArguments( logger: _logger, copier: null, pathTransformer: null, host: new EnvironmentVariableHost(), hostInfo: new HostInfo(null, null, new List <string>()), cancellation: cancellationTokenSource.Token, dataRootPath: serverDataRootPath.Path, configuration: distributedCacheServiceConfiguration, keyspace: null); DistributedCacheServiceFacade.RunAsync(distributedCacheServiceArguments).GetAwaiter().GetResult(); // Because the facade completes immediately and named wait handles don't exist in CORECLR, // completion here is gated on Control+C. In the future, this can be redone with another option, // such as a MemoryMappedFile or GRPC heartbeat. This is just intended to be functional. cancellationTokenSource.Token.WaitHandle.WaitOne(); #endif }