namespace Sitecore.Support.Pipelines.Loader { using System.Text; using System.Threading; using System.Web.Hosting; using System.Xml; using Sitecore.Abstractions; using Sitecore.Diagnostics; using Sitecore.Pipelines; using Sitecore.Reflection; using Sitecore.Xml; /// /// Configures thread limits for . /// Thread pool limits should be configured in machine config, however an access to machine config is not provided in cloud, thereby API-based approach must be taken. /// [UsedImplicitly] public class ConfigureThreadPool : IInitializable { #region fields /// /// The log to output running thread pool configuration in 'audit' level. /// [NotNull] private readonly BaseLog Log; /// /// Gets or sets the maximum worker threads. /// /// /// The maximum worker threads. /// protected int MaxWorkerThreadsPerCore; /// /// Gets or sets the maximum completion port threads. /// /// /// The maximum completion port threads. /// protected int MaxCompletionPortThreadsPerCore; /// /// Gets or sets the minimum worker threads. /// /// /// The minimum worker threads. /// protected int MinWorkerThreadsPerCore; /// /// Gets or sets the minimum completion port threads. /// /// /// The minimum completion port threads. /// protected int MinCompletionPortThreadsPerCore; #endregion /// /// Initializes a new instance of the class. /// /// The log to output running thread pool configuration in 'audit' level. public ConfigureThreadPool([NotNull]BaseLog log) { Assert.ArgumentNotNull(log, nameof(log)); this.Log = log; } #region Properties /// /// Gets a value indicating whether the needs calling code to assign properties even after calls. /// /// /// true if the assigns the properties; otherwise, false. /// public virtual bool AssignProperties => false; /// /// Gets the processor count. /// /// /// The processor count. /// public virtual int ProcessorCount => System.Environment.ProcessorCount; /// /// Gets or sets the maximum concurrent requests per CPU. /// /// /// The maximum concurrent requests allowed to be processed by one CPU at a time. /// public int MaxConcurrentRequestsPerCPU { get; protected set; } /// /// Gets the maximum worker threads. /// /// /// The maximum worker threads. /// public int MaxWorkerThreads => this.MaxWorkerThreadsPerCore * this.ProcessorCount; /// /// Gets the maximum completion port threads. /// /// /// The maximum completion port threads. /// public int MaxCompletionPortThreads => this.MaxCompletionPortThreadsPerCore * this.ProcessorCount; /// /// Gets the minimum worker threads. /// /// /// The minimum worker threads. /// public int MinWorkerThreads => this.MinWorkerThreadsPerCore * this.ProcessorCount; /// /// Gets the minimum completion port threads. /// /// /// The minimum completion port threads. /// public int MinCompletionPortThreads => this.MinCompletionPortThreadsPerCore * this.ProcessorCount; /// /// Gets a value indicating whether this code is executed in hosted environment (IIS). /// /// /// true if this instance is executed in hosting environment (f.e. IIS); otherwise, false (f.e. in tests). /// protected virtual bool IsHostedEnvironment => HostingEnvironment.IsHosted; #endregion /// /// Configures thread limits for based on configuration values. /// /// The arguments. public virtual void Process(PipelineArgs args) { if (!this.IsHostedEnvironment) { this.Log.Debug("Running outside IIS scope, skipping processor execution"); return; } this.LogThreadPoolStatistics(args, "Default thread pool size:"); if (this.MaxWorkerThreads != 0 && this.MaxCompletionPortThreads != 0) { this.SetMaxThreadPoolLimit(this.MaxWorkerThreads, this.MaxCompletionPortThreads, args); } if (this.MinWorkerThreads != 0 && this.MinCompletionPortThreads != 0) { this.SetMinThreadPoolLimit(this.MinWorkerThreads, this.MinCompletionPortThreads, args); } if (this.MaxConcurrentRequestsPerCPU != 0) { HostingEnvironment.MaxConcurrentRequestsPerCPU = this.MaxConcurrentRequestsPerCPU; } this.LogThreadPoolStatistics(args, "New thread pool size:"); } /// /// Logs the statistics. /// /// The arguments. /// The header for a message to begin with. public virtual void LogThreadPoolStatistics(PipelineArgs args, [NotNull] string header) { Assert.ArgumentNotNull(header, "header"); int minWorkerThreads; int minCompletionPortThreads; int maxWorkerThreads; int maxCompletionPortThreads; this.GetThreadStatistics(out minWorkerThreads, out minCompletionPortThreads, out maxWorkerThreads, out maxCompletionPortThreads); var message = new StringBuilder() .AppendLine(header) .AppendLine("Processor count:" + this.ProcessorCount) .AppendLine("minWorkerThreadsPerCore count:" + minWorkerThreads) .AppendLine("minCompletionPortThreadsPerCore count:" + minCompletionPortThreads) .AppendLine("maxWorkerThreadsPerCore count:" + maxWorkerThreads) .AppendLine("maxCompletionPortThreadsPerCore count:" + maxCompletionPortThreads) .AppendLine("MaxConcurrentRequestsPerCPU count:" + this.MaxConcurrentRequestsPerCPU) .ToString(); this.Log.Audit(message, this); } /// /// Gets the the statistics. /// /// The minimum worker threads. /// The minimum completion port threads. /// The maximum worker threads. /// The maximum completion port threads. public virtual void GetThreadStatistics(out int minWorkerThreads, out int minCompletionPortThreads, out int maxWorkerThreads, out int maxCompletionPortThreads) { ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads); ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads); } /// /// Sets the minimum thread pool limit. /// /// The minimum worker threads. /// The minimum completion port threads. /// The arguments. public virtual void SetMinThreadPoolLimit(int minWorkerThreads, int minCompletionPortThreads, PipelineArgs args) { ThreadPool.SetMinThreads(minWorkerThreads, minCompletionPortThreads); } /// /// Sets the maximum thread pool limit. /// /// The maximum worker threads. /// The maximum completion port threads. /// The arguments. public virtual void SetMaxThreadPoolLimit(int maxWorkerThreads, int maxCompletionPortThreads, PipelineArgs args) { ThreadPool.SetMaxThreads(maxWorkerThreads, maxCompletionPortThreads); } /// /// Initializes the object from the specified config node. /// /// The config node. public virtual void Initialize([CanBeNull]XmlNode configNode) { if (configNode == null) { return; } var attribute = XmlUtil.FindChildNode("MaxWorkerThreadsPerCore", configNode, false); if (attribute != null) { int.TryParse(attribute.InnerText, out this.MaxWorkerThreadsPerCore); } attribute = XmlUtil.FindChildNode("MaxCompletionPortThreadsPerCore", configNode, false); if (attribute != null) { int.TryParse(attribute.InnerText, out this.MaxCompletionPortThreadsPerCore); } attribute = XmlUtil.FindChildNode("MinWorkerThreadsPerCore", configNode, false); if (attribute != null) { int.TryParse(attribute.InnerText, out this.MinWorkerThreadsPerCore); } attribute = XmlUtil.FindChildNode("MinCompletionPortThreadsPerCore", configNode, false); if (attribute != null) { int.TryParse(attribute.InnerText, out this.MinCompletionPortThreadsPerCore); } attribute = XmlUtil.FindChildNode("MaxConcurrentRequestsPerCPU", configNode, false); if (attribute != null) { int maxConcurrentRequestsPerCPU; int.TryParse(attribute.InnerText, out maxConcurrentRequestsPerCPU); this.MaxConcurrentRequestsPerCPU = maxConcurrentRequestsPerCPU; } } } }