|
//------------------------------------------------------------------------------
// <copyright file="TdsParserSessionPool.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
namespace System.Data.SqlClient {
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections.Concurrent;
internal class TdsParserSessionPool {
// NOTE: This is a very simplistic, lightweight pooler. It wasn't
// intended to handle huge number of items, just to keep track
// of the session objects to ensure that they're cleaned up in
// a timely manner, to avoid holding on to an unacceptible
// amount of server-side resources in the event that consumers
// let their data readers be GC'd, instead of explicitly
// closing or disposing of them
private const int MaxInactiveCount = 10; // pick something, preferably small...
private static int _objectTypeCount; // Bid counter
private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
private readonly TdsParser _parser; // parser that owns us
private readonly List<TdsParserStateObject> _cache; // collection of all known sessions
private int _cachedCount; // lock-free _cache.Count
private TdsParserStateObject[] _freeStateObjects; // collection of all sessions available for reuse
private int _freeStateObjectCount; // Number of available free sessions
internal TdsParserSessionPool(TdsParser parser) {
_parser = parser;
_cache = new List<TdsParserStateObject>();
_freeStateObjects = new TdsParserStateObject[MaxInactiveCount];
_freeStateObjectCount = 0;
if (Bid.AdvancedOn) {
Bid.Trace("<sc.TdsParserSessionPool.ctor|ADV> %d# created session pool for parser %d\n", ObjectID, parser.ObjectID);
}
}
private bool IsDisposed {
get {
return (null == _freeStateObjects);
}
}
internal int ObjectID {
get {
return _objectID;
}
}
internal void Deactivate() {
// When being deactivated, we check all the sessions in the
// cache to make sure they're cleaned up and then we dispose of
// sessions that are past what we want to keep around.
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<sc.TdsParserSessionPool.Deactivate|ADV> %d# deactivating cachedCount=%d\n", ObjectID, _cachedCount);
try {
lock(_cache) {
// NOTE: The PutSession call below may choose to remove the
// session from the cache, which will throw off our
// enumerator. We avoid that by simply indexing backward
// through the array.
for (int i = _cache.Count - 1; i >= 0 ; i--) {
TdsParserStateObject session = _cache[i];
if (null != session) {
if (session.IsOrphaned) {
//
if (Bid.AdvancedOn) {
Bid.Trace("<sc.TdsParserSessionPool.Deactivate|ADV> %d# reclaiming session %d\n", ObjectID, session.ObjectID);
}
PutSession(session);
}
}
}
//
}
}
finally {
Bid.ScopeLeave(ref hscp);
}
}
// This is called from a ThreadAbort - ensure that it can be run from a CER Catch
internal void BestEffortCleanup() {
for (int i = 0; i < _cache.Count; i++) {
TdsParserStateObject session = _cache[i];
if (null != session) {
var sessionHandle = session.Handle;
if (sessionHandle != null) {
sessionHandle.Dispose();
}
}
}
}
internal void Dispose() {
if (Bid.AdvancedOn) {
Bid.Trace("<sc.TdsParserSessionPool.Dispose|ADV> %d# disposing cachedCount=%d\n", ObjectID, _cachedCount);
}
lock(_cache) {
// Dispose free sessions
for (int i = 0; i < _freeStateObjectCount; i++) {
if (_freeStateObjects[i] != null) {
_freeStateObjects[i].Dispose();
}
}
_freeStateObjects = null;
_freeStateObjectCount = 0;
// Dispose orphaned sessions
for (int i = 0; i < _cache.Count; i++) {
if (_cache[i] != null) {
if (_cache[i].IsOrphaned) {
_cache[i].Dispose();
}
else {
// Remove the "initial" callback (this will allow the stateObj to be GC collected if need be)
_cache[i].DecrementPendingCallbacks(false);
}
}
}
_cache.Clear();
_cachedCount = 0;
// Any active sessions will take care of themselves
// (It's too dangerous to dispose them, as this can cause AVs)
}
}
internal TdsParserStateObject GetSession(object owner) {
TdsParserStateObject session;
lock (_cache) {
if (IsDisposed) {
throw ADP.ClosedConnectionError();
}
else if (_freeStateObjectCount > 0) {
// Free state object - grab it
_freeStateObjectCount--;
session = _freeStateObjects[_freeStateObjectCount];
_freeStateObjects[_freeStateObjectCount] = null;
Debug.Assert(session != null, "There was a null session in the free session list?");
}
else {
// No free objects, create a new one
session = _parser.CreateSession();
if (Bid.AdvancedOn) {
Bid.Trace("<sc.TdsParserSessionPool.CreateSession|ADV> %d# adding session %d to pool\n", ObjectID, session.ObjectID);
}
_cache.Add(session);
_cachedCount = _cache.Count;
}
session.Activate(owner);
}
if (Bid.AdvancedOn) {
Bid.Trace("<sc.TdsParserSessionPool.GetSession|ADV> %d# using session %d\n", ObjectID, session.ObjectID);
}
return session;
}
internal void PutSession(TdsParserStateObject session) {
Debug.Assert (null != session, "null session?");
//Debug.Assert(null != session.Owner, "session without owner?");
bool okToReuse = session.Deactivate();
lock (_cache) {
if (IsDisposed) {
// We're diposed - just clean out the session
Debug.Assert(_cachedCount == 0, "SessionPool is disposed, but there are still sessions in the cache?");
session.Dispose();
}
else if ((okToReuse) && (_freeStateObjectCount < MaxInactiveCount)) {
// Session is good to re-use and our cache has space
if (Bid.AdvancedOn) {
Bid.Trace("<sc.TdsParserSessionPool.PutSession|ADV> %d# keeping session %d cachedCount=%d\n", ObjectID, session.ObjectID, _cachedCount);
}
Debug.Assert(!session._pendingData, "pending data on a pooled session?");
_freeStateObjects[_freeStateObjectCount] = session;
_freeStateObjectCount++;
}
else {
// Either the session is bad, or we have no cache space - so dispose the session and remove it
if (Bid.AdvancedOn) {
Bid.Trace("<sc.TdsParserSessionPool.PutSession|ADV> %d# disposing session %d cachedCount=%d\n", ObjectID, session.ObjectID, _cachedCount);
}
bool removed = _cache.Remove(session);
Debug.Assert(removed, "session not in pool?");
_cachedCount = _cache.Count;
session.Dispose();
}
session.RemoveOwner();
}
}
internal string TraceString() {
return String.Format(/*IFormatProvider*/ null,
"(ObjID={0}, free={1}, cached={2}, total={3})",
_objectID,
null == _freeStateObjects ? "(null)" : _freeStateObjectCount.ToString((IFormatProvider) null),
_cachedCount,
_cache.Count);
}
internal int ActiveSessionsCount {
get {
return _cachedCount - _freeStateObjectCount;
}
}
}
}
|