| File: System\ServiceModel\Channels\IdlingCommunicationPool.cs | Web Access |
| Project: src\src\System.ServiceModel.NetFramingBase\src\System.ServiceModel.NetFramingBase.csproj (System.ServiceModel.NetFramingBase) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Runtime; using System.Threading; using System.Threading.Tasks; namespace System.ServiceModel.Channels { internal abstract class IdlingCommunicationPool<TKey, TItem> : CommunicationPool<TKey, TItem> where TKey : class where TItem : class { private TimeSpan _leaseTimeout; protected IdlingCommunicationPool(int maxCount, TimeSpan idleTimeout, TimeSpan leaseTimeout) : base(maxCount) { IdleTimeout = idleTimeout; _leaseTimeout = leaseTimeout; } public TimeSpan IdleTimeout { get; } protected TimeSpan LeaseTimeout { get { return _leaseTimeout; } } protected override EndpointConnectionPool CreateEndpointConnectionPool(TKey key) { if (IdleTimeout != TimeSpan.MaxValue || _leaseTimeout != TimeSpan.MaxValue) { return new IdleTimeoutEndpointConnectionPool(this, key); } else { return base.CreateEndpointConnectionPool(key); } } protected class IdleTimeoutEndpointConnectionPool : EndpointConnectionPool { private IdleTimeoutIdleConnectionPool _connections; public IdleTimeoutEndpointConnectionPool(IdlingCommunicationPool<TKey, TItem> parent, TKey key) : base(parent, key) { _connections = new IdleTimeoutIdleConnectionPool(this, ThisLock); } protected override IdleConnectionPool GetIdleConnectionPool() { return _connections; } protected override void AbortItem(TItem item) { _connections.OnItemClosing(item); base.AbortItem(item); } protected override ValueTask CloseItemAsync(TItem item, TimeSpan timeout) { _connections.OnItemClosing(item); return base.CloseItemAsync(item, timeout); } public override void Prune(List<TItem> itemsToClose) { if (_connections != null) { _connections.Prune(itemsToClose, false); } } protected class IdleTimeoutIdleConnectionPool : PoolIdleConnectionPool { // for performance reasons we don't just blindly start a timer up to clean up // idle connections. However, if we're above a certain threshold of connections private const int timerThreshold = 1; private IdleTimeoutEndpointConnectionPool _parent; private TimeSpan _idleTimeout; private TimeSpan _leaseTimeout; private Timer _idleTimer; private static Action<object> s_onIdle; private object _thisLock; private Exception _pendingException; // Note that Take/Add/Return are already synchronized by ThisLock, so we don't need an extra // lock around our Dictionary access private Dictionary<TItem, IdlingConnectionSettings> _connectionMapping; public IdleTimeoutIdleConnectionPool(IdleTimeoutEndpointConnectionPool parent, object thisLock) : base(parent.Parent.MaxIdleConnectionPoolCount) { _parent = parent; IdlingCommunicationPool<TKey, TItem> idlingCommunicationPool = ((IdlingCommunicationPool<TKey, TItem>)parent.Parent); _idleTimeout = idlingCommunicationPool.IdleTimeout; _leaseTimeout = idlingCommunicationPool._leaseTimeout; _thisLock = thisLock; _connectionMapping = new Dictionary<TItem, IdlingConnectionSettings>(); } public override bool Add(TItem connection) { ThrowPendingException(); bool result = base.Add(connection); if (result) { _connectionMapping.Add(connection, new IdlingConnectionSettings()); StartTimerIfNecessary(); } return result; } public override bool Return(TItem connection) { ThrowPendingException(); if (!_connectionMapping.ContainsKey(connection)) { return false; } bool result = base.Return(connection); if (result) { _connectionMapping[connection].LastUsage = DateTime.UtcNow; StartTimerIfNecessary(); } return result; } public override TItem Take(out bool closeItem) { ThrowPendingException(); DateTime now = DateTime.UtcNow; TItem item = base.Take(out closeItem); if (!closeItem) { closeItem = IdleOutConnection(item, now); } return item; } public void OnItemClosing(TItem connection) { ThrowPendingException(); lock (_thisLock) { _connectionMapping.Remove(connection); } } private void CancelTimer() { if (_idleTimer != null) { _idleTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); } } private void StartTimerIfNecessary() { if (Count > timerThreshold) { if (_idleTimer == null) { if (s_onIdle == null) { s_onIdle = new Action<object>(OnIdle); } _idleTimer = new Timer(new TimerCallback(new Action<object>(s_onIdle)), this, _idleTimeout, Timeout.InfiniteTimeSpan); } else { _idleTimer.Change(_idleTimeout, Timeout.InfiniteTimeSpan); } } } private static void OnIdle(object state) { IdleTimeoutIdleConnectionPool pool = (IdleTimeoutIdleConnectionPool)state; pool.OnIdle(); } private void OnIdle() { List<TItem> itemsToClose = new List<TItem>(); lock (_thisLock) { try { Prune(itemsToClose, true); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } _pendingException = e; CancelTimer(); } } // allocate half the idle timeout for our graceful shutdowns TimeoutHelper timeoutHelper = new TimeoutHelper(TimeoutHelper.Divide(_idleTimeout, 2)); for (int i = 0; i < itemsToClose.Count; i++) { _parent.CloseIdleConnection(itemsToClose[i], timeoutHelper.RemainingTime()); } } public void Prune(List<TItem> itemsToClose, bool calledFromTimer) { if (!calledFromTimer) { ThrowPendingException(); } if (Count == 0) { return; } DateTime now = DateTime.UtcNow; bool setTimer = false; lock (_thisLock) { TItem[] connectionsCopy = new TItem[Count]; for (int i = 0; i < connectionsCopy.Length; i++) { bool closeItem; connectionsCopy[i] = base.Take(out closeItem); Fx.Assert(connectionsCopy[i] != null, "IdleConnections should only be modified under thisLock"); if (closeItem || IdleOutConnection(connectionsCopy[i], now)) { itemsToClose.Add(connectionsCopy[i]); connectionsCopy[i] = null; } } for (int i = 0; i < connectionsCopy.Length; i++) { if (connectionsCopy[i] != null) { bool successfulReturn = base.Return(connectionsCopy[i]); Fx.Assert(successfulReturn, "IdleConnections should only be modified under thisLock"); } } setTimer = (Count > 0); } if (calledFromTimer && setTimer) { _idleTimer.Change(_idleTimeout, Timeout.InfiniteTimeSpan); } } private bool IdleOutConnection(TItem connection, DateTime now) { if (connection == null) { return false; } bool result = false; IdlingConnectionSettings idlingSettings = _connectionMapping[connection]; if (now > (idlingSettings.LastUsage + _idleTimeout)) { TraceConnectionIdleTimeoutExpired(); result = true; } else if (now - idlingSettings.CreationTime >= _leaseTimeout) { TraceConnectionLeaseTimeoutExpired(); result = true; } return result; } private void ThrowPendingException() { if (_pendingException != null) { lock (_thisLock) { if (_pendingException != null) { Exception exceptionToThrow = _pendingException; _pendingException = null; throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(exceptionToThrow); } } } } private void TraceConnectionLeaseTimeoutExpired() { if (WcfEventSource.Instance.LeaseTimeoutIsEnabled()) { WcfEventSource.Instance.LeaseTimeout(SR.Format(SR.TraceCodeConnectionPoolLeaseTimeoutReached, _leaseTimeout), _parent.Key.ToString()); } } private void TraceConnectionIdleTimeoutExpired() { if (WcfEventSource.Instance.IdleTimeoutIsEnabled()) { WcfEventSource.Instance.IdleTimeout(SR.Format(SR.TraceCodeConnectionPoolIdleTimeoutReached, _idleTimeout), _parent.Key.ToString()); } } internal class IdlingConnectionSettings { private DateTime _lastUsage; public IdlingConnectionSettings() { CreationTime = DateTime.UtcNow; _lastUsage = CreationTime; } public DateTime CreationTime { get; } public DateTime LastUsage { get { return _lastUsage; } set { _lastUsage = value; } } } } } } }