Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.ServiceModel / System / ServiceModel / Channels / ConnectionPool.cs
1 //----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------------------
4 namespace System.ServiceModel.Channels
5 {
6     using System.Collections.Generic;
7     using System.Diagnostics;
8     using System.Net;
9     using System.Runtime;
10     using System.Runtime.Diagnostics;
11     using System.ServiceModel;
12     using System.ServiceModel.Diagnostics;
13     using System.ServiceModel.Diagnostics.Application;
14
15     // code that pools items and closes/aborts them as necessary.
16     // shared by IConnection and IChannel users
17     abstract class CommunicationPool<TKey, TItem>
18         where TKey : class
19         where TItem : class
20     {
21         Dictionary<TKey, EndpointConnectionPool> endpointPools;
22         int maxCount;
23         int openCount;
24         // need to make sure we prune over a certain number of endpoint pools
25         int pruneAccrual;
26         const int pruneThreshold = 30;
27
28         protected CommunicationPool(int maxCount)
29         {
30             this.maxCount = maxCount;
31             this.endpointPools = new Dictionary<TKey, EndpointConnectionPool>();
32             this.openCount = 1;
33         }
34
35         public int MaxIdleConnectionPoolCount
36         {
37             get { return this.maxCount; }
38         }
39
40         protected object ThisLock
41         {
42             get { return this; }
43         }
44
45         protected abstract void AbortItem(TItem item);
46
47         [Fx.Tag.Throws(typeof(CommunicationException), "A communication exception occurred closing this item")]
48         [Fx.Tag.Throws(typeof(TimeoutException), "Timed out trying to close this item")]
49         protected abstract void CloseItem(TItem item, TimeSpan timeout);
50         protected abstract void CloseItemAsync(TItem item, TimeSpan timeout);
51
52         protected abstract TKey GetPoolKey(EndpointAddress address, Uri via);
53
54         protected virtual EndpointConnectionPool CreateEndpointConnectionPool(TKey key)
55         {
56             return new EndpointConnectionPool(this, key);
57         }
58
59         public bool Close(TimeSpan timeout)
60         {
61             lock (ThisLock)
62             {
63                 if (openCount <= 0)
64                 {
65                     return true;
66                 }
67
68                 openCount--;
69
70                 if (openCount == 0)
71                 {
72                     this.OnClose(timeout);
73                     return true;
74                 }
75
76                 return false;
77             }
78         }
79
80         List<TItem> PruneIfNecessary()
81         {
82             List<TItem> itemsToClose = null;
83             pruneAccrual++;
84             if (pruneAccrual > pruneThreshold)
85             {
86                 pruneAccrual = 0;
87                 itemsToClose = new List<TItem>();
88
89                 // first prune the connection pool contents
90                 foreach (EndpointConnectionPool pool in endpointPools.Values)
91                 {
92                     pool.Prune(itemsToClose);
93                 }
94
95                 // figure out which connection pools are now empty
96                 List<TKey> endpointKeysToRemove = null;
97                 foreach (KeyValuePair<TKey, EndpointConnectionPool> poolEntry in endpointPools)
98                 {
99                     if (poolEntry.Value.CloseIfEmpty())
100                     {
101                         if (endpointKeysToRemove == null)
102                         {
103                             endpointKeysToRemove = new List<TKey>();
104                         }
105                         endpointKeysToRemove.Add(poolEntry.Key);
106                     }
107                 }
108
109                 // and then prune the connection pools themselves
110                 if (endpointKeysToRemove != null)
111                 {
112                     for (int i = 0; i < endpointKeysToRemove.Count; i++)
113                     {
114                         endpointPools.Remove(endpointKeysToRemove[i]);
115                     }
116                 }
117             }
118
119             return itemsToClose;
120         }
121
122         EndpointConnectionPool GetEndpointPool(TKey key, TimeSpan timeout)
123         {
124             EndpointConnectionPool result = null;
125             List<TItem> itemsToClose = null;
126             lock (ThisLock)
127             {
128                 if (!endpointPools.TryGetValue(key, out result))
129                 {
130                     itemsToClose = PruneIfNecessary();
131                     result = CreateEndpointConnectionPool(key);
132                     endpointPools.Add(key, result);
133                 }
134             }
135
136             Fx.Assert(result != null, "EndpointPool must be non-null at this point");
137             if (itemsToClose != null && itemsToClose.Count > 0)
138             {
139                 // allocate half the remaining timeout for our g----ful shutdowns
140                 TimeoutHelper timeoutHelper = new TimeoutHelper(TimeoutHelper.Divide(timeout, 2));
141                 for (int i = 0; i < itemsToClose.Count; i++)
142                 {
143                     result.CloseIdleConnection(itemsToClose[i], timeoutHelper.RemainingTime());
144                 }
145             }
146
147             return result;
148         }
149
150         public bool TryOpen()
151         {
152             lock (ThisLock)
153             {
154                 if (openCount <= 0)
155                 {
156                     // can't reopen connection pools since the registry purges them on close
157                     return false;
158                 }
159                 else
160                 {
161                     openCount++;
162                     return true;
163                 }
164             }
165         }
166
167         protected virtual void OnClosed()
168         {
169         }
170
171         void OnClose(TimeSpan timeout)
172         {
173             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
174             foreach (EndpointConnectionPool pool in endpointPools.Values)
175             {
176                 try
177                 {
178                     pool.Close(timeoutHelper.RemainingTime());
179                 }
180                 catch (CommunicationException exception)
181                 {
182                     if (DiagnosticUtility.ShouldTraceError)
183                     {
184                         TraceUtility.TraceEvent(TraceEventType.Error, TraceCode.ConnectionPoolCloseException, SR.GetString(SR.TraceCodeConnectionPoolCloseException), this, exception);
185                     }
186                 }
187                 catch (TimeoutException exception)
188                 {
189                     if (TD.CloseTimeoutIsEnabled())
190                     {
191                         TD.CloseTimeout(exception.Message);
192                     }
193                     if (DiagnosticUtility.ShouldTraceError)
194                     {
195                         TraceUtility.TraceEvent(TraceEventType.Error, TraceCode.ConnectionPoolCloseException, SR.GetString(SR.TraceCodeConnectionPoolCloseException), this, exception);
196                     }
197                 }
198             }
199
200             endpointPools.Clear();
201         }
202
203         public void AddConnection(TKey key, TItem connection, TimeSpan timeout)
204         {
205             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
206             EndpointConnectionPool endpointPool = GetEndpointPool(key, timeoutHelper.RemainingTime());
207             endpointPool.AddConnection(connection, timeoutHelper.RemainingTime());
208         }
209
210         public TItem TakeConnection(EndpointAddress address, Uri via, TimeSpan timeout, out TKey key)
211         {
212             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
213             key = this.GetPoolKey(address, via);
214             EndpointConnectionPool endpointPool = GetEndpointPool(key, timeoutHelper.RemainingTime());
215             return endpointPool.TakeConnection(timeoutHelper.RemainingTime());
216         }
217
218         public void ReturnConnection(TKey key, TItem connection, bool connectionIsStillGood, TimeSpan timeout)
219         {
220             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
221             EndpointConnectionPool endpointPool = GetEndpointPool(key, timeoutHelper.RemainingTime());
222             endpointPool.ReturnConnection(connection, connectionIsStillGood, timeoutHelper.RemainingTime());
223         }
224
225         // base class for our collection of Idle connections
226         protected abstract class IdleConnectionPool
227         {
228             public abstract int Count { get; }
229             public abstract bool Add(TItem item);
230             public abstract bool Return(TItem item);
231             public abstract TItem Take(out bool closeItem);
232         }
233
234         protected class EndpointConnectionPool
235         {
236             TKey key;
237             List<TItem> busyConnections;
238             bool closed;
239             IdleConnectionPool idleConnections;
240             CommunicationPool<TKey, TItem> parent;
241
242             public EndpointConnectionPool(CommunicationPool<TKey, TItem> parent, TKey key)
243             {
244                 this.key = key;
245                 this.parent = parent;
246                 this.busyConnections = new List<TItem>();
247             }
248
249             protected TKey Key
250             {
251                 get { return this.key; }
252             }
253
254             IdleConnectionPool IdleConnections
255             {
256                 get
257                 {
258                     if (idleConnections == null)
259                     {
260                         idleConnections = GetIdleConnectionPool();
261                     }
262
263                     return idleConnections;
264                 }
265             }
266
267             protected CommunicationPool<TKey, TItem> Parent
268             {
269                 get { return this.parent; }
270             }
271
272             protected object ThisLock
273             {
274                 get { return this; }
275             }
276
277             // close down the pool if empty
278             public bool CloseIfEmpty()
279             {
280                 lock (ThisLock)
281                 {
282                     if (!closed)
283                     {
284                         if (busyConnections.Count > 0)
285                         {
286                             return false;
287                         }
288
289                         if (idleConnections != null && idleConnections.Count > 0)
290                         {
291                             return false;
292                         }
293                         closed = true;
294                     }
295                 }
296
297                 return true;
298             }
299
300             protected virtual void AbortItem(TItem item)
301             {
302                 parent.AbortItem(item);
303             }
304
305             [Fx.Tag.Throws(typeof(CommunicationException), "A communication exception occurred closing this item")]
306             [Fx.Tag.Throws(typeof(TimeoutException), "Timed out trying to close this item")]
307             protected virtual void CloseItem(TItem item, TimeSpan timeout)
308             {
309                 parent.CloseItem(item, timeout);
310             }
311
312             protected virtual void CloseItemAsync(TItem item, TimeSpan timeout)
313             {
314                 parent.CloseItemAsync(item, timeout);
315             }
316
317             public void Abort()
318             {
319                 if (closed)
320                 {
321                     return;
322                 }
323
324                 List<TItem> idleItemsToClose = null;
325                 lock (ThisLock)
326                 {
327                     if (closed)
328                         return;
329
330                     closed = true;
331                     idleItemsToClose = SnapshotIdleConnections();
332                 }
333
334                 AbortConnections(idleItemsToClose);
335             }
336
337             [Fx.Tag.Throws(typeof(CommunicationException), "A communication exception occurred closing this item")]
338             [Fx.Tag.Throws(typeof(TimeoutException), "Timed out trying to close this item")]
339             public void Close(TimeSpan timeout)
340             {
341                 List<TItem> itemsToClose = null;
342                 lock (ThisLock)
343                 {
344                     if (closed)
345                         return;
346
347                     closed = true;
348                     itemsToClose = SnapshotIdleConnections();
349                 }
350
351                 try
352                 {
353                     TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
354                     for (int i = 0; i < itemsToClose.Count; i++)
355                     {
356                         this.CloseItem(itemsToClose[i], timeoutHelper.RemainingTime());
357                     }
358
359                     itemsToClose.Clear();
360                 }
361                 finally
362                 {
363                     AbortConnections(itemsToClose);
364                 }
365             }
366
367             void AbortConnections(List<TItem> idleItemsToClose)
368             {
369                 for (int i = 0; i < idleItemsToClose.Count; i++)
370                 {
371                     this.AbortItem(idleItemsToClose[i]);
372                 }
373
374                 for (int i = 0; i < busyConnections.Count; i++)
375                 {
376                     this.AbortItem(busyConnections[i]);
377                 }
378                 busyConnections.Clear();
379             }
380
381             // must call under lock (ThisLock) since we are calling IdleConnections.Take()
382             List<TItem> SnapshotIdleConnections()
383             {
384                 List<TItem> itemsToClose = new List<TItem>();
385                 bool dummy;
386                 for (;;)
387                 {
388                     TItem item = IdleConnections.Take(out dummy);
389                     if (item == null)
390                         break;
391
392                     itemsToClose.Add(item);
393                 }
394
395                 return itemsToClose;
396             }
397
398             public void AddConnection(TItem connection, TimeSpan timeout)
399             {
400                 bool closeConnection = false;
401                 lock (ThisLock)
402                 {
403                     if (!closed)
404                     {
405                         if (!IdleConnections.Add(connection))
406                         {
407                             closeConnection = true;
408                         }
409                     }
410                     else
411                     {
412                         closeConnection = true;
413                     }
414                 }
415
416                 if (closeConnection)
417                 {
418                     CloseIdleConnection(connection, timeout);
419                 }
420             }
421
422             protected virtual IdleConnectionPool GetIdleConnectionPool()
423             {
424                 return new PoolIdleConnectionPool(parent.MaxIdleConnectionPoolCount);
425             }
426
427             public virtual void Prune(List<TItem> itemsToClose)
428             {
429             }
430
431             public TItem TakeConnection(TimeSpan timeout)
432             {
433                 TItem item = null;
434                 List<TItem> itemsToClose = null;
435                 lock (ThisLock)
436                 {
437                     if (closed)
438                         return null;
439
440                     bool closeItem;
441                     while (true)
442                     {
443                         item = IdleConnections.Take(out closeItem);
444                         if (item == null)
445                         {
446                             break;
447                         }
448
449                         if (!closeItem)
450                         {
451                             busyConnections.Add(item);
452                             break;
453                         }
454
455                         if (itemsToClose == null)
456                         {
457                             itemsToClose = new List<TItem>();
458                         }
459                         itemsToClose.Add(item);
460                     }
461                 }
462
463                 // cleanup any stale items accrued from IdleConnections
464                 if (itemsToClose != null)
465                 {
466                     // and only allocate half the timeout passed in for our g----ful shutdowns
467                     TimeoutHelper timeoutHelper = new TimeoutHelper(TimeoutHelper.Divide(timeout, 2));
468                     for (int i = 0; i < itemsToClose.Count; i++)
469                     {
470                         CloseIdleConnection(itemsToClose[i], timeoutHelper.RemainingTime());
471                     }
472                 }
473
474                 if (TD.ConnectionPoolMissIsEnabled())
475                 {
476                     if (item == null && busyConnections != null)
477                     {
478                         TD.ConnectionPoolMiss(key != null ? key.ToString() : string.Empty, busyConnections.Count);
479                     }
480                 }
481
482                 return item;
483             }
484
485             public void ReturnConnection(TItem connection, bool connectionIsStillGood, TimeSpan timeout)
486             {
487                 bool closeConnection = false;
488                 bool abortConnection = false;
489
490                 lock (ThisLock)
491                 {
492                     if (!closed)
493                     {
494                         if (busyConnections.Remove(connection) && connectionIsStillGood)
495                         {
496                             if (!IdleConnections.Return(connection))
497                             {
498                                 closeConnection = true;
499                             }
500                         }
501                         else
502                         {
503                             abortConnection = true;
504                         }
505                     }
506                     else
507                     {
508                         abortConnection = true;
509                     }
510                 }
511
512                 if (closeConnection)
513                 {
514                     CloseIdleConnection(connection, timeout);
515                 }
516                 else if (abortConnection)
517                 {
518                     this.AbortItem(connection);
519                     OnConnectionAborted();
520                 }
521             }
522
523             public void CloseIdleConnection(TItem connection, TimeSpan timeout)
524             {
525                 bool throwing = true;
526                 try
527                 {
528                     this.CloseItemAsync(connection, timeout);
529                     throwing = false;
530                 }
531                 catch (Exception e)
532                 {
533                     if (Fx.IsFatal(e))
534                     {
535                         throw;
536                     }
537
538                     DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
539                 }
540                 finally
541                 {
542                     if (throwing)
543                     {
544                         this.AbortItem(connection);
545                     }
546                 }
547             }
548
549             protected virtual void OnConnectionAborted()
550             {
551             }
552
553             protected class PoolIdleConnectionPool
554                 : IdleConnectionPool
555             {
556                 Pool<TItem> idleConnections;
557                 int maxCount;
558
559                 public PoolIdleConnectionPool(int maxCount)
560                 {
561                     this.idleConnections = new Pool<TItem>(maxCount);
562                     this.maxCount = maxCount;
563                 }
564
565                 public override int Count
566                 {
567                     get { return idleConnections.Count; }
568                 }
569
570                 public override bool Add(TItem connection)
571                 {
572                     return ReturnToPool(connection);
573                 }
574
575                 public override bool Return(TItem connection)
576                 {
577                     return ReturnToPool(connection);
578                 }
579
580                 bool ReturnToPool(TItem connection)
581                 {
582                     bool result = this.idleConnections.Return(connection);
583                     if (!result)
584                     {
585                         if (TD.MaxOutboundConnectionsPerEndpointExceededIsEnabled())
586                         {
587                             TD.MaxOutboundConnectionsPerEndpointExceeded(SR.GetString(SR.TraceCodeConnectionPoolMaxOutboundConnectionsPerEndpointQuotaReached, maxCount));
588                         }
589                         if (DiagnosticUtility.ShouldTraceInformation)
590                         {
591                             TraceUtility.TraceEvent(TraceEventType.Information,
592                                 TraceCode.ConnectionPoolMaxOutboundConnectionsPerEndpointQuotaReached,
593                                 SR.GetString(SR.TraceCodeConnectionPoolMaxOutboundConnectionsPerEndpointQuotaReached, maxCount),
594                                 this);
595                         }
596                     }
597                     else if (TD.OutboundConnectionsPerEndpointRatioIsEnabled())
598                     {
599                         TD.OutboundConnectionsPerEndpointRatio(this.idleConnections.Count, maxCount);
600                     }
601
602                     return result;
603                 }
604
605                 public override TItem Take(out bool closeItem)
606                 {
607                     closeItem = false;
608                     TItem ret = this.idleConnections.Take();
609                     if (TD.OutboundConnectionsPerEndpointRatioIsEnabled())
610                     {
611                         TD.OutboundConnectionsPerEndpointRatio(this.idleConnections.Count, maxCount);
612                     }
613                     return ret;
614                 }
615             }
616         }
617     }
618
619     // all our connection pools support Idling out of connections and lease timeout
620     // (though Named Pipes doesn't leverage the lease timeout)
621     abstract class ConnectionPool : IdlingCommunicationPool<string, IConnection>
622     {
623         int connectionBufferSize;
624         TimeSpan maxOutputDelay;
625         string name;
626
627         protected ConnectionPool(IConnectionOrientedTransportChannelFactorySettings settings, TimeSpan leaseTimeout)
628             : base(settings.MaxOutboundConnectionsPerEndpoint, settings.IdleTimeout, leaseTimeout)
629         {
630             this.connectionBufferSize = settings.ConnectionBufferSize;
631             this.maxOutputDelay = settings.MaxOutputDelay;
632             this.name = settings.ConnectionPoolGroupName;
633         }
634
635         public string Name
636         {
637             get { return this.name; }
638         }
639
640         protected override void AbortItem(IConnection item)
641         {
642             item.Abort();
643         }
644
645         protected override void CloseItem(IConnection item, TimeSpan timeout)
646         {
647             item.Close(timeout, false);
648         }
649
650         protected override void CloseItemAsync(IConnection item, TimeSpan timeout)
651         {
652             item.Close(timeout, true);
653         }
654
655         public virtual bool IsCompatible(IConnectionOrientedTransportChannelFactorySettings settings)
656         {
657             return (
658                 (this.name == settings.ConnectionPoolGroupName) &&
659                 (this.connectionBufferSize == settings.ConnectionBufferSize) &&
660                 (this.MaxIdleConnectionPoolCount == settings.MaxOutboundConnectionsPerEndpoint) &&
661                 (this.IdleTimeout == settings.IdleTimeout) &&
662                 (this.maxOutputDelay == settings.MaxOutputDelay)
663                 );
664         }
665     }
666
667     // Helper class used to manage the lifetime of a connection relative to its pool.
668     abstract class ConnectionPoolHelper
669     {
670         IConnectionInitiator connectionInitiator;
671         ConnectionPool connectionPool;
672         Uri via;
673         bool closed;
674
675         // key for rawConnection in the connection pool
676         string connectionKey;
677
678         // did rawConnection originally come from connectionPool?
679         bool isConnectionFromPool;
680
681         // the "raw" connection that should be stored in the pool
682         IConnection rawConnection;
683
684         // the "upgraded" connection built on top of the "raw" connection to be used for I/O
685         IConnection upgradedConnection;
686
687         EventTraceActivity eventTraceActivity;
688
689         public ConnectionPoolHelper(ConnectionPool connectionPool, IConnectionInitiator connectionInitiator, Uri via)
690         {
691             this.connectionInitiator = connectionInitiator;
692             this.connectionPool = connectionPool;
693             this.via = via;
694         }
695
696         object ThisLock
697         {
698             get { return this; }
699         }
700
701         protected EventTraceActivity EventTraceActivity
702         {
703             get
704             {
705                 if (this.eventTraceActivity == null)
706                 {
707                     this.eventTraceActivity = EventTraceActivity.GetFromThreadOrCreate();
708                 }
709                 return this.eventTraceActivity;
710             }
711         }
712
713         protected abstract IConnection AcceptPooledConnection(IConnection connection, ref TimeoutHelper timeoutHelper);
714         protected abstract IAsyncResult BeginAcceptPooledConnection(IConnection connection, ref TimeoutHelper timeoutHelper,
715             AsyncCallback callback, object state);
716         protected abstract IConnection EndAcceptPooledConnection(IAsyncResult result);
717
718         protected abstract TimeoutException CreateNewConnectionTimeoutException(TimeSpan timeout, TimeoutException innerException);
719
720         public IAsyncResult BeginEstablishConnection(TimeSpan timeout, AsyncCallback callback, object state)
721         {
722             return new EstablishConnectionAsyncResult(this, timeout, callback, state);
723         }
724
725         public IConnection EndEstablishConnection(IAsyncResult result)
726         {
727             return EstablishConnectionAsyncResult.End(result);
728         }
729
730         IConnection TakeConnection(TimeSpan timeout)
731         {
732             return this.connectionPool.TakeConnection(null, this.via, timeout, out this.connectionKey);
733         }
734
735         public IConnection EstablishConnection(TimeSpan timeout)
736         {
737             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
738             IConnection localRawConnection = null;
739             IConnection localUpgradedConnection = null;
740             bool localIsConnectionFromPool = true;
741
742             EventTraceActivity localEventTraceActivity = this.EventTraceActivity;
743             if (TD.EstablishConnectionStartIsEnabled())
744             {
745                 TD.EstablishConnectionStart(localEventTraceActivity,
746                                             this.via != null ? this.via.AbsoluteUri : string.Empty);
747             }
748
749             // first try and use a connection from our pool (and use it if we successfully receive an ACK)
750             while (localIsConnectionFromPool)
751             {
752                 localRawConnection = this.TakeConnection(timeoutHelper.RemainingTime());
753                 if (localRawConnection == null)
754                 {
755                     localIsConnectionFromPool = false;
756                 }
757                 else
758                 {
759                     bool preambleSuccess = false;
760                     try
761                     {
762                         localUpgradedConnection = AcceptPooledConnection(localRawConnection, ref timeoutHelper);
763                         preambleSuccess = true;
764                         break;
765                     }
766                     catch (CommunicationException e)
767                     {
768                         DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
769                         // CommmunicationException is ok since it was a cached connection of unknown state
770                     }
771                     catch (TimeoutException e)
772                     {
773                         if (TD.OpenTimeoutIsEnabled())
774                         {
775                             TD.OpenTimeout(e.Message);
776                         }
777                         DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
778                         // ditto for TimeoutException
779                     }
780                     finally
781                     {
782                         if (!preambleSuccess)
783                         {
784                             if (TD.ConnectionPoolPreambleFailedIsEnabled())
785                             {
786                                 TD.ConnectionPoolPreambleFailed(localEventTraceActivity);
787                             }
788
789                             if (DiagnosticUtility.ShouldTraceInformation)
790                             {
791                                 TraceUtility.TraceEvent(
792                                     TraceEventType.Information,
793                                     TraceCode.FailedAcceptFromPool,
794                                     SR.GetString(
795                                         SR.TraceCodeFailedAcceptFromPool,
796                                         timeoutHelper.RemainingTime()));
797                             }
798
799                             // This cannot throw TimeoutException since isConnectionStillGood is false (doesn't attempt a Close).
800                             this.connectionPool.ReturnConnection(connectionKey, localRawConnection, false, TimeSpan.Zero);
801                         }
802                     }
803                 }
804             }
805
806             // if there isn't anything in the pool, we need to use a new connection
807             if (!localIsConnectionFromPool)
808             {
809                 bool success = false;
810                 TimeSpan connectTimeout = timeoutHelper.RemainingTime();
811                 try
812                 {
813                     try
814                     {
815                         localRawConnection = this.connectionInitiator.Connect(this.via, connectTimeout);
816                     }
817                     catch (TimeoutException e)
818                     {
819                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateNewConnectionTimeoutException(
820                             connectTimeout, e));
821                     }
822
823                     this.connectionInitiator = null;
824                     localUpgradedConnection = AcceptPooledConnection(localRawConnection, ref timeoutHelper);
825                     success = true;
826                 }
827                 finally
828                 {
829                     if (!success)
830                     {
831                         connectionKey = null;
832                         if (localRawConnection != null)
833                         {
834                             localRawConnection.Abort();
835                         }
836                     }
837                 }
838             }
839
840             SnapshotConnection(localUpgradedConnection, localRawConnection, localIsConnectionFromPool);
841
842             if (TD.EstablishConnectionStopIsEnabled())
843             {
844                 TD.EstablishConnectionStop(localEventTraceActivity);
845             }
846
847             return localUpgradedConnection;
848         }
849
850         void SnapshotConnection(IConnection upgradedConnection, IConnection rawConnection, bool isConnectionFromPool)
851         {
852             lock (ThisLock)
853             {
854                 if (closed)
855                 {
856                     upgradedConnection.Abort();
857
858                     // cleanup our pool if necessary
859                     if (isConnectionFromPool)
860                     {
861                         this.connectionPool.ReturnConnection(this.connectionKey, rawConnection, false, TimeSpan.Zero);
862                     }
863
864                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
865                         new CommunicationObjectAbortedException(
866                         SR.GetString(SR.OperationAbortedDuringConnectionEstablishment, this.via)));
867                 }
868                 else
869                 {
870                     this.upgradedConnection = upgradedConnection;
871                     this.rawConnection = rawConnection;
872                     this.isConnectionFromPool = isConnectionFromPool;
873                 }
874             }
875         }
876
877         public void Abort()
878         {
879             ReleaseConnection(true, TimeSpan.Zero);
880         }
881
882         public void Close(TimeSpan timeout)
883         {
884             ReleaseConnection(false, timeout);
885         }
886
887         void ReleaseConnection(bool abort, TimeSpan timeout)
888         {
889             string localConnectionKey;
890             IConnection localUpgradedConnection;
891             IConnection localRawConnection;
892
893             lock (ThisLock)
894             {
895                 this.closed = true;
896                 localConnectionKey = this.connectionKey;
897                 localUpgradedConnection = this.upgradedConnection;
898                 localRawConnection = this.rawConnection;
899
900                 this.upgradedConnection = null;
901                 this.rawConnection = null;
902             }
903
904             if (localUpgradedConnection == null)
905             {
906                 return;
907             }
908
909             try
910             {
911                 if (this.isConnectionFromPool)
912                 {
913                     this.connectionPool.ReturnConnection(localConnectionKey, localRawConnection, !abort, timeout);
914                 }
915                 else
916                 {
917                     if (abort)
918                     {
919                         localUpgradedConnection.Abort();
920                     }
921                     else
922                     {
923                         this.connectionPool.AddConnection(localConnectionKey, localRawConnection, timeout);
924                     }
925                 }
926             }
927             catch (CommunicationException e)
928             {
929                 DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
930                 localUpgradedConnection.Abort();
931             }
932         }
933
934         class EstablishConnectionAsyncResult : AsyncResult
935         {
936             ConnectionPoolHelper parent;
937             TimeoutHelper timeoutHelper;
938             IConnection currentConnection;
939             IConnection rawConnection;
940             bool newConnection;
941             bool cleanupConnection;
942             TimeSpan connectTimeout;
943             static AsyncCallback onConnect;
944             static AsyncCallback onProcessConnection = Fx.ThunkCallback(new AsyncCallback(OnProcessConnection));
945             EventTraceActivity eventTraceActivity;
946
947             public EstablishConnectionAsyncResult(ConnectionPoolHelper parent,
948                 TimeSpan timeout, AsyncCallback callback, object state)
949                 : base(callback, state)
950             {
951                 this.parent = parent;
952                 this.timeoutHelper = new TimeoutHelper(timeout);
953
954                 bool success = false;
955                 bool completeSelf = false;
956                 try
957                 {
958                     completeSelf = Begin();
959                     success = true;
960                 }
961                 finally
962                 {
963                     if (!success)
964                     {
965                         Cleanup();
966                     }
967                 }
968
969                 if (completeSelf)
970                 {
971                     Cleanup();
972                     base.Complete(true);
973                 }
974             }
975
976             EventTraceActivity EventTraceActivity
977             {
978                 get
979                 {
980                     if (this.eventTraceActivity == null)
981                     {
982                         this.eventTraceActivity = new EventTraceActivity();
983                     }
984                     return this.eventTraceActivity;
985                 }
986             }
987
988             public static IConnection End(IAsyncResult result)
989             {
990                 EstablishConnectionAsyncResult thisPtr = AsyncResult.End<EstablishConnectionAsyncResult>(result);
991
992                 if (TD.EstablishConnectionStopIsEnabled())
993                 {
994                     TD.EstablishConnectionStop(thisPtr.EventTraceActivity);
995                 }
996
997                 return thisPtr.currentConnection;
998             }
999
1000             bool Begin()
1001             {
1002                 if (TD.EstablishConnectionStartIsEnabled())
1003                 {
1004                     TD.EstablishConnectionStart(this.EventTraceActivity, this.parent.connectionKey);
1005                 }
1006
1007                 IConnection connection = parent.TakeConnection(timeoutHelper.RemainingTime());
1008
1009                 TrackConnection(connection);
1010
1011                 // first try and use a connection from our pool
1012                 bool openingFromPool;
1013                 if (OpenUsingConnectionPool(out openingFromPool))
1014                 {
1015                     return true;
1016                 }
1017
1018                 if (openingFromPool)
1019                 {
1020                     return false;
1021                 }
1022                 else
1023                 {
1024                     // if there isn't anything in the pool, we need to use a new connection
1025                     return OpenUsingNewConnection();
1026                 }
1027             }
1028
1029             bool OpenUsingConnectionPool(out bool openingFromPool)
1030             {
1031                 openingFromPool = true;
1032                 while (this.currentConnection != null)
1033                 {
1034                     bool snapshotCollection = false;
1035                     try
1036                     {
1037                         if (ProcessConnection())
1038                         {
1039                             snapshotCollection = true;
1040                         }
1041                         else
1042                         {
1043                             return false;
1044                         }
1045                     }
1046                     catch (CommunicationException e)
1047                     {
1048                         DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
1049                         // CommunicationException is allowed for cached channels, as the connection
1050                         // could be stale
1051                         Cleanup(); // remove residual state
1052                     }
1053                     catch (TimeoutException e)
1054                     {
1055                         if (TD.OpenTimeoutIsEnabled())
1056                         {
1057                             TD.OpenTimeout(e.Message);
1058                         }
1059                         DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
1060                         // ditto for TimeoutException
1061                         Cleanup(); // remove residual state
1062                     }
1063
1064                     if (snapshotCollection) // connection succeeded. Snapshot and return
1065                     {
1066                         SnapshotConnection();
1067                         return true;
1068                     }
1069
1070                     // previous connection failed, try again
1071                     IConnection connection = parent.TakeConnection(timeoutHelper.RemainingTime());
1072
1073                     TrackConnection(connection);
1074                 }
1075
1076                 openingFromPool = false;
1077                 return false;
1078             }
1079
1080             bool OpenUsingNewConnection()
1081             {
1082                 this.newConnection = true;
1083                 IAsyncResult result;
1084
1085                 try
1086                 {
1087                     this.connectTimeout = timeoutHelper.RemainingTime();
1088
1089                     if (onConnect == null)
1090                     {
1091                         onConnect = Fx.ThunkCallback(new AsyncCallback(OnConnect));
1092                     }
1093
1094                     result = parent.connectionInitiator.BeginConnect(
1095                          parent.via, this.connectTimeout, onConnect, this);
1096                 }
1097                 catch (TimeoutException e)
1098                 {
1099                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1100                         parent.CreateNewConnectionTimeoutException(connectTimeout, e));
1101                 }
1102
1103                 if (!result.CompletedSynchronously)
1104                 {
1105                     return false;
1106                 }
1107
1108                 return HandleConnect(result);
1109             }
1110
1111             bool HandleConnect(IAsyncResult connectResult)
1112             {
1113                 try
1114                 {
1115                     TrackConnection(parent.connectionInitiator.EndConnect(connectResult));
1116                 }
1117                 catch (TimeoutException e)
1118                 {
1119                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1120                         parent.CreateNewConnectionTimeoutException(connectTimeout, e));
1121                 }
1122
1123                 if (ProcessConnection())
1124                 {
1125                     // success. Snapshot and return
1126                     SnapshotConnection();
1127                     return true;
1128                 }
1129                 else
1130                 {
1131                     return false;
1132                 }
1133             }
1134
1135             bool ProcessConnection()
1136             {
1137                 IAsyncResult result = parent.BeginAcceptPooledConnection(this.rawConnection,
1138                     ref timeoutHelper, onProcessConnection, this);
1139                 if (!result.CompletedSynchronously)
1140                 {
1141                     return false;
1142                 }
1143
1144                 return HandleProcessConnection(result);
1145             }
1146
1147             bool HandleProcessConnection(IAsyncResult result)
1148             {
1149                 this.currentConnection = parent.EndAcceptPooledConnection(result);
1150                 this.cleanupConnection = false;
1151                 return true;
1152             }
1153
1154             void SnapshotConnection()
1155             {
1156                 parent.SnapshotConnection(this.currentConnection, this.rawConnection, !this.newConnection);
1157             }
1158
1159             void TrackConnection(IConnection connection)
1160             {
1161                 this.cleanupConnection = true;
1162                 this.rawConnection = connection;
1163                 this.currentConnection = connection;
1164             }
1165
1166             void Cleanup()
1167             {
1168                 if (this.cleanupConnection)
1169                 {
1170                     if (this.newConnection)
1171                     {
1172                         if (this.currentConnection != null)
1173                         {
1174                             this.currentConnection.Abort();
1175                             this.currentConnection = null;
1176                         }
1177                     }
1178                     else if (this.rawConnection != null)
1179                     {
1180                         if (DiagnosticUtility.ShouldTraceInformation)
1181                         {
1182                             TraceUtility.TraceEvent(
1183                                 TraceEventType.Information,
1184                                 TraceCode.FailedAcceptFromPool,
1185                                 SR.GetString(
1186                                     SR.TraceCodeFailedAcceptFromPool,
1187                                     this.timeoutHelper.RemainingTime()));
1188                         }
1189
1190                         // This cannot throw TimeoutException since isConnectionStillGood is false (doesn't attempt a Close).
1191                         parent.connectionPool.ReturnConnection(parent.connectionKey, this.rawConnection,
1192                             false, timeoutHelper.RemainingTime());
1193                         this.currentConnection = null;
1194                         this.rawConnection = null;
1195                     }
1196
1197                     this.cleanupConnection = false;
1198                 }
1199             }
1200
1201             static void OnConnect(IAsyncResult result)
1202             {
1203                 if (result.CompletedSynchronously)
1204                 {
1205                     return;
1206                 }
1207
1208                 EstablishConnectionAsyncResult thisPtr = (EstablishConnectionAsyncResult)result.AsyncState;
1209
1210                 Exception completionException = null;
1211                 bool completeSelf;
1212                 try
1213                 {
1214                     completeSelf = thisPtr.HandleConnect(result);
1215                 }
1216 #pragma warning suppress 56500 // Microsoft, transferring exception to another thread
1217                 catch (Exception e)
1218                 {
1219                     if (Fx.IsFatal(e))
1220                     {
1221                         throw;
1222                     }
1223
1224                     completeSelf = true;
1225                     completionException = e;
1226                 }
1227
1228                 if (completeSelf)
1229                 {
1230                     thisPtr.Cleanup();
1231                     thisPtr.Complete(false, completionException);
1232                 }
1233             }
1234
1235             static void OnProcessConnection(IAsyncResult result)
1236             {
1237                 if (result.CompletedSynchronously)
1238                 {
1239                     return;
1240                 }
1241
1242                 EstablishConnectionAsyncResult thisPtr = (EstablishConnectionAsyncResult)result.AsyncState;
1243
1244                 Exception completionException = null;
1245                 bool completeSelf;
1246                 try
1247                 {
1248                     bool snapshotCollection = false;
1249                     try
1250                     {
1251                         completeSelf = thisPtr.HandleProcessConnection(result);
1252                         if (completeSelf)
1253                         {
1254                             snapshotCollection = true;
1255                         }
1256                     }
1257                     catch (CommunicationException communicationException)
1258                     {
1259                         if (!thisPtr.newConnection) // CommunicationException is ok from our cache
1260                         {
1261                             DiagnosticUtility.TraceHandledException(communicationException, TraceEventType.Information);                            
1262                             thisPtr.Cleanup();
1263                             completeSelf = thisPtr.Begin();
1264                         }
1265                         else
1266                         {
1267                             completeSelf = true;
1268                             completionException = communicationException;
1269                         }
1270                     }
1271                     catch (TimeoutException timeoutException)
1272                     {
1273                         if (!thisPtr.newConnection) // TimeoutException is ok from our cache
1274                         {
1275                             if (TD.OpenTimeoutIsEnabled())
1276                             {
1277                                 TD.OpenTimeout(timeoutException.Message);
1278                             }
1279                             DiagnosticUtility.TraceHandledException(timeoutException, TraceEventType.Information);                            
1280                             thisPtr.Cleanup();
1281                             completeSelf = thisPtr.Begin();
1282                         }
1283                         else
1284                         {
1285                             completeSelf = true;
1286                             completionException = timeoutException;
1287                         }
1288                     }
1289
1290                     if (snapshotCollection)
1291                     {
1292                         thisPtr.SnapshotConnection();
1293                     }
1294                 }
1295 #pragma warning suppress 56500 // Microsoft, transferring exception to another thread
1296                 catch (Exception e)
1297                 {
1298                     if (Fx.IsFatal(e))
1299                     {
1300                         throw;
1301                     }
1302
1303                     completeSelf = true;
1304                     completionException = e;
1305                 }
1306
1307                 if (completeSelf)
1308                 {
1309                     thisPtr.Cleanup();
1310                     thisPtr.Complete(false, completionException);
1311                 }
1312             }
1313         }
1314     }
1315 }
1316
1317