示例#1
0
        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);
            }));
        }
示例#2
0
        /// <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));
        }
示例#3
0
        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,
            });
        }
示例#4
0
        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);
            }
        }
示例#5
0
        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));
        }
示例#6
0
        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));
        }
示例#7
0
            /// <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));
        }
示例#9
0
        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);
        }
示例#11
0
            /// <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>();
                });
            }
示例#12
0
        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);
            }));
        }
示例#15
0
 private static AbsolutePath GetPathForLifetimeTracking(DistributedCacheServiceConfiguration configuration) => configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName);
示例#16
0
 private bool IsOutOfProcCacheEnabled(DistributedCacheServiceConfiguration cacheConfig) =>
 cacheConfig.DistributedContentSettings.RunCacheOutOfProc == true;
示例#17
0
 static AbsolutePath getRootPath(DistributedCacheServiceConfiguration configuration) => configuration.LocalCasSettings.GetCacheRootPathWithScenario(LocalCasServiceSettings.DefaultCacheName);
示例#18
0
        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);
        }
示例#19
0
 private bool IsLauncherEnabled(DistributedCacheServiceConfiguration cacheConfig) =>
 cacheConfig.DistributedContentSettings.LauncherSettings != null;
示例#20
0
        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();
        }
示例#21
0
        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
        }