1 //------------------------------------------------------------------------------
2 // <copyright file="ConnectionPool.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
10 using System.Net.Sockets;
11 using System.Collections;
12 using System.Diagnostics;
13 using System.Globalization;
14 using System.Runtime.InteropServices;
15 using System.Security;
16 using System.Security.Permissions;
17 using System.Threading;
19 internal delegate void GeneralAsyncDelegate(object request, object state);
20 internal delegate PooledStream CreateConnectionDelegate(ConnectionPool pool);
24 /// Impliments basic ConnectionPooling by pooling PooledStreams
27 internal class ConnectionPool {
34 private static TimerThread.Callback s_CleanupCallback = new TimerThread.Callback(CleanupCallbackWrapper);
35 private static TimerThread.Callback s_CancelErrorCallback = new TimerThread.Callback(CancelErrorCallbackWrapper);
36 private static TimerThread.Queue s_CancelErrorQueue = TimerThread.GetOrCreateQueue(ErrorWait);
38 private const int MaxQueueSize = (int)0x00100000;
40 // The order of these is important; we want the WaitAny call to be signaled
41 // for a free object before a creation signal. Only the index first signaled
42 // object is returned from the WaitAny call.
43 private const int SemaphoreHandleIndex = (int)0x0;
44 private const int ErrorHandleIndex = (int)0x1;
45 private const int CreationHandleIndex = (int)0x2;
47 private const int WaitTimeout = (int)0x102;
48 private const int WaitAbandoned = (int)0x80;
50 private const int ErrorWait = 5 * 1000; // 5 seconds
52 private readonly TimerThread.Queue m_CleanupQueue;
54 private State m_State;
55 private InterlockedStack m_StackOld;
56 private InterlockedStack m_StackNew;
58 private int m_WaitCount;
59 private WaitHandle[] m_WaitHandles;
61 private Exception m_ResError;
62 private volatile bool m_ErrorOccured;
64 private TimerThread.Timer m_ErrorTimer;
66 private ArrayList m_ObjectList;
67 private int m_TotalObjects;
69 private Queue m_QueuedRequests;
70 private Thread m_AsyncThread;
72 private int m_MaxPoolSize;
73 private int m_MinPoolSize;
74 private ServicePoint m_ServicePoint;
75 private CreateConnectionDelegate m_CreateConnectionCallback;
77 private Mutex CreationMutex {
79 return (Mutex) m_WaitHandles[CreationHandleIndex];
83 private ManualResetEvent ErrorEvent {
85 return (ManualResetEvent) m_WaitHandles[ErrorHandleIndex];
89 private Semaphore Semaphore {
91 return (Semaphore) m_WaitHandles[SemaphoreHandleIndex];
96 /// <para>Constructor - binds pool with a servicePoint and sets up a cleanup Timer to remove Idle Connections</para>
98 internal ConnectionPool(ServicePoint servicePoint, int maxPoolSize, int minPoolSize, int idleTimeout, CreateConnectionDelegate createConnectionCallback) : base() {
99 m_State = State.Initializing;
101 m_CreateConnectionCallback = createConnectionCallback;
102 m_MaxPoolSize = maxPoolSize;
103 m_MinPoolSize = minPoolSize;
104 m_ServicePoint = servicePoint;
108 if (idleTimeout > 0) {
109 // special case: if the timeout value is 1 then the timer thread should have a duration
110 // of 1 to avoid having the timer callback run constantly
111 m_CleanupQueue = TimerThread.GetOrCreateQueue(idleTimeout == 1 ? 1 : (idleTimeout / 2));
112 m_CleanupQueue.CreateTimer(s_CleanupCallback, this);
117 /// <para>Internal init stuff, creates stacks, queue, wait handles etc</para>
119 private void Initialize() {
120 m_StackOld = new InterlockedStack();
121 m_StackNew = new InterlockedStack();
123 m_QueuedRequests = new Queue();
125 m_WaitHandles = new WaitHandle[3];
126 m_WaitHandles[SemaphoreHandleIndex] = new Semaphore(0, MaxQueueSize);
127 m_WaitHandles[ErrorHandleIndex] = new ManualResetEvent(false);
128 m_WaitHandles[CreationHandleIndex] = new Mutex();
130 m_ErrorTimer = null; // No error yet.
132 m_ObjectList = new ArrayList();
133 m_State = State.Running;
138 /// <para>Async state object, used for storing state on async calls</para>
140 private class AsyncConnectionPoolRequest {
141 public AsyncConnectionPoolRequest(ConnectionPool pool, object owningObject, GeneralAsyncDelegate asyncCallback, int creationTimeout) {
143 OwningObject = owningObject;
144 AsyncCallback = asyncCallback;
145 CreationTimeout = creationTimeout;
147 public object OwningObject;
148 public GeneralAsyncDelegate AsyncCallback;
150 public bool Completed;
152 public ConnectionPool Pool;
153 public int CreationTimeout;
157 /// <para>Queues a AsyncConnectionPoolRequest to our queue of requests needing
158 /// a pooled stream. If an AsyncThread is not created, we create one,
159 /// and let it process the queued items</para>
161 private void QueueRequest(AsyncConnectionPoolRequest asyncRequest) {
162 lock(m_QueuedRequests) {
163 m_QueuedRequests.Enqueue(asyncRequest);
164 if (m_AsyncThread == null) {
165 m_AsyncThread = new Thread(new ThreadStart(AsyncThread));
166 m_AsyncThread.IsBackground = true;
167 m_AsyncThread.Start();
173 /// <para>Processes async queued requests that are blocked on needing a free pooled stream
174 /// works as follows:
175 /// 1. while there are blocked requests, take one out of the queue
176 /// 2. Wait for a free connection, when one becomes avail, then notify the request that its there
177 /// 3. repeat 1 until there are no more queued requests
178 /// 4. if there are no more requests waiting to for a free stream, then close down this thread
181 private void AsyncThread() {
183 while (m_QueuedRequests.Count > 0) {
184 bool continueLoop = true;
185 AsyncConnectionPoolRequest asyncState = null;
186 lock (m_QueuedRequests) {
187 asyncState = (AsyncConnectionPoolRequest) m_QueuedRequests.Dequeue();
190 WaitHandle [] localWaitHandles = m_WaitHandles;
191 PooledStream PooledStream = null;
193 while ((PooledStream == null) && continueLoop) {
194 int result = WaitHandle.WaitAny(localWaitHandles, asyncState.CreationTimeout, false);
196 Get(asyncState.OwningObject, result, ref continueLoop, ref localWaitHandles);
199 PooledStream.Activate(asyncState.OwningObject, asyncState.AsyncCallback);
200 } catch (Exception e) {
201 if(PooledStream != null){
202 PutConnection(PooledStream, asyncState.OwningObject, asyncState.CreationTimeout, false);
204 asyncState.AsyncCallback(asyncState.OwningObject, e);
208 lock(m_QueuedRequests) {
209 if (m_QueuedRequests.Count == 0) {
210 m_AsyncThread = null;
218 /// <para>Count of total pooled streams associated with this pool, including streams that are being used</para>
221 get { return(m_TotalObjects); }
225 /// <para>Our ServicePoint, used for IP resolution</para>
227 internal ServicePoint ServicePoint {
229 return m_ServicePoint;
234 /// <para>Our Max Size of outstanding pooled streams</para>
236 internal int MaxPoolSize {
238 return m_MaxPoolSize;
243 /// <para>Our Min Size of the pool to remove idled items down to</para>
245 internal int MinPoolSize {
247 return m_MinPoolSize;
252 /// <para>An Error occurred usually due to an abort</para>
254 private bool ErrorOccurred {
255 get { return m_ErrorOccured; }
258 private static void CleanupCallbackWrapper(TimerThread.Timer timer, int timeNoticed, object context)
260 ConnectionPool pThis = (ConnectionPool) context;
264 pThis.CleanupCallback();
268 pThis.m_CleanupQueue.CreateTimer(s_CleanupCallback, context);
273 /// Cleans up everything in both the old and new stack. If a connection is in use
274 /// then it will be on neither stack and it is the responsibility of the object
275 /// using that connection to clean it up when it is finished using it. This does
276 /// not clean up the ConnectionPool object and new connections can still be
277 /// created if needed in the future should this ConnectionPool object be reused
279 /// preconditions: none
281 /// postconditions: any connections not currently in use by an object will be
282 /// gracefully terminated and purged from this connection pool
284 internal void ForceCleanup()
287 Logging.Enter(Logging.Web, "ConnectionPool::ForceCleanup");
290 // If WaitOne returns false, all connections in the pool are in use
291 // so no cleanup should be performed. The last object owning
292 // a connection from the pool will perform final cleanup.
294 if (Semaphore.WaitOne(0, false)) {
295 // Try to clean up from new stack first, if there isn't anything on new
296 // then try old. When we lock the Semaphore, it gives us a license to
297 // remove only one connection from the pool but it can be from either
298 // stack since if the Semaphore is locked by another thread it means that
299 // there must have been more than one connection available in either stack
300 PooledStream pooledStream = (PooledStream)m_StackNew.Pop();
302 // no streams in stack new, there must therefore be one in stack old since we
303 // were able to acquire the semaphore
304 if(pooledStream == null) {
305 pooledStream = (PooledStream)m_StackOld.Pop();
308 Debug.Assert(pooledStream != null, "Acquired Semaphore with no connections in either stack");
309 Destroy(pooledStream);
312 // couldn't get semaphore, nothing to do here
318 Logging.Exit(Logging.Web, "ConnectionPool::ForceCleanup");
323 /// <para>This is called by a timer, to check for needed cleanup of idle pooled streams</para>
325 private void CleanupCallback()
327 // Called when the cleanup-timer ticks over.
329 // This is the automatic prunning method. Every period, we will perform a two-step
330 // process. First, for the objects above MinPool, we will obtain the semaphore for
331 // the object and then destroy it if it was on the old stack. We will continue this
332 // until we either reach MinPool size, or we are unable to obtain a free object, or
333 // until we have exhausted all the objects on the old stack. After that, push all
334 // objects on the new stack to the old stack. So, every period the objects on the
335 // old stack are destroyed and the objects on the new stack are pushed to the old
336 // stack. All objects that are currently out and in use are not on either stack.
337 // With this logic, a object is prunned if unused for at least one period but not
338 // more than two periods.
340 // Destroy free objects above MinPool size from old stack.
341 while(Count > MinPoolSize) { // While above MinPoolSize...
343 // acquiring the Semaphore gives us a license to remove one and only
344 // one connection from the pool
345 if (Semaphore.WaitOne(0, false) ) { // != WaitTimeout
346 // We obtained a objects from the semaphore.
347 PooledStream pooledStream = (PooledStream) m_StackOld.Pop();
349 if (null != pooledStream) {
350 // If we obtained one from the old stack, destroy it.
351 Destroy(pooledStream);
354 // Else we exhausted the old stack, so break
355 // and release the Semaphore to indicate that
356 // no connection was actually removed so whatever
357 // we had locked is still available.
358 Semaphore.ReleaseSemaphore();
365 // Push to the old-stack. For each free object, move object from new stack
366 // to old stack. The Semaphore guarantees that we are allowed to handle
367 // one connection at a time so moving a connection between stacks is safe since
368 // one connection is reserved for the duration of this loop and we only touch
369 // one connection at a time on the new stack
370 if(Semaphore.WaitOne(0, false)) { // != WaitTimeout
372 PooledStream pooledStream = (PooledStream) m_StackNew.Pop();
374 if (null == pooledStream)
377 GlobalLog.Assert(!pooledStream.IsEmancipated, "Pooled object not in pool.");
378 GlobalLog.Assert(pooledStream.CanBePooled, "Pooled object is not poolable.");
380 m_StackOld.Push(pooledStream);
382 // no connections were actually destroyed so signal that a connection is now
383 // available since we are no longer reserving a connection by holding the
385 Semaphore.ReleaseSemaphore();
390 /// <para>Creates a new PooledStream, performs checks as well on the new stream</para>
392 private PooledStream Create(CreateConnectionDelegate createConnectionCallback) {
393 GlobalLog.Enter("ConnectionPool#" + ValidationHelper.HashString(this) + "::Create");
394 PooledStream newObj = null;
397 newObj = createConnectionCallback(this);
400 throw new InternalException(); // Create succeeded, but null object
402 if (!newObj.CanBePooled)
403 throw new InternalException(); // Create succeeded, but non-poolable object
405 newObj.PrePush(null);
407 lock (m_ObjectList.SyncRoot) {
408 m_ObjectList.Add(newObj);
409 m_TotalObjects = m_ObjectList.Count;
412 GlobalLog.Print("Create pooledStream#"+ValidationHelper.HashString(newObj));
415 GlobalLog.Print("Pool Exception: " + e.Message);
417 newObj = null; // set to null, so we do not return bad new object
418 // Failed to create instance
422 GlobalLog.Leave("ConnectionPool#" + ValidationHelper.HashString(this) + "::Create",ValidationHelper.HashString(newObj));
428 /// <para>Destroys a pooled stream from the pool</para>
430 private void Destroy(PooledStream pooledStream) {
431 GlobalLog.Print("Destroy pooledStream#"+ValidationHelper.HashString(pooledStream));
433 if (null != pooledStream) {
436 lock (m_ObjectList.SyncRoot) {
437 m_ObjectList.Remove(pooledStream);
438 m_TotalObjects = m_ObjectList.Count;
443 pooledStream.Dispose();
448 private static void CancelErrorCallbackWrapper(TimerThread.Timer timer, int timeNoticed, object context)
450 ((ConnectionPool) context).CancelErrorCallback();
454 /// <para>Called on error, after we waited a set amount of time from aborting</para>
456 private void CancelErrorCallback()
458 TimerThread.Timer timer = m_ErrorTimer;
459 if (timer != null && timer.Cancel())
461 m_ErrorOccured = false;
469 /// <para>Retrieves a pooled stream from the pool proper
470 /// this work by first attemting to find something in the pool on the New stack
471 /// and then trying the Old stack if something is not there availble </para>
473 private PooledStream GetFromPool(object owningObject) {
474 PooledStream res = null;
475 GlobalLog.Enter("ConnectionPool#" + ValidationHelper.HashString(this) + "::GetFromPool");
476 res = (PooledStream) m_StackNew.Pop();
478 res = (PooledStream) m_StackOld.Pop();
481 // The semaphore guaranteed that a connection was available so if res is
482 // null it means that this contract has been violated somewhere
483 GlobalLog.Assert(res != null, "GetFromPool called with nothing in the pool!");
486 res.PostPop(owningObject);
487 GlobalLog.Print("GetFromGeneralPool pooledStream#"+ValidationHelper.HashString(res));
490 GlobalLog.Leave("ConnectionPool#" + ValidationHelper.HashString(this) + "::GetFromPool",ValidationHelper.HashString(res));
495 /// <para>Retrieves the pooled stream out of the pool, does this by using the result
496 /// of a WaitAny as input, and then based on whether it has a mutex, event, semaphore,
497 /// or timeout decides what action to take</para>
499 private PooledStream Get(object owningObject, int result, ref bool continueLoop, ref WaitHandle [] waitHandles) {
500 PooledStream pooledStream = null;
501 GlobalLog.Enter("ConnectionPool#" + ValidationHelper.HashString(this) + "::Get", result.ToString());
504 // From the WaitAny docs: "If more than one object became signaled during
505 // the call, this is the array index of the signaled object with the
506 // smallest index value of all the signaled objects." This is important
507 // so that the free object signal will be returned before a creation
512 Interlocked.Decrement(ref m_WaitCount);
513 continueLoop = false;
514 GlobalLog.Leave("ConnectionPool#" + ValidationHelper.HashString(this) + "::Get","throw Timeout WebException");
515 throw new WebException(NetRes.GetWebStatusString("net_timeout", WebExceptionStatus.ConnectFailure), WebExceptionStatus.Timeout);
518 case ErrorHandleIndex:
519 // Throw the error that PoolCreateRequest stashed.
520 int newWaitCount = Interlocked.Decrement(ref m_WaitCount);
521 continueLoop = false;
522 Exception exceptionToThrow = m_ResError;
523 if (newWaitCount == 0) {
524 CancelErrorCallback();
526 throw exceptionToThrow;
528 // The creation mutex signaled, which means no connections are available in
529 // the connection pool. This means you might be able to create a connection.
530 case CreationHandleIndex:
533 // try creating a new connection
534 pooledStream = UserCreateRequest();
536 if (null != pooledStream) {
537 pooledStream.PostPop(owningObject);
538 Interlocked.Decrement(ref m_WaitCount);
539 continueLoop = false;
543 // If we were not able to create an object, check to see if
544 // we reached MaxPoolSize. If so, we will no longer wait on
545 // the CreationHandle, but instead wait for a free object or
548 // Consider changing: if we receive the CreationHandle midway into the wait
549 // period and re-wait, we will be waiting on the full period
550 if (Count >= MaxPoolSize && 0 != MaxPoolSize) {
551 if (!ReclaimEmancipatedObjects()) {
552 // modify handle array not to wait on creation mutex anymore
553 waitHandles = new WaitHandle[2];
554 waitHandles[0] = m_WaitHandles[0];
555 waitHandles[1] = m_WaitHandles[1];
562 CreationMutex.ReleaseMutex();
567 // the semaphore was signaled which can only happen
568 // when a connection has been placed in the pool
569 // so there is guaranteed available inventory
570 Interlocked.Decrement(ref m_WaitCount);
571 pooledStream = GetFromPool(owningObject);
572 continueLoop = false;
575 GlobalLog.Leave("ConnectionPool#" + ValidationHelper.HashString(this) + "::Get",ValidationHelper.HashString(pooledStream));
580 /// <para>Aborts the queued requests to the pool</para>
582 internal void Abort() {
583 if (m_ResError == null) {
584 m_ResError = new WebException(
585 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
586 WebExceptionStatus.RequestCanceled);
589 m_ErrorOccured = true;
590 m_ErrorTimer = s_CancelErrorQueue.CreateTimer(s_CancelErrorCallback, this);
594 /// <para>Attempts to create a PooledStream, by trying to get a pooled Connection,
595 /// or by creating its own new one</para>
597 internal PooledStream GetConnection(object owningObject,
598 GeneralAsyncDelegate asyncCallback,
599 int creationTimeout) {
601 PooledStream stream = null;
602 bool continueLoop = true;
603 bool async = (asyncCallback != null) ? true : false;
605 GlobalLog.Enter("ConnectionPool#" + ValidationHelper.HashString(this) + "::GetConnection");
607 if(m_State != State.Running) {
608 throw new InternalException();
611 Interlocked.Increment(ref m_WaitCount);
612 WaitHandle[] localWaitHandles = m_WaitHandles;
615 result = WaitHandle.WaitAny(localWaitHandles, 0, false);
616 if (result != WaitTimeout) {
617 stream = Get(owningObject, result, ref continueLoop, ref localWaitHandles);
619 if (stream == null) {
620 GlobalLog.Print("GetConnection:"+ValidationHelper.HashString(this)+" going async");
621 AsyncConnectionPoolRequest asyncState = new AsyncConnectionPoolRequest(this, owningObject, asyncCallback, creationTimeout);
622 QueueRequest(asyncState);
625 // loop while we don't have an error/timeout and we haven't gotten a stream yet
626 while ((stream == null) && continueLoop) {
627 result = WaitHandle.WaitAny(localWaitHandles, creationTimeout, false);
628 stream = Get(owningObject, result, ref continueLoop, ref localWaitHandles);
632 if (null != stream) {
633 // if there is already a stream, then we're not going async
634 if (!stream.IsInitalizing) {
635 asyncCallback = null;
639 // If activate returns false, it is going to finish asynchronously
640 // and therefore the stream will be returned in a callback and
641 // we should not return it here (return null)
642 if (stream.Activate(owningObject, asyncCallback) == false)
646 PutConnection(stream,owningObject,creationTimeout, false);
650 throw new InternalException();
653 GlobalLog.Leave("ConnectionPool#" + ValidationHelper.HashString(this) + "::GetConnection", ValidationHelper.HashString(stream));
659 /// Attempts to return a PooledStream to the pool. Default is that it can be reused if it can
663 internal void PutConnection(PooledStream pooledStream, object owningObject, int creationTimeout)
666 PutConnection(pooledStream, owningObject, creationTimeout, true);
671 /// Attempts to return a PooledStream to the pool. If canReuse is false, then the
672 /// connection will be destroyed even if it is marked as reusable and a new conneciton will
673 /// be created. If it is true, then the connection will still be checked to ensure that
674 /// it can be pooled and will be cleaned up if it can not for another reason.
677 internal void PutConnection(PooledStream pooledStream, object owningObject, int creationTimeout, bool canReuse) {
678 GlobalLog.Print("ConnectionPool#" + ValidationHelper.HashString(this) + "::PutConnection");
679 if (pooledStream == null) {
680 throw new ArgumentNullException("pooledStream");
683 pooledStream.PrePush(owningObject);
685 if (m_State != State.ShuttingDown) {
686 pooledStream.Deactivate();
688 // cancel our error status, if we have no new requests waiting anymore
689 if (m_WaitCount == 0) {
690 CancelErrorCallback();
693 if (canReuse && pooledStream.CanBePooled) {
694 PutNew(pooledStream);
698 Destroy(pooledStream);
699 } finally { // Make sure to release the mutex even under error conditions.
700 // Make sure we recreate a new pooled stream, if there are requests for a stream
702 if (m_WaitCount > 0) {
703 if (!CreationMutex.WaitOne(creationTimeout, false)) {
707 pooledStream = UserCreateRequest();
708 if (null != pooledStream) {
709 PutNew(pooledStream);
712 CreationMutex.ReleaseMutex();
720 // If we're shutting down, we destroy the object.
721 Destroy(pooledStream);
727 /// <para>Places a new/reusable stream in the new stack of the pool</para>
729 private void PutNew(PooledStream pooledStream) {
730 GlobalLog.Enter("ConnectionPool#" + ValidationHelper.HashString(this) + "::PutNew", "#"+ValidationHelper.HashString(pooledStream));
732 GlobalLog.Assert(null != pooledStream, "Why are we adding a null object to the pool?");
733 GlobalLog.Assert(pooledStream.CanBePooled, "Non-poolable object in pool.");
735 m_StackNew.Push(pooledStream);
736 // ensure that the semaphore's count is incremented to signal an available connection is in
738 Semaphore.ReleaseSemaphore();
739 GlobalLog.Leave("ConnectionPool#" + ValidationHelper.HashString(this) + "::PutNew");
744 /// <para>Reclaim any pooled Streams that have seen their users/WebRequests GCed away</para>
746 private bool ReclaimEmancipatedObjects() {
747 bool emancipatedObjectFound = false;
748 GlobalLog.Enter("ConnectionPool#" + ValidationHelper.HashString(this) + "::ReclaimEmancipatedObjects");
750 lock(m_ObjectList.SyncRoot) {
752 object[] objectList = m_ObjectList.ToArray();
753 if (null != objectList) {
755 for (int i = 0; i < objectList.Length; ++i) {
756 PooledStream pooledStream = (PooledStream) objectList[i];
758 if (null != pooledStream) {
762 Monitor.TryEnter(pooledStream, ref locked);
765 if (pooledStream.IsEmancipated) {
767 GlobalLog.Print("EmancipatedObject pooledStream#"+ValidationHelper.HashString(pooledStream));
768 PutConnection(pooledStream, null, Timeout.Infinite);
769 emancipatedObjectFound = true;
775 Monitor.Exit(pooledStream);
781 GlobalLog.Leave("ConnectionPool#" + ValidationHelper.HashString(this) + "::ReclaimEmancipatedObjects",emancipatedObjectFound.ToString());
782 return emancipatedObjectFound;
786 /// <para>Creates a new PooledStream is allowable</para>
788 private PooledStream UserCreateRequest() {
789 // called by user when they were not able to obtain a free object but
790 // instead obtained creation mutex
791 GlobalLog.Enter("ConnectionPool#" + ValidationHelper.HashString(this) + "::UserCreateRequest");
793 PooledStream pooledStream = null;
795 if (!ErrorOccurred) {
796 if (Count < MaxPoolSize || 0 == MaxPoolSize) {
797 // If we have an odd number of total objects, reclaim any dead objects.
798 // If we did not find any objects to reclaim, create a new one.
801 if ((Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects())
802 pooledStream = Create(m_CreateConnectionCallback);
805 GlobalLog.Leave("ConnectionPool#" + ValidationHelper.HashString(this) + "::UserCreateRequest", ValidationHelper.HashString(pooledStream));
812 /// <para>Used to Pool streams in a thread safe manner</para>
814 sealed internal class InterlockedStack {
815 private readonly Stack _stack = new Stack();
821 private readonly Hashtable doublepush = new Hashtable();
824 internal InterlockedStack() {
827 internal void Push(Object pooledStream) {
828 GlobalLog.Assert(null != pooledStream, "push null");
829 if (null == pooledStream) { throw new ArgumentNullException("pooledStream"); }
830 lock(_stack.SyncRoot) {
832 GlobalLog.Assert(null == doublepush[pooledStream], "object already in stack");
833 doublepush[pooledStream] = _stack.Count;
835 _stack.Push(pooledStream);
837 GlobalLog.Assert(_count+1 == _stack.Count, "push count mishandle");
840 _count = _stack.Count;
845 internal Object Pop() {
846 lock(_stack.SyncRoot) {
847 object pooledStream = null;
848 if (0 <_stack.Count) {
849 pooledStream = _stack.Pop();
851 GlobalLog.Assert(_count-1 == _stack.Count, "pop count mishandle");
852 doublepush.Remove(pooledStream);
855 _count = _stack.Count;