e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1534 lines
67 KiB
C#
1534 lines
67 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="SqlSessionStateStore.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* SqlSessionStateStore.cs
|
|
*
|
|
* Copyright (c) 1998-2000, Microsoft Corporation
|
|
*
|
|
*/
|
|
|
|
namespace System.Web.SessionState {
|
|
|
|
using System;
|
|
using System.Configuration;
|
|
using System.Collections;
|
|
using System.Threading;
|
|
using System.IO;
|
|
using System.Web;
|
|
using System.Web.Caching;
|
|
using System.Web.Util;
|
|
using System.Data;
|
|
using System.Data.SqlClient;
|
|
using System.Data.Common;
|
|
using System.Text;
|
|
using System.Security.Principal;
|
|
using System.Xml;
|
|
using System.Collections.Specialized;
|
|
using System.Configuration.Provider;
|
|
using System.Globalization;
|
|
using System.Web.Management;
|
|
using System.Web.Hosting;
|
|
using System.Web.Configuration;
|
|
|
|
/*
|
|
* Provides session state via SQL Server
|
|
*/
|
|
internal class SqlSessionStateStore : SessionStateStoreProviderBase {
|
|
|
|
internal enum SupportFlags : uint {
|
|
None = 0x00000000,
|
|
GetLockAge = 0x00000001,
|
|
Uninitialized = 0xFFFFFFFF
|
|
}
|
|
|
|
#pragma warning disable 0649
|
|
static ReadWriteSpinLock s_lock;
|
|
#pragma warning restore 0649
|
|
static int s_isClearPoolInProgress;
|
|
static int s_commandTimeout;
|
|
static TimeSpan s_retryInterval;
|
|
static SqlPartitionInfo s_singlePartitionInfo;
|
|
static PartitionManager s_partitionManager;
|
|
static bool s_oneTimeInited;
|
|
static bool s_usePartition;
|
|
static EventHandler s_onAppDomainUnload;
|
|
|
|
|
|
// We keep these info because we don't want to hold on to the config object.
|
|
static string s_configPartitionResolverType;
|
|
static string s_configSqlConnectionFileName;
|
|
static int s_configSqlConnectionLineNumber;
|
|
static bool s_configAllowCustomSqlDatabase;
|
|
static bool s_configCompressionEnabled;
|
|
|
|
// Per request info
|
|
HttpContext _rqContext;
|
|
int _rqOrigStreamLen;
|
|
IPartitionResolver _partitionResolver;
|
|
SqlPartitionInfo _partitionInfo;
|
|
|
|
const int ITEM_SHORT_LENGTH = 7000;
|
|
const int SQL_ERROR_PRIMARY_KEY_VIOLATION = 2627;
|
|
const int SQL_LOGIN_FAILED = 18456;
|
|
const int SQL_LOGIN_FAILED_2 = 18452;
|
|
const int SQL_LOGIN_FAILED_3 = 18450;
|
|
const int SQL_CANNOT_OPEN_DATABASE_FOR_LOGIN = 4060;
|
|
const int SQL_TIMEOUT_EXPIRED = -2;
|
|
const int APP_SUFFIX_LENGTH = 8;
|
|
const int FIRST_RETRY_SLEEP_TIME = 5000;
|
|
const int RETRY_SLEEP_TIME = 1000;
|
|
|
|
static int ID_LENGTH = SessionIDManager.SessionIDMaxLength + APP_SUFFIX_LENGTH;
|
|
internal const int SQL_COMMAND_TIMEOUT_DEFAULT = 30; // in sec
|
|
|
|
internal SqlSessionStateStore() {
|
|
}
|
|
|
|
internal override void Initialize(string name, NameValueCollection config, IPartitionResolver partitionResolver) {
|
|
_partitionResolver = partitionResolver;
|
|
Initialize(name, config);
|
|
}
|
|
|
|
#if DBG
|
|
SessionStateModule _module;
|
|
|
|
internal void SetModule(SessionStateModule module) {
|
|
_module = module;
|
|
}
|
|
#endif
|
|
|
|
public override void Initialize(string name, NameValueCollection config)
|
|
{
|
|
if (String.IsNullOrEmpty(name))
|
|
name = "SQL Server Session State Provider";
|
|
|
|
base.Initialize(name, config);
|
|
|
|
if (!s_oneTimeInited) {
|
|
s_lock.AcquireWriterLock();
|
|
try {
|
|
if (!s_oneTimeInited) {
|
|
OneTimeInit();
|
|
}
|
|
}
|
|
finally {
|
|
s_lock.ReleaseWriterLock();
|
|
}
|
|
}
|
|
|
|
if (!s_usePartition) {
|
|
// For single partition, the connection info won't change from request to request
|
|
Debug.Assert(_partitionResolver == null);
|
|
_partitionInfo = s_singlePartitionInfo;
|
|
}
|
|
}
|
|
|
|
void OneTimeInit() {
|
|
SessionStateSection config = RuntimeConfig.GetAppConfig().SessionState;
|
|
|
|
s_configPartitionResolverType = config.PartitionResolverType;
|
|
s_configSqlConnectionFileName = config.ElementInformation.Properties["sqlConnectionString"].Source;
|
|
s_configSqlConnectionLineNumber = config.ElementInformation.Properties["sqlConnectionString"].LineNumber;
|
|
s_configAllowCustomSqlDatabase = config.AllowCustomSqlDatabase;
|
|
s_configCompressionEnabled = config.CompressionEnabled;
|
|
|
|
if (_partitionResolver == null) {
|
|
String sqlConnectionString = config.SqlConnectionString;
|
|
|
|
SessionStateModule.ReadConnectionString(config, ref sqlConnectionString, "sqlConnectionString");
|
|
s_singlePartitionInfo = (SqlPartitionInfo)CreatePartitionInfo(sqlConnectionString);
|
|
}
|
|
else {
|
|
s_usePartition = true;
|
|
s_partitionManager = new PartitionManager(new CreatePartitionInfo(CreatePartitionInfo));
|
|
}
|
|
|
|
s_commandTimeout = (int)config.SqlCommandTimeout.TotalSeconds;
|
|
s_retryInterval = config.SqlConnectionRetryInterval;
|
|
|
|
s_isClearPoolInProgress = 0;
|
|
|
|
// We only need to do this in one instance
|
|
s_onAppDomainUnload = new EventHandler(OnAppDomainUnload);
|
|
Thread.GetDomain().DomainUnload += s_onAppDomainUnload;
|
|
|
|
// Last thing to set.
|
|
s_oneTimeInited = true;
|
|
}
|
|
|
|
void OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs) {
|
|
Debug.Trace("SqlSessionStateStore", "OnAppDomainUnload called");
|
|
|
|
Thread.GetDomain().DomainUnload -= s_onAppDomainUnload;
|
|
|
|
if (_partitionResolver == null) {
|
|
if (s_singlePartitionInfo != null) {
|
|
s_singlePartitionInfo.Dispose();
|
|
}
|
|
}
|
|
else {
|
|
if (s_partitionManager != null) {
|
|
s_partitionManager.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal IPartitionInfo CreatePartitionInfo(string sqlConnectionString) {
|
|
/*
|
|
* Parse the connection string for errors. We want to ensure
|
|
* that the user's connection string doesn't contain an
|
|
* Initial Catalog entry, so we must first create a dummy connection.
|
|
*/
|
|
SqlConnection dummyConnection;
|
|
string attachDBFilename = null;
|
|
|
|
try {
|
|
dummyConnection = new SqlConnection(sqlConnectionString);
|
|
}
|
|
catch (Exception e) {
|
|
if (s_usePartition) {
|
|
HttpException outerException = new HttpException(
|
|
SR.GetString(SR.Error_parsing_sql_partition_resolver_string, s_configPartitionResolverType, e.Message), e);
|
|
|
|
outerException.SetFormatter(new UseLastUnhandledErrorFormatter(outerException));
|
|
|
|
throw outerException;
|
|
}
|
|
else {
|
|
throw new ConfigurationErrorsException(
|
|
SR.GetString(SR.Error_parsing_session_sqlConnectionString, e.Message), e,
|
|
s_configSqlConnectionFileName, s_configSqlConnectionLineNumber);
|
|
}
|
|
}
|
|
|
|
// Search for both Database and AttachDbFileName. Don't append our
|
|
// database name if either of them exists.
|
|
string database = dummyConnection.Database;
|
|
SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(sqlConnectionString);
|
|
|
|
if (String.IsNullOrEmpty(database)) {
|
|
database = scsb.AttachDBFilename;
|
|
attachDBFilename = database;
|
|
}
|
|
|
|
if (!String.IsNullOrEmpty(database)) {
|
|
if (!s_configAllowCustomSqlDatabase) {
|
|
if (s_usePartition) {
|
|
throw new HttpException(
|
|
SR.GetString(SR.No_database_allowed_in_sql_partition_resolver_string,
|
|
s_configPartitionResolverType, dummyConnection.DataSource, database));
|
|
}
|
|
else {
|
|
throw new ConfigurationErrorsException(
|
|
SR.GetString(SR.No_database_allowed_in_sqlConnectionString),
|
|
s_configSqlConnectionFileName, s_configSqlConnectionLineNumber);
|
|
}
|
|
}
|
|
|
|
if (attachDBFilename != null) {
|
|
HttpRuntime.CheckFilePermission(attachDBFilename, true);
|
|
}
|
|
}
|
|
else {
|
|
sqlConnectionString += ";Initial Catalog=ASPState";
|
|
}
|
|
|
|
return new SqlPartitionInfo(new ResourcePool(new TimeSpan(0, 0, 5), int.MaxValue),
|
|
scsb.IntegratedSecurity,
|
|
sqlConnectionString);
|
|
|
|
}
|
|
|
|
public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback) {
|
|
return false;
|
|
}
|
|
|
|
public override void Dispose() {
|
|
}
|
|
|
|
public override void InitializeRequest(HttpContext context) {
|
|
Debug.Assert(context != null, "context != null");
|
|
|
|
_rqContext = context;
|
|
_rqOrigStreamLen = 0;
|
|
|
|
if (s_usePartition) {
|
|
// For multiple partition case, the connection info can change from request to request
|
|
_partitionInfo = null;
|
|
}
|
|
}
|
|
|
|
public override void EndRequest(HttpContext context) {
|
|
Debug.Assert(context != null, "context != null");
|
|
_rqContext = null;
|
|
}
|
|
|
|
public bool KnowForSureNotUsingIntegratedSecurity {
|
|
get {
|
|
if (_partitionInfo == null) {
|
|
Debug.Assert(s_usePartition, "_partitionInfo can be null only if we're using paritioning and we haven't called GetConnection yet.");
|
|
// If we're using partitioning, we need the session id to figure out the connection
|
|
// string. Without it, we can't know for sure.
|
|
return false;
|
|
}
|
|
else {
|
|
Debug.Assert(_partitionInfo != null);
|
|
return !_partitionInfo.UseIntegratedSecurity;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Regarding resource pool, we will turn it on if in <identity>:
|
|
// - User is not using integrated security
|
|
// - impersonation = "false"
|
|
// - impersonation = "true" and userName/password is NON-null
|
|
// - impersonation = "true" and IIS is using Anonymous
|
|
//
|
|
// Otherwise, the impersonated account will be dynamic and we have to turn
|
|
// resource pooling off.
|
|
//
|
|
// Note:
|
|
// In case 2. above, the user can specify different usernames in different
|
|
// web.config in different subdirs in the app. In this case, we will just
|
|
// cache the connections in the resource pool based on the identity of the
|
|
// connection. So in this specific scenario it is possible to have the
|
|
// resource pool filled with mixed identities.
|
|
//
|
|
bool CanUsePooling() {
|
|
bool ret;
|
|
|
|
if (KnowForSureNotUsingIntegratedSecurity) {
|
|
Debug.Trace("SessionStatePooling", "CanUsePooling: not using integrated security");
|
|
ret = true;
|
|
}
|
|
else if (_rqContext == null) {
|
|
// One way this can happen is we hit an error on page compilation,
|
|
// and SessionStateModule.OnEndRequest is called
|
|
Debug.Trace("SessionStatePooling", "CanUsePooling: no context");
|
|
ret = false;
|
|
}
|
|
else if (!_rqContext.IsClientImpersonationConfigured) {
|
|
Debug.Trace("SessionStatePooling", "CanUsePooling: mode is None or Application");
|
|
ret = true;
|
|
}
|
|
else if (HttpRuntime.IsOnUNCShareInternal) {
|
|
Debug.Trace("SessionStatePooling", "CanUsePooling: mode is UNC");
|
|
ret = false;
|
|
}
|
|
else {
|
|
string logon = _rqContext.WorkerRequest.GetServerVariable("LOGON_USER");
|
|
|
|
Debug.Trace("SessionStatePooling", "LOGON_USER = '" + logon + "'; identity = '" + _rqContext.User.Identity.Name + "'; IsUNC = " + HttpRuntime.IsOnUNCShareInternal);
|
|
|
|
if (String.IsNullOrEmpty(logon)) {
|
|
ret = true;
|
|
}
|
|
else {
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
Debug.Trace("SessionStatePooling", "CanUsePooling returns " + ret);
|
|
return ret;
|
|
}
|
|
|
|
SqlStateConnection GetConnection(string id, ref bool usePooling) {
|
|
SqlStateConnection conn = null;
|
|
|
|
if (_partitionInfo == null) {
|
|
Debug.Assert(s_partitionManager != null);
|
|
Debug.Assert(_partitionResolver != null);
|
|
|
|
_partitionInfo = (SqlPartitionInfo)s_partitionManager.GetPartition(_partitionResolver, id);
|
|
}
|
|
|
|
Debug.Trace("SessionStatePooling", "Calling GetConnection under " + WindowsIdentity.GetCurrent().Name);
|
|
#if DBG
|
|
Debug.Assert(_module._rqChangeImpersonationRefCount != 0,
|
|
"SessionStateModule.ChangeImpersonation should have been called before making any call to SQL");
|
|
#endif
|
|
|
|
usePooling = CanUsePooling();
|
|
if (usePooling) {
|
|
conn = (SqlStateConnection) _partitionInfo.RetrieveResource();
|
|
if (conn != null && (conn.Connection.State & ConnectionState.Open) == 0) {
|
|
conn.Dispose();
|
|
conn = null;
|
|
}
|
|
}
|
|
|
|
if (conn == null) {
|
|
conn = new SqlStateConnection(_partitionInfo, s_retryInterval);
|
|
}
|
|
|
|
return conn;
|
|
}
|
|
|
|
void DisposeOrReuseConnection(ref SqlStateConnection conn, bool usePooling) {
|
|
try {
|
|
if (conn == null) {
|
|
return;
|
|
}
|
|
|
|
if (usePooling) {
|
|
conn.ClearAllParameters();
|
|
_partitionInfo.StoreResource(conn);
|
|
conn = null;
|
|
}
|
|
}
|
|
finally {
|
|
if (conn != null) {
|
|
conn.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void ThrowSqlConnectionException(SqlConnection conn, Exception e) {
|
|
if (s_usePartition) {
|
|
throw new HttpException(
|
|
SR.GetString(SR.Cant_connect_sql_session_database_partition_resolver,
|
|
s_configPartitionResolverType, conn.DataSource, conn.Database),
|
|
e);
|
|
}
|
|
else {
|
|
throw new HttpException(
|
|
SR.GetString(SR.Cant_connect_sql_session_database),
|
|
e);
|
|
}
|
|
}
|
|
|
|
SessionStateStoreData DoGet(HttpContext context, String id, bool getExclusive,
|
|
out bool locked,
|
|
out TimeSpan lockAge,
|
|
out object lockId,
|
|
out SessionStateActions actionFlags) {
|
|
SqlDataReader reader;
|
|
byte [] buf;
|
|
MemoryStream stream = null;
|
|
SessionStateStoreData item;
|
|
bool useGetLockAge = false;
|
|
SqlStateConnection conn = null;
|
|
SqlCommand cmd = null;
|
|
bool usePooling = true;
|
|
|
|
Debug.Assert(id.Length <= SessionIDManager.SESSION_ID_LENGTH_LIMIT, "id.Length <= SessionIDManager.SESSION_ID_LENGTH_LIMIT");
|
|
Debug.Assert(context != null, "context != null");
|
|
|
|
// Set default return values
|
|
locked = false;
|
|
lockId = null;
|
|
lockAge = TimeSpan.Zero;
|
|
actionFlags = 0;
|
|
|
|
buf = null;
|
|
reader = null;
|
|
|
|
conn = GetConnection(id, ref usePooling);
|
|
|
|
Debug.Assert(_partitionInfo != null, "_partitionInfo != null");
|
|
Debug.Assert(_partitionInfo.SupportFlags != SupportFlags.Uninitialized, "_partitionInfo.SupportFlags != SupportFlags.Uninitialized");
|
|
|
|
//
|
|
// In general, if we're talking to a SQL 2000 or above, we use LockAge; otherwise we use LockDate.
|
|
// Below are the details:
|
|
//
|
|
// Version 1
|
|
// ---------
|
|
// In v1, the lockDate is generated and stored in SQL using local time, and we calculate the "lockage"
|
|
// (i.e. how long the item is locked) by having the web server read lockDate from SQL and substract it
|
|
// from DateTime.Now. But this approach introduced two problems:
|
|
// 1. SQL server and web servers need to be in the same time zone.
|
|
// 2. Daylight savings problem.
|
|
//
|
|
// Version 1.1
|
|
// -----------
|
|
// In v1.1, if using SQL 2000 we fixed the problem by calculating the "lockage" directly in SQL
|
|
// so that the SQL server and the web server don't have to be in the same time zone. We also
|
|
// use UTC date to store time in SQL so that the Daylight savings problem is solved.
|
|
//
|
|
// In summary, if using SQL 2000 we made the following changes to the SQL tables:
|
|
// i. The column Expires is using now UTC time
|
|
// ii. Add new SP TempGetStateItem2 and TempGetStateItemExclusive2 to return a lockage
|
|
// instead of a lockDate.
|
|
// iii. To support v1 web server, we still need to have TempGetStateItem and
|
|
// TempGetStateItemExclusive. However, we modify it a bit so that they use
|
|
// UTC time to update Expires column.
|
|
//
|
|
// If using SQL 7, we decided not to fix the problem, and the SQL scripts for SQL 7 remain pretty much
|
|
// the same. That means v1.1 web server will continue to call TempGetStateItem and
|
|
// TempGetStateItemExclusive and use v1 way to calculate the "lockage".
|
|
//
|
|
// Version 2.0
|
|
// -----------
|
|
// In v2.0 we added some new SP TempGetStateItem3 and TempGetStateItemExclusive3
|
|
// because we added a new return value 'actionFlags'. However, the principle remains the same
|
|
// that we support lockAge only if talking to SQL 2000.
|
|
//
|
|
// (When one day MS stops supporting SQL 7 we can remove all the SQL7-specific scripts and
|
|
// stop all these craziness.)
|
|
//
|
|
if ((_partitionInfo.SupportFlags & SupportFlags.GetLockAge) != 0) {
|
|
useGetLockAge = true;
|
|
}
|
|
|
|
try {
|
|
if (getExclusive) {
|
|
cmd = conn.TempGetExclusive;
|
|
}
|
|
else {
|
|
cmd = conn.TempGet;
|
|
}
|
|
|
|
cmd.Parameters[0].Value = id + _partitionInfo.AppSuffix; // @id
|
|
cmd.Parameters[1].Value = Convert.DBNull; // @itemShort
|
|
cmd.Parameters[2].Value = Convert.DBNull; // @locked
|
|
cmd.Parameters[3].Value = Convert.DBNull; // @lockDate or @lockAge
|
|
cmd.Parameters[4].Value = Convert.DBNull; // @lockCookie
|
|
cmd.Parameters[5].Value = Convert.DBNull; // @actionFlags
|
|
|
|
using(reader = SqlExecuteReaderWithRetry(cmd, CommandBehavior.Default)) {
|
|
|
|
/* If the cmd returned data, we must read it all before getting out params */
|
|
if (reader != null) {
|
|
try {
|
|
if (reader.Read()) {
|
|
Debug.Trace("SqlSessionStateStore", "Sql Get returned long item");
|
|
buf = (byte[]) reader[0];
|
|
}
|
|
} catch(Exception e) {
|
|
ThrowSqlConnectionException(cmd.Connection, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check if value was returned */
|
|
if (Convert.IsDBNull(cmd.Parameters[2].Value)) {
|
|
Debug.Trace("SqlSessionStateStore", "Sql Get returned null");
|
|
return null;
|
|
}
|
|
|
|
/* Check if item is locked */
|
|
Debug.Assert(!Convert.IsDBNull(cmd.Parameters[3].Value), "!Convert.IsDBNull(cmd.Parameters[3].Value)");
|
|
Debug.Assert(!Convert.IsDBNull(cmd.Parameters[4].Value), "!Convert.IsDBNull(cmd.Parameters[4].Value)");
|
|
|
|
locked = (bool) cmd.Parameters[2].Value;
|
|
lockId = (int) cmd.Parameters[4].Value;
|
|
|
|
if (locked) {
|
|
Debug.Trace("SqlSessionStateStore", "Sql Get returned item that was locked");
|
|
Debug.Assert(((int)cmd.Parameters[5].Value & (int)SessionStateActions.InitializeItem) == 0,
|
|
"(cmd.Parameters[5].Value & SessionStateActions.InitializeItem) == 0; uninit item shouldn't be locked");
|
|
|
|
if (useGetLockAge) {
|
|
lockAge = new TimeSpan(0, 0, (int) cmd.Parameters[3].Value);
|
|
}
|
|
else {
|
|
DateTime lockDate;
|
|
lockDate = (DateTime) cmd.Parameters[3].Value;
|
|
lockAge = DateTime.Now - lockDate;
|
|
}
|
|
|
|
Debug.Trace("SqlSessionStateStore", "LockAge = " + lockAge);
|
|
|
|
if (lockAge > new TimeSpan(0, 0, Sec.ONE_YEAR)) {
|
|
Debug.Trace("SqlSessionStateStore", "Lock age is more than 1 year!!!");
|
|
lockAge = TimeSpan.Zero;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
actionFlags = (SessionStateActions) cmd.Parameters[5].Value;
|
|
|
|
if (buf == null) {
|
|
/* Get short item */
|
|
Debug.Assert(!Convert.IsDBNull(cmd.Parameters[1].Value), "!Convert.IsDBNull(cmd.Parameters[1].Value)");
|
|
Debug.Trace("SqlSessionStateStore", "Sql Get returned short item");
|
|
buf = (byte[]) cmd.Parameters[1].Value;
|
|
Debug.Assert(buf != null, "buf != null");
|
|
}
|
|
|
|
// Done with the connection.
|
|
DisposeOrReuseConnection(ref conn, usePooling);
|
|
|
|
using(stream = new MemoryStream(buf)) {
|
|
item = SessionStateUtility.DeserializeStoreData(context, stream, s_configCompressionEnabled);
|
|
_rqOrigStreamLen = (int) stream.Position;
|
|
}
|
|
return item;
|
|
}
|
|
finally {
|
|
DisposeOrReuseConnection(ref conn, usePooling);
|
|
}
|
|
}
|
|
|
|
public override SessionStateStoreData GetItem(HttpContext context,
|
|
String id,
|
|
out bool locked,
|
|
out TimeSpan lockAge,
|
|
out object lockId,
|
|
out SessionStateActions actionFlags) {
|
|
Debug.Trace("SqlSessionStateStore", "Calling Sql Get, id=" + id);
|
|
|
|
SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
|
|
return DoGet(context, id, false, out locked, out lockAge, out lockId, out actionFlags);
|
|
}
|
|
|
|
public override SessionStateStoreData GetItemExclusive(HttpContext context,
|
|
String id,
|
|
out bool locked,
|
|
out TimeSpan lockAge,
|
|
out object lockId,
|
|
out SessionStateActions actionFlags) {
|
|
Debug.Trace("SqlSessionStateStore", "Calling Sql GetExclusive, id=" + id);
|
|
|
|
SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
|
|
return DoGet(context, id, true, out locked, out lockAge, out lockId, out actionFlags);
|
|
}
|
|
|
|
|
|
public override void ReleaseItemExclusive(HttpContext context,
|
|
String id,
|
|
object lockId) {
|
|
Debug.Trace("SqlSessionStateStore", "Calling Sql ReleaseExclusive, id=" + id);
|
|
Debug.Assert(lockId != null, "lockId != null");
|
|
Debug.Assert(context != null, "context != null");
|
|
|
|
bool usePooling = true;
|
|
SqlStateConnection conn = null;
|
|
int lockCookie = (int)lockId;
|
|
|
|
try {
|
|
SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
|
|
|
|
conn = GetConnection(id, ref usePooling);
|
|
SqlCommand cmd = conn.TempReleaseExclusive;
|
|
cmd.Parameters[0].Value = id + _partitionInfo.AppSuffix;
|
|
cmd.Parameters[1].Value = lockCookie;
|
|
SqlExecuteNonQueryWithRetry(cmd, false, null);
|
|
|
|
}
|
|
finally {
|
|
DisposeOrReuseConnection(ref conn, usePooling);
|
|
}
|
|
}
|
|
|
|
public override void SetAndReleaseItemExclusive(HttpContext context,
|
|
String id,
|
|
SessionStateStoreData item,
|
|
object lockId,
|
|
bool newItem) {
|
|
byte [] buf;
|
|
int length;
|
|
SqlCommand cmd;
|
|
bool usePooling = true;
|
|
SqlStateConnection conn = null;
|
|
int lockCookie;
|
|
|
|
Debug.Assert(context != null, "context != null");
|
|
|
|
try {
|
|
Debug.Trace("SqlSessionStateStore", "Calling Sql Set, id=" + id);
|
|
|
|
Debug.Assert(item.Items != null, "item.Items != null");
|
|
Debug.Assert(item.StaticObjects != null, "item.StaticObjects != null");
|
|
|
|
SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
|
|
|
|
try {
|
|
SessionStateUtility.SerializeStoreData(item, ITEM_SHORT_LENGTH, out buf, out length, s_configCompressionEnabled);
|
|
}
|
|
catch {
|
|
if (!newItem) {
|
|
((SessionStateStoreProviderBase)this).ReleaseItemExclusive(context, id, lockId);
|
|
}
|
|
throw;
|
|
}
|
|
|
|
// Save it to the store
|
|
|
|
if (lockId == null) {
|
|
lockCookie = 0;
|
|
}
|
|
else {
|
|
lockCookie = (int)lockId;
|
|
}
|
|
|
|
conn = GetConnection(id, ref usePooling);
|
|
|
|
if (!newItem) {
|
|
Debug.Assert(_rqOrigStreamLen > 0, "_rqOrigStreamLen > 0");
|
|
if (length <= ITEM_SHORT_LENGTH) {
|
|
if (_rqOrigStreamLen <= ITEM_SHORT_LENGTH) {
|
|
cmd = conn.TempUpdateShort;
|
|
}
|
|
else {
|
|
cmd = conn.TempUpdateShortNullLong;
|
|
}
|
|
}
|
|
else {
|
|
if (_rqOrigStreamLen <= ITEM_SHORT_LENGTH) {
|
|
cmd = conn.TempUpdateLongNullShort;
|
|
}
|
|
else {
|
|
cmd = conn.TempUpdateLong;
|
|
}
|
|
}
|
|
|
|
}
|
|
else {
|
|
if (length <= ITEM_SHORT_LENGTH) {
|
|
cmd = conn.TempInsertShort;
|
|
}
|
|
else {
|
|
cmd = conn.TempInsertLong;
|
|
}
|
|
}
|
|
|
|
cmd.Parameters[0].Value = id + _partitionInfo.AppSuffix;
|
|
cmd.Parameters[1].Size = length;
|
|
cmd.Parameters[1].Value = buf;
|
|
cmd.Parameters[2].Value = item.Timeout;
|
|
if (!newItem) {
|
|
cmd.Parameters[3].Value = lockCookie;
|
|
}
|
|
SqlExecuteNonQueryWithRetry(cmd, newItem, id);
|
|
}
|
|
finally {
|
|
DisposeOrReuseConnection(ref conn, usePooling);
|
|
}
|
|
}
|
|
|
|
public override void RemoveItem(HttpContext context,
|
|
String id,
|
|
object lockId,
|
|
SessionStateStoreData item) {
|
|
Debug.Trace("SqlSessionStateStore", "Calling Sql Remove, id=" + id);
|
|
Debug.Assert(lockId != null, "lockId != null");
|
|
Debug.Assert(context != null, "context != null");
|
|
|
|
bool usePooling = true;
|
|
SqlStateConnection conn = null;
|
|
int lockCookie = (int)lockId;
|
|
|
|
try {
|
|
SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
|
|
|
|
conn = GetConnection(id, ref usePooling);
|
|
SqlCommand cmd = conn.TempRemove;
|
|
cmd.Parameters[0].Value = id + _partitionInfo.AppSuffix;
|
|
cmd.Parameters[1].Value = lockCookie;
|
|
SqlExecuteNonQueryWithRetry(cmd, false, null);
|
|
}
|
|
finally {
|
|
DisposeOrReuseConnection(ref conn, usePooling);
|
|
}
|
|
}
|
|
|
|
public override void ResetItemTimeout(HttpContext context, String id) {
|
|
Debug.Trace("SqlSessionStateStore", "Calling Sql ResetTimeout, id=" + id);
|
|
Debug.Assert(context != null, "context != null");
|
|
|
|
bool usePooling = true;
|
|
SqlStateConnection conn = null;
|
|
|
|
try {
|
|
SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
|
|
|
|
conn = GetConnection(id, ref usePooling);
|
|
SqlCommand cmd = conn.TempResetTimeout;
|
|
cmd.Parameters[0].Value = id + _partitionInfo.AppSuffix;
|
|
SqlExecuteNonQueryWithRetry(cmd, false, null);
|
|
}
|
|
finally {
|
|
DisposeOrReuseConnection(ref conn, usePooling);
|
|
}
|
|
}
|
|
|
|
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
|
|
{
|
|
Debug.Assert(context != null, "context != null");
|
|
return SessionStateUtility.CreateLegitStoreData(context, null, null, timeout);
|
|
}
|
|
|
|
public override void CreateUninitializedItem(HttpContext context, String id, int timeout) {
|
|
Debug.Trace("SqlSessionStateStore", "Calling Sql InsertUninitializedItem, id=" + id);
|
|
Debug.Assert(context != null, "context != null");
|
|
|
|
bool usePooling = true;
|
|
SqlStateConnection conn = null;
|
|
byte [] buf;
|
|
int length;
|
|
|
|
try {
|
|
SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
|
|
|
|
// Store an empty data
|
|
SessionStateUtility.SerializeStoreData(CreateNewStoreData(context, timeout),
|
|
ITEM_SHORT_LENGTH, out buf, out length, s_configCompressionEnabled);
|
|
|
|
conn = GetConnection(id, ref usePooling);
|
|
|
|
SqlCommand cmd = conn.TempInsertUninitializedItem;
|
|
cmd.Parameters[0].Value = id + _partitionInfo.AppSuffix;
|
|
cmd.Parameters[1].Size = length;
|
|
cmd.Parameters[1].Value = buf;
|
|
cmd.Parameters[2].Value = timeout;
|
|
SqlExecuteNonQueryWithRetry(cmd, true, id);
|
|
}
|
|
finally {
|
|
DisposeOrReuseConnection(ref conn, usePooling);
|
|
}
|
|
}
|
|
|
|
static bool IsInsertPKException(SqlException ex, bool ignoreInsertPKException, string id) {
|
|
// If the severity is greater than 20, we have a serious error.
|
|
// The server usually closes the connection in these cases.
|
|
if (ex != null &&
|
|
ex.Number == SQL_ERROR_PRIMARY_KEY_VIOLATION &&
|
|
ignoreInsertPKException) {
|
|
|
|
Debug.Trace("SessionStateClientSet",
|
|
"Insert failed because of primary key violation; just leave gracefully; id=" + id);
|
|
|
|
// It's possible that two threads (from the same session) are creating the session
|
|
// state, both failed to get it first, and now both tried to insert it.
|
|
// One thread may lose with a Primary Key Violation error. If so, that thread will
|
|
// just lose and exit gracefully.
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool IsFatalSqlException(SqlException ex) {
|
|
// We will retry sql operations for serious errors.
|
|
// We consider fatal exceptions any error with severity >= 20.
|
|
// In this case, the SQL server closes the connection.
|
|
//
|
|
|
|
|
|
|
|
if(ex != null &&
|
|
(ex.Class >= 20 ||
|
|
ex.Number == SQL_CANNOT_OPEN_DATABASE_FOR_LOGIN ||
|
|
ex.Number == SQL_TIMEOUT_EXPIRED)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void ClearFlagForClearPoolInProgress() {
|
|
// clear s_isClearPoolInProgress if it was set
|
|
Interlocked.CompareExchange(ref s_isClearPoolInProgress, 0, 1);
|
|
}
|
|
|
|
static bool CanRetry(SqlException ex, SqlConnection conn,
|
|
ref bool isFirstAttempt, ref DateTime endRetryTime) {
|
|
if (s_retryInterval.Seconds <= 0) {
|
|
// no retry policy set
|
|
return false;
|
|
}
|
|
if (!IsFatalSqlException(ex)) {
|
|
if (!isFirstAttempt) {
|
|
ClearFlagForClearPoolInProgress();
|
|
}
|
|
return false;
|
|
}
|
|
if (isFirstAttempt) {
|
|
// check if someone has called ClearPool for this connection string
|
|
// s_isClearPoolInProgress can be:
|
|
// 0 - no one called ClearPool;
|
|
// 1 - ClearPool is in progress or has already been called
|
|
|
|
// If no one called ClearPool (s_isClearPoolInProgress = 0), then
|
|
// make s_isClearPoolInProgress 1 and call clear pool
|
|
if (0 == Interlocked.CompareExchange(ref s_isClearPoolInProgress, 1, 0)) {
|
|
Debug.Trace("SqlSessionStateStore", "CanRetry: Call ClearPool to destroy the corrupted connections in the pool");
|
|
SqlConnection.ClearPool(conn);
|
|
}
|
|
|
|
// First time we sleep longer than for subsequent retries.
|
|
Thread.Sleep(FIRST_RETRY_SLEEP_TIME);
|
|
endRetryTime = DateTime.UtcNow.Add(s_retryInterval);
|
|
|
|
isFirstAttempt = false;
|
|
return true;
|
|
}
|
|
if (DateTime.UtcNow > endRetryTime) {
|
|
// the specified retry interval is up, we can't retry anymore
|
|
if (!isFirstAttempt) {
|
|
ClearFlagForClearPoolInProgress();
|
|
}
|
|
return false;
|
|
}
|
|
// sleep the specified time and allow retry
|
|
Thread.Sleep(RETRY_SLEEP_TIME);
|
|
return true;
|
|
}
|
|
|
|
static int SqlExecuteNonQueryWithRetry(SqlCommand cmd, bool ignoreInsertPKException, string id) {
|
|
bool isFirstAttempt = true;
|
|
DateTime endRetryTime = DateTime.UtcNow;
|
|
|
|
while(true) {
|
|
try {
|
|
if (cmd.Connection.State != ConnectionState.Open) {
|
|
// reopen the connection
|
|
// (gets closed if a previous operation throwed a SQL exception with severity >= 20)
|
|
cmd.Connection.Open();
|
|
}
|
|
int result = cmd.ExecuteNonQuery();
|
|
// the operation succeeded
|
|
// If we retried, it's possible ClearPool has been called.
|
|
// In this case, we clear the flag that shows ClearPool is in progress.
|
|
if (!isFirstAttempt) {
|
|
ClearFlagForClearPoolInProgress();
|
|
}
|
|
return result;
|
|
}
|
|
catch (SqlException e) {
|
|
// if specified, ignore primary key violations
|
|
if (IsInsertPKException(e, ignoreInsertPKException, id)) {
|
|
// ignoreInsertPKException = insert && newItem
|
|
return -1;
|
|
}
|
|
|
|
if (!CanRetry(e, cmd.Connection, ref isFirstAttempt, ref endRetryTime)) {
|
|
// just throw, because not all conditions to retry are satisfied
|
|
ThrowSqlConnectionException(cmd.Connection, e);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
// just throw, we have a different Exception
|
|
ThrowSqlConnectionException(cmd.Connection, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
static SqlDataReader SqlExecuteReaderWithRetry(SqlCommand cmd, CommandBehavior cmdBehavior) {
|
|
bool isFirstAttempt = true;
|
|
DateTime endRetryTime = DateTime.UtcNow;
|
|
|
|
while(true) {
|
|
try {
|
|
if (cmd.Connection.State != ConnectionState.Open) {
|
|
// reopen the connection
|
|
// (gets closed if a previous operation throwed a SQL exception with severity >= 20)
|
|
cmd.Connection.Open();
|
|
}
|
|
SqlDataReader reader = cmd.ExecuteReader(cmdBehavior);
|
|
// the operation succeeded
|
|
if (!isFirstAttempt) {
|
|
ClearFlagForClearPoolInProgress();
|
|
}
|
|
return reader;
|
|
}
|
|
catch (SqlException e) {
|
|
if (!CanRetry(e, cmd.Connection, ref isFirstAttempt, ref endRetryTime)) {
|
|
// just throw, default to previous behavior
|
|
ThrowSqlConnectionException(cmd.Connection, e);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
// just throw, we have a different Exception
|
|
ThrowSqlConnectionException(cmd.Connection, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal class SqlPartitionInfo : PartitionInfo {
|
|
bool _useIntegratedSecurity;
|
|
string _sqlConnectionString;
|
|
string _tracingPartitionString;
|
|
SupportFlags _support = SupportFlags.Uninitialized;
|
|
string _appSuffix;
|
|
object _lock = new object();
|
|
bool _sqlInfoInited;
|
|
|
|
const string APP_SUFFIX_FORMAT = "x8";
|
|
const int APPID_MAX = 280;
|
|
const int SQL_2000_MAJ_VER = 8;
|
|
|
|
internal SqlPartitionInfo(ResourcePool rpool, bool useIntegratedSecurity, string sqlConnectionString)
|
|
: base(rpool) {
|
|
_useIntegratedSecurity = useIntegratedSecurity;
|
|
_sqlConnectionString = sqlConnectionString;
|
|
Debug.Trace("PartitionInfo", "Created a new info, sqlConnectionString=" + sqlConnectionString);
|
|
}
|
|
|
|
internal bool UseIntegratedSecurity {
|
|
get { return _useIntegratedSecurity; }
|
|
}
|
|
|
|
internal string SqlConnectionString {
|
|
get { return _sqlConnectionString; }
|
|
}
|
|
|
|
internal SupportFlags SupportFlags {
|
|
get { return _support; }
|
|
set { _support = value; }
|
|
}
|
|
|
|
protected override string TracingPartitionString {
|
|
get {
|
|
if (_tracingPartitionString == null) {
|
|
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(_sqlConnectionString);
|
|
builder.Password = String.Empty;
|
|
builder.UserID = String.Empty;
|
|
_tracingPartitionString = builder.ConnectionString;
|
|
}
|
|
return _tracingPartitionString;
|
|
}
|
|
}
|
|
|
|
internal string AppSuffix {
|
|
get { return _appSuffix; }
|
|
}
|
|
|
|
void GetServerSupportOptions(SqlConnection sqlConnection) {
|
|
Debug.Assert(SupportFlags == SupportFlags.Uninitialized);
|
|
|
|
SqlCommand cmd;
|
|
SqlDataReader reader = null;
|
|
SupportFlags flags = SupportFlags.None;
|
|
bool v2 = false;
|
|
SqlParameter p;
|
|
|
|
// First, check if the SQL server is running Whidbey scripts
|
|
cmd = new SqlCommand("Select name from sysobjects where type = 'P' and name = 'TempGetVersion'", sqlConnection);
|
|
cmd.CommandType = CommandType.Text;
|
|
|
|
using(reader = SqlExecuteReaderWithRetry(cmd, CommandBehavior.SingleRow)) {
|
|
if (reader.Read()) {
|
|
// This function first appears in Whidbey (v2). So we know it's
|
|
// at least 2.0 even without reading its content.
|
|
v2 = true;
|
|
}
|
|
}
|
|
|
|
if (!v2) {
|
|
|
|
if (s_usePartition) {
|
|
throw new HttpException(
|
|
SR.GetString(SR.Need_v2_SQL_Server_partition_resolver,
|
|
s_configPartitionResolverType, sqlConnection.DataSource, sqlConnection.Database));
|
|
}
|
|
else {
|
|
throw new HttpException(
|
|
SR.GetString(SR.Need_v2_SQL_Server));
|
|
}
|
|
}
|
|
|
|
// Then, see if it's SQL 2000 or above
|
|
|
|
cmd = new SqlCommand("dbo.GetMajorVersion", sqlConnection);
|
|
cmd.CommandType = CommandType.StoredProcedure;
|
|
p = cmd.Parameters.Add(new SqlParameter("@@ver", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
|
|
SqlExecuteNonQueryWithRetry(cmd, false, null);
|
|
try {
|
|
if ((int)p.Value >= SQL_2000_MAJ_VER) {
|
|
// For details, see the extensive doc in DoGet method.
|
|
flags |= SupportFlags.GetLockAge;
|
|
}
|
|
|
|
Debug.Trace("PartitionInfo", "SupportFlags initialized to " + flags);
|
|
|
|
SupportFlags = flags;
|
|
}
|
|
catch (Exception e) {
|
|
SqlSessionStateStore.ThrowSqlConnectionException(sqlConnection, e);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
internal void InitSqlInfo(SqlConnection sqlConnection) {
|
|
if (_sqlInfoInited) {
|
|
return;
|
|
}
|
|
|
|
lock (_lock) {
|
|
if (_sqlInfoInited) {
|
|
return;
|
|
}
|
|
|
|
GetServerSupportOptions(sqlConnection);
|
|
|
|
// Get AppSuffix info
|
|
|
|
SqlParameter p;
|
|
|
|
SqlCommand cmdTempGetAppId = new SqlCommand("dbo.TempGetAppID", sqlConnection);
|
|
cmdTempGetAppId.CommandType = CommandType.StoredProcedure;
|
|
cmdTempGetAppId.CommandTimeout = s_commandTimeout;
|
|
|
|
// AppDomainAppId will contain the whole metabase path of the request's app
|
|
// e.g. /lm/w3svc/1/root/fxtest
|
|
p = cmdTempGetAppId.Parameters.Add(new SqlParameter("@appName", SqlDbType.VarChar, APPID_MAX));
|
|
p.Value = HttpRuntime.AppDomainAppId;
|
|
|
|
p = cmdTempGetAppId.Parameters.Add(new SqlParameter("@appId", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
p.Value = Convert.DBNull;
|
|
|
|
cmdTempGetAppId.ExecuteNonQuery();
|
|
Debug.Assert(!Convert.IsDBNull(p), "!Convert.IsDBNull(p)");
|
|
int appId = (int) p.Value;
|
|
_appSuffix = (appId).ToString(APP_SUFFIX_FORMAT, CultureInfo.InvariantCulture);
|
|
|
|
_sqlInfoInited = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
Here are all the sprocs created for session state and how they're used:
|
|
|
|
CreateTempTables
|
|
- Called during setup
|
|
|
|
DeleteExpiredSessions
|
|
- Called by SQL agent to remove expired sessions
|
|
|
|
GetHashCode
|
|
- Called by sproc TempGetAppID
|
|
|
|
GetMajorVersion
|
|
- Called during setup
|
|
|
|
TempGetAppID
|
|
- Called when an asp.net application starts up
|
|
|
|
TempGetStateItem
|
|
- Used for ReadOnly session state
|
|
- Called by v1 asp.net
|
|
- Called by v1.1 asp.net against SQL 7
|
|
|
|
TempGetStateItem2
|
|
- Used for ReadOnly session state
|
|
- Called by v1.1 asp.net against SQL 2000
|
|
|
|
TempGetStateItem3
|
|
- Used for ReadOnly session state
|
|
- Called by v2 asp.net
|
|
|
|
TempGetStateItemExclusive
|
|
- Called by v1 asp.net
|
|
- Called by v1.1 asp.net against SQL 7
|
|
|
|
TempGetStateItemExclusive2
|
|
- Called by v1.1 asp.net against SQL 2000
|
|
|
|
TempGetStateItemExclusive3
|
|
- Called by v2 asp.net
|
|
|
|
TempGetVersion
|
|
- Called by v2 asp.net when an application starts up
|
|
|
|
TempInsertStateItemLong
|
|
- Used when creating a new session state with size > 7000 bytes
|
|
|
|
TempInsertStateItemShort
|
|
- Used when creating a new session state with size <= 7000 bytes
|
|
|
|
TempInsertUninitializedItem
|
|
- Used when creating a new uninitilized session state (cookieless="true" and regenerateExpiredSessionId="true" in config)
|
|
|
|
TempReleaseStateItemExclusive
|
|
- Used when a request that has acquired the session state (exclusively) hit an error during the page execution
|
|
|
|
TempRemoveStateItem
|
|
- Used when a session is abandoned
|
|
|
|
TempResetTimeout
|
|
- Used when a request (with an active session state) is handled by an HttpHandler which doesn't support IRequiresSessionState interface.
|
|
|
|
TempUpdateStateItemLong
|
|
- Used when updating a session state with size > 7000 bytes
|
|
|
|
TempUpdateStateItemLongNullShort
|
|
- Used when updating a session state where original size <= 7000 bytes but new size > 7000 bytes
|
|
|
|
TempUpdateStateItemShort
|
|
- Used when updating a session state with size <= 7000 bytes
|
|
|
|
TempUpdateStateItemShortNullLong
|
|
- Used when updating a session state where original size > 7000 bytes but new size <= 7000 bytes
|
|
|
|
*/
|
|
class SqlStateConnection : IDisposable {
|
|
SqlConnection _sqlConnection;
|
|
SqlCommand _cmdTempGet;
|
|
SqlCommand _cmdTempGetExclusive;
|
|
SqlCommand _cmdTempReleaseExclusive;
|
|
SqlCommand _cmdTempInsertShort;
|
|
SqlCommand _cmdTempInsertLong;
|
|
SqlCommand _cmdTempUpdateShort;
|
|
SqlCommand _cmdTempUpdateShortNullLong;
|
|
SqlCommand _cmdTempUpdateLong;
|
|
SqlCommand _cmdTempUpdateLongNullShort;
|
|
SqlCommand _cmdTempRemove;
|
|
SqlCommand _cmdTempResetTimeout;
|
|
SqlCommand _cmdTempInsertUninitializedItem;
|
|
|
|
SqlPartitionInfo _partitionInfo;
|
|
|
|
internal SqlStateConnection(SqlPartitionInfo sqlPartitionInfo, TimeSpan retryInterval) {
|
|
Debug.Trace("SessionStateConnectionIdentity", "Connecting under " + WindowsIdentity.GetCurrent().Name);
|
|
|
|
_partitionInfo = sqlPartitionInfo;
|
|
_sqlConnection = new SqlConnection(sqlPartitionInfo.SqlConnectionString);
|
|
|
|
bool isFirstAttempt = true;
|
|
DateTime endRetryTime = DateTime.UtcNow;
|
|
|
|
while(true) {
|
|
try {
|
|
_sqlConnection.Open();
|
|
// the operation succeeded, exit the loop
|
|
if(!isFirstAttempt) {
|
|
ClearFlagForClearPoolInProgress();
|
|
}
|
|
break;
|
|
}
|
|
catch (SqlException e) {
|
|
if (e != null &&
|
|
(e.Number == SQL_LOGIN_FAILED ||
|
|
e.Number == SQL_LOGIN_FAILED_2 ||
|
|
e.Number == SQL_LOGIN_FAILED_3))
|
|
{
|
|
string user;
|
|
|
|
SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(sqlPartitionInfo.SqlConnectionString);
|
|
if (scsb.IntegratedSecurity) {
|
|
user = WindowsIdentity.GetCurrent().Name;
|
|
}
|
|
else {
|
|
user = scsb.UserID;
|
|
}
|
|
|
|
HttpException outerException = new HttpException(
|
|
SR.GetString(SR.Login_failed_sql_session_database, user ), e);
|
|
|
|
outerException.SetFormatter(new UseLastUnhandledErrorFormatter(outerException));
|
|
ClearConnectionAndThrow(outerException);
|
|
}
|
|
if (!CanRetry(e, _sqlConnection, ref isFirstAttempt, ref endRetryTime))
|
|
{
|
|
// just throw, the retry conditions are not satisfied
|
|
ClearConnectionAndThrow(e);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
// just throw, we have a different Exception
|
|
ClearConnectionAndThrow(e);
|
|
}
|
|
}
|
|
|
|
try {
|
|
_partitionInfo.InitSqlInfo(_sqlConnection);
|
|
Debug.Assert(sqlPartitionInfo.SupportFlags != SupportFlags.Uninitialized);
|
|
|
|
PerfCounters.IncrementCounter(AppPerfCounter.SESSION_SQL_SERVER_CONNECTIONS);
|
|
}
|
|
catch {
|
|
Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void ClearConnectionAndThrow(Exception e) {
|
|
SqlConnection connection = _sqlConnection;
|
|
_sqlConnection = null;
|
|
ThrowSqlConnectionException(connection, e);
|
|
}
|
|
|
|
internal void ClearAllParameters() {
|
|
ClearAllParameters(_cmdTempGet);
|
|
ClearAllParameters(_cmdTempGetExclusive);
|
|
ClearAllParameters(_cmdTempReleaseExclusive);
|
|
ClearAllParameters(_cmdTempInsertShort);
|
|
ClearAllParameters(_cmdTempInsertLong);
|
|
ClearAllParameters(_cmdTempUpdateShort);
|
|
ClearAllParameters(_cmdTempUpdateShortNullLong);
|
|
ClearAllParameters(_cmdTempUpdateLong);
|
|
ClearAllParameters(_cmdTempUpdateLongNullShort);
|
|
ClearAllParameters(_cmdTempRemove);
|
|
ClearAllParameters(_cmdTempResetTimeout);
|
|
ClearAllParameters(_cmdTempInsertUninitializedItem);
|
|
}
|
|
|
|
internal void ClearAllParameters(SqlCommand cmd) {
|
|
if (cmd == null) {
|
|
return;
|
|
}
|
|
|
|
foreach (SqlParameter param in cmd.Parameters) {
|
|
param.Value = Convert.DBNull;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempGet {
|
|
get {
|
|
if (_cmdTempGet == null) {
|
|
SqlParameter p;
|
|
|
|
_cmdTempGet = new SqlCommand("dbo.TempGetStateItem3", _sqlConnection);
|
|
_cmdTempGet.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempGet.CommandTimeout = s_commandTimeout;
|
|
|
|
// Use a different set of parameters for the sprocs that support GetLockAge
|
|
if ((_partitionInfo.SupportFlags & SupportFlags.GetLockAge) != 0) {
|
|
_cmdTempGet.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@itemShort", SqlDbType.VarBinary, ITEM_SHORT_LENGTH));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@locked", SqlDbType.Bit));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@lockAge", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@actionFlags", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
}
|
|
else {
|
|
_cmdTempGet.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@itemShort", SqlDbType.VarBinary, ITEM_SHORT_LENGTH));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@locked", SqlDbType.Bit));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@lockDate", SqlDbType.DateTime));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGet.Parameters.Add(new SqlParameter("@actionFlags", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
}
|
|
}
|
|
|
|
return _cmdTempGet;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempGetExclusive {
|
|
get {
|
|
if (_cmdTempGetExclusive == null) {
|
|
SqlParameter p;
|
|
|
|
_cmdTempGetExclusive = new SqlCommand("dbo.TempGetStateItemExclusive3", _sqlConnection);
|
|
_cmdTempGetExclusive.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempGetExclusive.CommandTimeout = s_commandTimeout;
|
|
|
|
// Use a different set of parameters for the sprocs that support GetLockAge
|
|
if ((_partitionInfo.SupportFlags & SupportFlags.GetLockAge) != 0) {
|
|
_cmdTempGetExclusive.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@itemShort", SqlDbType.VarBinary, ITEM_SHORT_LENGTH));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@locked", SqlDbType.Bit));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@lockAge", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@actionFlags", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
}
|
|
else {
|
|
_cmdTempGetExclusive.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@itemShort", SqlDbType.VarBinary, ITEM_SHORT_LENGTH));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@locked", SqlDbType.Bit));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@lockDate", SqlDbType.DateTime));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
p = _cmdTempGetExclusive.Parameters.Add(new SqlParameter("@actionFlags", SqlDbType.Int));
|
|
p.Direction = ParameterDirection.Output;
|
|
}
|
|
}
|
|
|
|
return _cmdTempGetExclusive;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempReleaseExclusive {
|
|
get {
|
|
if (_cmdTempReleaseExclusive == null) {
|
|
/* ReleaseExlusive */
|
|
_cmdTempReleaseExclusive = new SqlCommand("dbo.TempReleaseStateItemExclusive", _sqlConnection);
|
|
_cmdTempReleaseExclusive.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempReleaseExclusive.CommandTimeout = s_commandTimeout;
|
|
_cmdTempReleaseExclusive.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempReleaseExclusive.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
}
|
|
|
|
return _cmdTempReleaseExclusive;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempInsertLong {
|
|
get {
|
|
if (_cmdTempInsertLong == null) {
|
|
_cmdTempInsertLong = new SqlCommand("dbo.TempInsertStateItemLong", _sqlConnection);
|
|
_cmdTempInsertLong.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempInsertLong.CommandTimeout = s_commandTimeout;
|
|
_cmdTempInsertLong.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempInsertLong.Parameters.Add(new SqlParameter("@itemLong", SqlDbType.Image, 8000));
|
|
_cmdTempInsertLong.Parameters.Add(new SqlParameter("@timeout", SqlDbType.Int));
|
|
}
|
|
|
|
return _cmdTempInsertLong;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempInsertShort {
|
|
get {
|
|
/* Insert */
|
|
if (_cmdTempInsertShort == null) {
|
|
_cmdTempInsertShort = new SqlCommand("dbo.TempInsertStateItemShort", _sqlConnection);
|
|
_cmdTempInsertShort.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempInsertShort.CommandTimeout = s_commandTimeout;
|
|
_cmdTempInsertShort.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempInsertShort.Parameters.Add(new SqlParameter("@itemShort", SqlDbType.VarBinary, ITEM_SHORT_LENGTH));
|
|
_cmdTempInsertShort.Parameters.Add(new SqlParameter("@timeout", SqlDbType.Int));
|
|
}
|
|
|
|
return _cmdTempInsertShort;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempUpdateLong {
|
|
get {
|
|
if (_cmdTempUpdateLong == null) {
|
|
_cmdTempUpdateLong = new SqlCommand("dbo.TempUpdateStateItemLong", _sqlConnection);
|
|
_cmdTempUpdateLong.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempUpdateLong.CommandTimeout = s_commandTimeout;
|
|
_cmdTempUpdateLong.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempUpdateLong.Parameters.Add(new SqlParameter("@itemLong", SqlDbType.Image, 8000));
|
|
_cmdTempUpdateLong.Parameters.Add(new SqlParameter("@timeout", SqlDbType.Int));
|
|
_cmdTempUpdateLong.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
}
|
|
|
|
return _cmdTempUpdateLong;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempUpdateShort {
|
|
get {
|
|
/* Update */
|
|
if (_cmdTempUpdateShort == null) {
|
|
_cmdTempUpdateShort = new SqlCommand("dbo.TempUpdateStateItemShort", _sqlConnection);
|
|
_cmdTempUpdateShort.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempUpdateShort.CommandTimeout = s_commandTimeout;
|
|
_cmdTempUpdateShort.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempUpdateShort.Parameters.Add(new SqlParameter("@itemShort", SqlDbType.VarBinary, ITEM_SHORT_LENGTH));
|
|
_cmdTempUpdateShort.Parameters.Add(new SqlParameter("@timeout", SqlDbType.Int));
|
|
_cmdTempUpdateShort.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
}
|
|
|
|
return _cmdTempUpdateShort;
|
|
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempUpdateShortNullLong {
|
|
get {
|
|
if (_cmdTempUpdateShortNullLong == null) {
|
|
_cmdTempUpdateShortNullLong = new SqlCommand("dbo.TempUpdateStateItemShortNullLong", _sqlConnection);
|
|
_cmdTempUpdateShortNullLong.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempUpdateShortNullLong.CommandTimeout = s_commandTimeout;
|
|
_cmdTempUpdateShortNullLong.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempUpdateShortNullLong.Parameters.Add(new SqlParameter("@itemShort", SqlDbType.VarBinary, ITEM_SHORT_LENGTH));
|
|
_cmdTempUpdateShortNullLong.Parameters.Add(new SqlParameter("@timeout", SqlDbType.Int));
|
|
_cmdTempUpdateShortNullLong.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
}
|
|
|
|
return _cmdTempUpdateShortNullLong;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempUpdateLongNullShort {
|
|
get {
|
|
if (_cmdTempUpdateLongNullShort == null) {
|
|
_cmdTempUpdateLongNullShort = new SqlCommand("dbo.TempUpdateStateItemLongNullShort", _sqlConnection);
|
|
_cmdTempUpdateLongNullShort.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempUpdateLongNullShort.CommandTimeout = s_commandTimeout;
|
|
_cmdTempUpdateLongNullShort.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempUpdateLongNullShort.Parameters.Add(new SqlParameter("@itemLong", SqlDbType.Image, 8000));
|
|
_cmdTempUpdateLongNullShort.Parameters.Add(new SqlParameter("@timeout", SqlDbType.Int));
|
|
_cmdTempUpdateLongNullShort.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
}
|
|
|
|
return _cmdTempUpdateLongNullShort;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempRemove {
|
|
get {
|
|
if (_cmdTempRemove == null) {
|
|
/* Remove */
|
|
_cmdTempRemove = new SqlCommand("dbo.TempRemoveStateItem", _sqlConnection);
|
|
_cmdTempRemove.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempRemove.CommandTimeout = s_commandTimeout;
|
|
_cmdTempRemove.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempRemove.Parameters.Add(new SqlParameter("@lockCookie", SqlDbType.Int));
|
|
|
|
}
|
|
|
|
return _cmdTempRemove;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempInsertUninitializedItem {
|
|
get {
|
|
if (_cmdTempInsertUninitializedItem == null) {
|
|
_cmdTempInsertUninitializedItem = new SqlCommand("dbo.TempInsertUninitializedItem", _sqlConnection);
|
|
_cmdTempInsertUninitializedItem.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempInsertUninitializedItem.CommandTimeout = s_commandTimeout;
|
|
_cmdTempInsertUninitializedItem.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
_cmdTempInsertUninitializedItem.Parameters.Add(new SqlParameter("@itemShort", SqlDbType.VarBinary, ITEM_SHORT_LENGTH));
|
|
_cmdTempInsertUninitializedItem.Parameters.Add(new SqlParameter("@timeout", SqlDbType.Int));
|
|
}
|
|
|
|
return _cmdTempInsertUninitializedItem;
|
|
}
|
|
}
|
|
|
|
internal SqlCommand TempResetTimeout {
|
|
get {
|
|
if (_cmdTempResetTimeout == null) {
|
|
/* ResetTimeout */
|
|
_cmdTempResetTimeout = new SqlCommand("dbo.TempResetTimeout", _sqlConnection);
|
|
_cmdTempResetTimeout.CommandType = CommandType.StoredProcedure;
|
|
_cmdTempResetTimeout.CommandTimeout = s_commandTimeout;
|
|
_cmdTempResetTimeout.Parameters.Add(new SqlParameter("@id", SqlDbType.NVarChar, ID_LENGTH));
|
|
}
|
|
|
|
return _cmdTempResetTimeout;
|
|
}
|
|
}
|
|
|
|
public void Dispose() {
|
|
Debug.Trace("ResourcePool", "Disposing SqlStateConnection");
|
|
if (_sqlConnection != null) {
|
|
_sqlConnection.Close();
|
|
_sqlConnection = null;
|
|
PerfCounters.DecrementCounter(AppPerfCounter.SESSION_SQL_SERVER_CONNECTIONS);
|
|
}
|
|
}
|
|
|
|
internal SqlConnection Connection {
|
|
get { return _sqlConnection; }
|
|
}
|
|
}
|
|
}
|
|
}
|