33using Enyim . Caching . Memcached . Results ;
44using Enyim . Caching . Memcached . Results . Extensions ;
55using Enyim . Collections ;
6+
67using Microsoft . Extensions . Logging ;
8+
79using System ;
810using System . Collections . Concurrent ;
911using System . Collections . Generic ;
@@ -25,7 +27,7 @@ namespace Enyim.Caching.Memcached
2527 public class MemcachedNode : IMemcachedNode
2628 {
2729 private readonly ILogger _logger ;
28- private static readonly object SyncRoot = new Object ( ) ;
30+ private static readonly object SyncRoot = new ( ) ;
2931 private bool _isDisposed ;
3032 private readonly EndPoint _endPoint ;
3133 private readonly ISocketPoolConfiguration _config ;
@@ -46,8 +48,8 @@ public MemcachedNode(
4648 EndPointString = endpoint ? . ToString ( ) . Replace ( "Unspecified/" , string . Empty ) ;
4749 _config = socketPoolConfig ;
4850
49- if ( socketPoolConfig . ConnectionTimeout . TotalMilliseconds >= Int32 . MaxValue )
50- throw new InvalidOperationException ( "ConnectionTimeout must be < Int32 .MaxValue" ) ;
51+ if ( socketPoolConfig . ConnectionTimeout . TotalMilliseconds >= int . MaxValue )
52+ throw new InvalidOperationException ( "ConnectionTimeout must be < int .MaxValue" ) ;
5153
5254 if ( socketPoolConfig . InitPoolTimeout . TotalSeconds < 1 )
5355 {
@@ -281,6 +283,7 @@ private class InternalPoolImpl : IDisposable
281283 private readonly EndPoint _endPoint ;
282284 private readonly TimeSpan _queueTimeout ;
283285 private readonly TimeSpan _receiveTimeout ;
286+ private readonly TimeSpan _connectionIdleTimeout ;
284287 private SemaphoreSlim _semaphore ;
285288
286289 private readonly object initLock = new Object ( ) ;
@@ -298,12 +301,15 @@ internal InternalPoolImpl(
298301 throw new InvalidOperationException ( "queueTimeout must be >= TimeSpan.Zero" , null ) ;
299302 if ( config . ReceiveTimeout < TimeSpan . Zero )
300303 throw new InvalidOperationException ( "ReceiveTimeout must be >= TimeSpan.Zero" , null ) ;
304+ if ( config . ConnectionIdleTimeout < TimeSpan . Zero )
305+ throw new InvalidOperationException ( "ConnectionIdleTimeout must be >= TimeSpan.Zero" , null ) ;
301306
302307 _ownerNode = ownerNode ;
303308 _isAlive = true ;
304309 _endPoint = ownerNode . EndPoint ;
305310 _queueTimeout = config . QueueTimeout ;
306311 _receiveTimeout = config . ReceiveTimeout ;
312+ _connectionIdleTimeout = config . ConnectionIdleTimeout ;
307313
308314 _minItems = config . MinPoolSize ;
309315 _maxItems = config . MaxPoolSize ;
@@ -344,7 +350,7 @@ internal void InitPool()
344350 }
345351 catch ( Exception e )
346352 {
347- _logger . LogError ( "Could not init pool." , new EventId ( 0 ) , e ) ;
353+ _logger . LogError ( e , "Could not init pool." ) ;
348354
349355 MarkAsDead ( ) ;
350356 }
@@ -379,7 +385,7 @@ internal async Task InitPoolAsync()
379385 }
380386 catch ( Exception e )
381387 {
382- _logger . LogError ( "Could not init pool." , new EventId ( 0 ) , e ) ;
388+ _logger . LogError ( e , "Could not init pool." ) ;
383389
384390 MarkAsDead ( ) ;
385391 }
@@ -418,10 +424,9 @@ public DateTime MarkedAsDeadUtc
418424 public IPooledSocketResult Acquire ( )
419425 {
420426 var result = new PooledSocketResult ( ) ;
421- var message = string . Empty ;
422-
423427 if ( _isDebugEnabled ) _logger . LogDebug ( $ "Acquiring stream from pool on node '{ _endPoint } '") ;
424428
429+ string message ;
425430 if ( ! _isAlive || _isDisposed )
426431 {
427432 message = "Pool is dead or disposed, returning null. " + _endPoint ;
@@ -432,8 +437,6 @@ public IPooledSocketResult Acquire()
432437 return result ;
433438 }
434439
435- PooledSocket retval = null ;
436-
437440 if ( ! _semaphore . Wait ( _queueTimeout ) )
438441 {
439442 message = "Pool is full, timeouting. " + _endPoint ;
@@ -456,20 +459,23 @@ public IPooledSocketResult Acquire()
456459 return result ;
457460 }
458461
462+
463+ PooledSocket socket ;
459464 // do we have free items?
460- if ( _freeItems . TryPop ( out retval ) )
465+ if ( TryPopPooledSocket ( out socket ) )
461466 {
462467 #region [ get it from the pool ]
463468
464469 try
465470 {
466- retval . Reset ( ) ;
471+ socket . Reset ( ) ;
467472
468- message = "Socket was reset. " + retval . InstanceId ;
473+ message = "Socket was reset. " + socket . InstanceId ;
469474 if ( _isDebugEnabled ) _logger . LogDebug ( message ) ;
470475
471476 result . Pass ( message ) ;
472- result . Value = retval ;
477+ socket . UpdateLastUsed ( ) ;
478+ result . Value = socket ;
473479 return result ;
474480 }
475481 catch ( Exception e )
@@ -495,9 +501,9 @@ public IPooledSocketResult Acquire()
495501 {
496502 // okay, create the new item
497503 var startTime = DateTime . Now ;
498- retval = CreateSocket ( ) ;
504+ socket = CreateSocket ( ) ;
499505 _logger . LogInformation ( "MemcachedAcquire-CreateSocket: {0}ms" , ( DateTime . Now - startTime ) . TotalMilliseconds ) ;
500- result . Value = retval ;
506+ result . Value = socket ;
501507 result . Pass ( ) ;
502508 }
503509 catch ( Exception e )
@@ -542,7 +548,7 @@ public async Task<IPooledSocketResult> AcquireAsync()
542548 return result ;
543549 }
544550
545- PooledSocket retval = null ;
551+ PooledSocket socket = null ;
546552
547553 if ( ! await _semaphore . WaitAsync ( _queueTimeout ) )
548554 {
@@ -566,13 +572,13 @@ public async Task<IPooledSocketResult> AcquireAsync()
566572 }
567573
568574 // do we have free items?
569- if ( _freeItems . TryPop ( out retval ) )
575+ if ( TryPopPooledSocket ( out socket ) )
570576 {
571577 #region [ get it from the pool ]
572578
573579 try
574580 {
575- var resetTask = retval . ResetAsync ( ) ;
581+ var resetTask = socket . ResetAsync ( ) ;
576582
577583 if ( await Task . WhenAny ( resetTask , Task . Delay ( _receiveTimeout ) ) == resetTask )
578584 {
@@ -581,19 +587,20 @@ public async Task<IPooledSocketResult> AcquireAsync()
581587 else
582588 {
583589 _semaphore . Release ( ) ;
584- retval . IsAlive = false ;
590+ socket . IsAlive = false ;
585591
586- message = "Timeout to reset an acquired socket. InstanceId " + retval . InstanceId ;
592+ message = "Timeout to reset an acquired socket. InstanceId " + socket . InstanceId ;
587593 _logger . LogError ( message ) ;
588594 result . Fail ( message ) ;
589595 return result ;
590596 }
591597
592- message = "Socket was reset. InstanceId " + retval . InstanceId ;
598+ message = "Socket was reset. InstanceId " + socket . InstanceId ;
593599 if ( _isDebugEnabled ) _logger . LogDebug ( message ) ;
594600
595601 result . Pass ( message ) ;
596- result . Value = retval ;
602+ socket . UpdateLastUsed ( ) ;
603+ result . Value = socket ;
597604 return result ;
598605 }
599606 catch ( Exception e )
@@ -619,9 +626,9 @@ public async Task<IPooledSocketResult> AcquireAsync()
619626 {
620627 // okay, create the new item
621628 var startTime = DateTime . Now ;
622- retval = await CreateSocketAsync ( ) ;
629+ socket = await CreateSocketAsync ( ) ;
623630 _logger . LogInformation ( "MemcachedAcquire-CreateSocket: {0}ms" , ( DateTime . Now - startTime ) . TotalMilliseconds ) ;
624- result . Value = retval ;
631+ result . Value = socket ;
625632 result . Pass ( ) ;
626633 }
627634 catch ( Exception e )
@@ -737,6 +744,34 @@ private void ReleaseSocket(PooledSocket socket)
737744 }
738745 }
739746
747+ private bool TryPopPooledSocket ( out PooledSocket pooledSocket )
748+ {
749+ if ( _freeItems . TryPop ( out var socket ) )
750+ {
751+ if ( _connectionIdleTimeout > TimeSpan . Zero &&
752+ socket . LastUsed < DateTime . UtcNow . Subtract ( _connectionIdleTimeout ) )
753+ {
754+ try
755+ {
756+ _logger . LogInformation ( "Connection idle timeout {idleTimeout} reached." , _connectionIdleTimeout ) ;
757+ socket . Destroy ( ) ;
758+ }
759+ catch ( Exception ex )
760+ {
761+ _logger . LogError ( ex , $ "Failed to destroy { nameof ( PooledSocket ) } ") ;
762+ }
763+
764+ pooledSocket = null ;
765+ return false ;
766+ }
767+
768+ pooledSocket = socket ;
769+ return true ;
770+ }
771+
772+ pooledSocket = null ;
773+ return false ;
774+ }
740775
741776 ~ InternalPoolImpl ( )
742777 {
@@ -758,12 +793,10 @@ public void Dispose()
758793 _isAlive = false ;
759794 _isDisposed = true ;
760795
761- PooledSocket ps ;
762-
763- while ( _freeItems . TryPop ( out ps ) )
796+ while ( _freeItems . TryPop ( out var socket ) )
764797 {
765- try { ps . Destroy ( ) ; }
766- catch { }
798+ try { socket . Destroy ( ) ; }
799+ catch ( Exception ex ) { _logger . LogError ( ex , $ "failed to destroy { nameof ( PooledSocket ) } " ) ; }
767800 }
768801
769802 _ownerNode = null ;
0 commit comments