|
//------------------------------------------------------------------------------
// <copyright file="StateRuntime.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
* StateWebRuntime
*
* Copyright (c) 1998-1999, Microsoft Corporation
*
*/
namespace System.Web.SessionState {
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Threading;
using System.Web;
using System.Web.Caching;
using System.Web.Configuration;
using System.Web.Util;
/// <internalonly/>
/// <devdoc>
/// </devdoc>
[ComImport, Guid("7297744b-e188-40bf-b7e9-56698d25cf44"), System.Runtime.InteropServices.InterfaceTypeAttribute(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
public interface IStateRuntime {
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
[SecurityPermission(SecurityAction.InheritanceDemand, Unrestricted = true)]
void StopProcessing();
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
[SecurityPermission(SecurityAction.InheritanceDemand, Unrestricted = true)]
void ProcessRequest(
[In, MarshalAs(UnmanagedType.SysInt)]
IntPtr tracker,
[In, MarshalAs(UnmanagedType.I4)]
int verb,
[In, MarshalAs(UnmanagedType.LPWStr)]
string uri,
[In, MarshalAs(UnmanagedType.I4)]
int exclusive,
[In, MarshalAs(UnmanagedType.I4)]
int timeout,
[In, MarshalAs(UnmanagedType.I4)]
int lockCookieExists,
[In, MarshalAs(UnmanagedType.I4)]
int lockCookie,
[In, MarshalAs(UnmanagedType.I4)]
int contentLength,
[In, MarshalAs(UnmanagedType.SysInt)]
IntPtr content);
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
[SecurityPermission(SecurityAction.InheritanceDemand, Unrestricted = true)]
void ProcessRequest(
[In, MarshalAs(UnmanagedType.SysInt)]
IntPtr tracker,
[In, MarshalAs(UnmanagedType.I4)]
int verb,
[In, MarshalAs(UnmanagedType.LPWStr)]
string uri,
[In, MarshalAs(UnmanagedType.I4)]
int exclusive,
[In, MarshalAs(UnmanagedType.I4)]
int extraFlags,
[In, MarshalAs(UnmanagedType.I4)]
int timeout,
[In, MarshalAs(UnmanagedType.I4)]
int lockCookieExists,
[In, MarshalAs(UnmanagedType.I4)]
int lockCookie,
[In, MarshalAs(UnmanagedType.I4)]
int contentLength,
[In, MarshalAs(UnmanagedType.SysInt)]
IntPtr content);
}
/// <internalonly/>
/// <devdoc>
/// </devdoc>
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
public sealed class StateRuntime : IStateRuntime {
static StateRuntime() {
WebConfigurationFileMap webFileMap = new WebConfigurationFileMap();
UserMapPath mapPath = new UserMapPath(webFileMap);
HttpConfigurationSystem.EnsureInit(mapPath, false, true);
StateApplication app = new StateApplication();
HttpApplicationFactory.SetCustomApplication(app);
PerfCounters.OpenStateCounters();
ResetStateServerCounters();
}
/// <devdoc>
/// <para>
/// Initializes a new instance of the <see cref='System.Web.State.StateRuntime'/>
/// class.
/// </para>
/// </devdoc>
public StateRuntime() {
}
/*
* Shutdown runtime
*/
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void StopProcessing() {
ResetStateServerCounters();
HttpRuntime.Close();
}
static void ResetStateServerCounters() {
PerfCounters.SetStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_TOTAL, 0);
PerfCounters.SetStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_ACTIVE, 0);
PerfCounters.SetStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_TIMED_OUT, 0);
PerfCounters.SetStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_ABANDONED, 0);
}
public void ProcessRequest(
IntPtr tracker,
int verb,
string uri,
int exclusive,
int timeout,
int lockCookieExists,
int lockCookie,
int contentLength,
IntPtr content
) {
ProcessRequest(
tracker,
verb,
uri,
exclusive,
0,
timeout,
lockCookieExists,
lockCookie,
contentLength,
content);
}
/*
* Process one ISAPI request
*
* @param ecb ECB
*/
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void ProcessRequest(
IntPtr tracker,
int verb,
string uri,
int exclusive,
int extraFlags,
int timeout,
int lockCookieExists,
int lockCookie,
int contentLength,
IntPtr content
) {
StateHttpWorkerRequest wr;
wr = new StateHttpWorkerRequest(
tracker, (UnsafeNativeMethods.StateProtocolVerb) verb, uri,
(UnsafeNativeMethods.StateProtocolExclusive) exclusive, extraFlags, timeout,
lockCookieExists, lockCookie, contentLength, content);
HttpRuntime.ProcessRequest(wr);
}
}
internal static class StateHeaders {
internal const String EXCLUSIVE_NAME = "Http_Exclusive";
internal const String EXCLUSIVE_VALUE_ACQUIRE = "acquire";
internal const String EXCLUSIVE_VALUE_RELEASE = "release";
internal const String TIMEOUT_NAME = "Http_Timeout";
internal const String TIMEOUT_NAME_RAW = "Timeout";
internal const String LOCKCOOKIE_NAME = "Http_LockCookie";
internal const String LOCKCOOKIE_NAME_RAW = "LockCookie";
internal const String LOCKDATE_NAME = "Http_LockDate";
internal const String LOCKDATE_NAME_RAW = "LockDate";
internal const String LOCKAGE_NAME = "Http_LockAge";
internal const String LOCKAGE_NAME_RAW = "LockAge";
internal const String EXTRAFLAGS_NAME = "Http_ExtraFlags";
internal const String EXTRAFLAGS_NAME_RAW = "ExtraFlags";
internal const String ACTIONFLAGS_NAME = "Http_ActionFlags";
internal const String ACTIONFLAGS_NAME_RAW = "ActionFlags";
};
internal sealed class CachedContent {
internal byte[] _content;
internal IntPtr _stateItem; // The pointer to the native memory that points to the psi
internal bool _locked;
internal DateTime _utcLockDate;
internal TimeSpan _slidingExpiration;
internal int _lockCookie;
internal int _extraFlags;
#pragma warning disable 0649
internal ReadWriteSpinLock _spinLock;
#pragma warning restore 0649
internal CachedContent(
byte [] content,
IntPtr stateItem,
bool locked,
DateTime utcLockDate,
TimeSpan slidingExpiration,
int lockCookie,
int extraFlags) {
_content = content;
_stateItem = stateItem;
_locked = locked;
_utcLockDate = utcLockDate;
_slidingExpiration = slidingExpiration;
_lockCookie = lockCookie;
_extraFlags = extraFlags;
}
}
internal class StateApplication : IHttpHandler {
CacheItemRemovedCallback _removedHandler;
internal StateApplication() {
if (!HttpRuntime.IsFullTrust) {
// DevDiv #89021: This type passes user-supplied data to unmanaged code, so we need
// to ensure that it can only be used from within a FullTrust environment.
throw new InvalidOperationException(SR.GetString(SR.StateApplication_FullTrustOnly));
}
_removedHandler = new CacheItemRemovedCallback(this.OnCacheItemRemoved);
}
public void ProcessRequest(HttpContext context) {
// Don't send content-type header.
context.Response.ContentType = null;
switch (context.Request.HttpVerb) {
case HttpVerb.GET:
DoGet(context);
break;
case HttpVerb.PUT:
DoPut(context);
break;
case HttpVerb.HEAD:
DoHead(context);
break;
case HttpVerb.DELETE:
DoDelete(context);
break;
default:
DoUnknown(context);
break;
}
}
public bool IsReusable {
get { return true; }
}
private string CreateKey(HttpRequest request) {
return CacheInternal.PrefixStateApplication + HttpUtility.UrlDecode(request.RawUrl);
}
private void ReportInvalidHeader(HttpContext context, String header) {
HttpResponse response;
response = context.Response;
response.StatusCode = 400;
response.Write("<html><head><title>Bad Request</title></head>\r\n");
response.Write("<body><h1>Http/1.1 400 Bad Request</h1>");
response.Write("Invalid header <b>" + header + "</b></body></html>");
}
private void ReportLocked(HttpContext context, CachedContent content) {
HttpResponse response;
DateTime localLockDate;
long lockAge;
// Note that due to a bug in the RTM state server client,
// we cannot add to body of the response when sending this
// message, otherwise the client will leak memory.
response = context.Response;
response.StatusCode = 423;
localLockDate = DateTimeUtil.ConvertToLocalTime(content._utcLockDate);
lockAge = (DateTime.UtcNow - content._utcLockDate).Ticks / TimeSpan.TicksPerSecond;
response.AppendHeader(StateHeaders.LOCKDATE_NAME_RAW, localLockDate.Ticks.ToString(CultureInfo.InvariantCulture));
response.AppendHeader(StateHeaders.LOCKAGE_NAME_RAW, lockAge.ToString(CultureInfo.InvariantCulture));
response.AppendHeader(StateHeaders.LOCKCOOKIE_NAME_RAW, content._lockCookie.ToString(CultureInfo.InvariantCulture));
}
private void ReportActionFlags(HttpContext context, int flags) {
HttpResponse response;
// Note that due to a bug in the RTM state server client,
// we cannot add to body of the response when sending this
// message, otherwise the client will leak memory.
response = context.Response;
response.AppendHeader(StateHeaders.ACTIONFLAGS_NAME_RAW, flags.ToString(CultureInfo.InvariantCulture));
}
private void ReportNotFound(HttpContext context) {
context.Response.StatusCode = 404;
}
bool GetOptionalNonNegativeInt32HeaderValue(HttpContext context, string header, out int value)
{
bool headerValid;
string valueAsString;
value = -1;
valueAsString = context.Request.Headers[header];
if (valueAsString == null) {
headerValid = true;
}
else {
headerValid = false;
try {
value = Int32.Parse(valueAsString, CultureInfo.InvariantCulture);
if (value >= 0) {
headerValid = true;
}
}
catch {
}
}
if (!headerValid) {
ReportInvalidHeader(context, header);
}
return headerValid;
}
bool GetRequiredNonNegativeInt32HeaderValue(HttpContext context, string header, out int value)
{
bool headerValid = GetOptionalNonNegativeInt32HeaderValue(context, header, out value);
if (headerValid && value == -1) {
headerValid = false;
ReportInvalidHeader(context, header);
}
return headerValid;
}
bool GetOptionalInt32HeaderValue(HttpContext context, string header, out int value, out bool found)
{
bool headerValid;
string valueAsString;
found = false;
value = 0;
valueAsString = context.Request.Headers[header];
if (valueAsString == null) {
headerValid = true;
}
else {
headerValid = false;
try {
value = Int32.Parse(valueAsString, CultureInfo.InvariantCulture);
headerValid = true;
found = true;
}
catch {
}
}
if (!headerValid) {
ReportInvalidHeader(context, header);
}
return headerValid;
}
/*
* Check Exclusive header for get, getexlusive, releaseexclusive
* use the path as the id
* Create the cache key
* follow inproc.
*/
internal /*public*/ void DoGet(HttpContext context) {
HttpRequest request = context.Request;
HttpResponse response = context.Response;
Stream responseStream;
byte[] buf;
string exclusiveAccess;
string key;
CachedContent content;
int lockCookie;
int timeout;
key = CreateKey(request);
content = (CachedContent) HttpRuntime.Cache.InternalCache.Get(key);
if (content == null) {
ReportNotFound(context);
return;
}
exclusiveAccess = request.Headers[StateHeaders.EXCLUSIVE_NAME];
content._spinLock.AcquireWriterLock();
try {
if (content._content == null) {
ReportNotFound(context);
return;
}
int initialFlags;
initialFlags = content._extraFlags;
if ((initialFlags & (int)SessionStateItemFlags.Uninitialized) != 0) {
// It is an uninitialized item. We have to remove that flag.
// We only allow one request to do that.
// For details, see inline doc for SessionStateItemFlags.Uninitialized flag.
// If initialFlags != return value of CompareExchange, it means another request has
// removed the flag.
if (initialFlags == Interlocked.CompareExchange(
ref content._extraFlags,
initialFlags & (~((int)SessionStateItemFlags.Uninitialized)),
initialFlags)) {
ReportActionFlags(context, (int)SessionStateActions.InitializeItem);
}
}
if (exclusiveAccess == StateHeaders.EXCLUSIVE_VALUE_RELEASE) {
if (!GetRequiredNonNegativeInt32HeaderValue(context, StateHeaders.LOCKCOOKIE_NAME, out lockCookie))
return;
if (content._locked) {
if (lockCookie == content._lockCookie) {
content._locked = false;
}
else {
ReportLocked(context, content);
}
}
else {
// should be locked but isn't.
context.Response.StatusCode = 200;
}
}
else {
if (content._locked) {
ReportLocked(context, content);
return;
}
if (exclusiveAccess == StateHeaders.EXCLUSIVE_VALUE_ACQUIRE) {
content._locked = true;
content._utcLockDate = DateTime.UtcNow;
content._lockCookie++;
response.AppendHeader(StateHeaders.LOCKCOOKIE_NAME_RAW, (content._lockCookie).ToString(CultureInfo.InvariantCulture));
}
timeout = (int)(content._slidingExpiration.Ticks / TimeSpan.TicksPerMinute);
response.AppendHeader(StateHeaders.TIMEOUT_NAME_RAW, (timeout).ToString(CultureInfo.InvariantCulture));
responseStream = response.OutputStream;
buf = content._content;
responseStream.Write(buf, 0, buf.Length);
response.Flush();
}
}
finally {
content._spinLock.ReleaseWriterLock();
}
}
internal /*public*/ void DoPut(HttpContext context) {
IntPtr stateItemDelete;
stateItemDelete = FinishPut(context);
if (stateItemDelete != IntPtr.Zero) {
UnsafeNativeMethods.STWNDDeleteStateItem(stateItemDelete);
}
}
unsafe IntPtr FinishPut(HttpContext context) {
HttpRequest request = context.Request;
HttpResponse response = context.Response;
Stream requestStream;
byte[] buf;
int timeoutMinutes;
TimeSpan timeout;
int extraFlags;
string key;
CachedContent content;
CachedContent contentCurrent;
int lockCookie;
int lockCookieNew = 1;
IntPtr stateItem;
CacheStoreProvider cacheInternal = HttpRuntime.Cache.InternalCache;
/* create the content */
requestStream = request.InputStream;
int bufferSize = (int)(requestStream.Length - requestStream.Position);
buf = new byte[bufferSize];
requestStream.Read(buf, 0 , buf.Length);
fixed (byte * pBuf = buf) {
// The ctor of StateHttpWorkerRequest convert the native pointer address
// into an array of bytes, and in our we revert it back to an IntPtr
stateItem = (IntPtr)(*((void **)pBuf));
}
/* get headers */
if (!GetOptionalNonNegativeInt32HeaderValue(context, StateHeaders.TIMEOUT_NAME, out timeoutMinutes)) {
return stateItem;
}
if (timeoutMinutes == -1) {
timeoutMinutes = SessionStateModule.TIMEOUT_DEFAULT;
}
if (timeoutMinutes > SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES) {
ReportInvalidHeader(context, StateHeaders.TIMEOUT_NAME);
return stateItem;
}
timeout = new TimeSpan(0, timeoutMinutes, 0);
bool found;
if (!GetOptionalInt32HeaderValue(context, StateHeaders.EXTRAFLAGS_NAME, out extraFlags, out found)) {
return stateItem;
}
if (!found) {
extraFlags = 0;
}
/* lookup current value */
key = CreateKey(request);
contentCurrent = (CachedContent) cacheInternal.Get(key);
if (contentCurrent != null) {
// DevDivBugs 146875: Expired Session State race condition
// We make sure we do not overwrite an already existing item with an uninitialized item.
if (((int)SessionStateItemFlags.Uninitialized & extraFlags) == 1) {
return stateItem;
}
if (!GetOptionalNonNegativeInt32HeaderValue(context, StateHeaders.LOCKCOOKIE_NAME, out lockCookie)) {
return stateItem;
}
contentCurrent._spinLock.AcquireWriterLock();
try {
if (contentCurrent._content == null) {
ReportNotFound(context);
return stateItem;
}
/* Only set the item if we are the owner */
if (contentCurrent._locked && (lockCookie == -1 || lockCookie != contentCurrent._lockCookie)) {
ReportLocked(context, contentCurrent);
return stateItem;
}
if (contentCurrent._slidingExpiration == timeout && contentCurrent._content != null) {
/* delete the old state item */
IntPtr stateItemOld = contentCurrent._stateItem;
/* change the item in place */
contentCurrent._content = buf;
contentCurrent._stateItem = stateItem;
contentCurrent._locked = false;
return stateItemOld;
}
/*
The timeout has changed. In this case, we are removing the old item and
inserting a new one.
Update _extraFlags to ignore the cache item removed callback (this way,
we will not decrease the number of active sessions).
*/
contentCurrent._extraFlags |= (int)SessionStateItemFlags.IgnoreCacheItemRemoved;
/*
* If not locked, keep it locked until it is completely replaced.
* Prevent overwriting when we drop the lock.
*/
contentCurrent._locked = true;
contentCurrent._lockCookie = 0;
lockCookieNew = lockCookie;
}
finally {
contentCurrent._spinLock.ReleaseWriterLock();
}
}
content = new CachedContent(buf, stateItem, false, DateTime.MinValue, timeout, lockCookieNew, extraFlags);
cacheInternal.Insert(key, content, new CacheInsertOptions() {
SlidingExpiration = timeout,
Priority = CacheItemPriority.NotRemovable,
OnRemovedCallback = _removedHandler
});
if (contentCurrent == null) {
IncrementStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_TOTAL);
IncrementStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_ACTIVE);
}
return IntPtr.Zero;
}
internal /*public*/ void DoDelete(HttpContext context) {
string key = CreateKey(context.Request);
CacheStoreProvider cacheInternal = HttpRuntime.Cache.InternalCache;
CachedContent content = (CachedContent) cacheInternal.Get(key);
/* If the item isn't there, we probably took too long to run. */
if (content == null) {
ReportNotFound(context);
return;
}
int lockCookie;
if (!GetOptionalNonNegativeInt32HeaderValue(context, StateHeaders.LOCKCOOKIE_NAME, out lockCookie))
return;
content._spinLock.AcquireWriterLock();
try {
if (content._content == null) {
ReportNotFound(context);
return;
}
/* Only remove the item if we are the owner */
if (content._locked && (lockCookie == -1 || content._lockCookie != lockCookie)) {
ReportLocked(context, content);
return;
}
/*
* If not locked, keep it locked until it is completely removed.
* Prevent overwriting when we drop the lock.
*/
content._locked = true;
content._lockCookie = 0;
}
finally {
content._spinLock.ReleaseWriterLock();
}
cacheInternal.Remove(key);
}
internal /*public*/ void DoHead(HttpContext context) {
string key;
Object item;
key = CreateKey(context.Request);
item = HttpRuntime.Cache.InternalCache.Get(key);
if (item == null) {
ReportNotFound(context);
}
}
/*
* Unknown Http verb. Responds with "400 Bad Request".
* Override this method to report different Http code.
*/
internal /*public*/ void DoUnknown(HttpContext context) {
context.Response.StatusCode = 400;
}
unsafe void OnCacheItemRemoved(String key, Object value, CacheItemRemovedReason reason) {
CachedContent content;
IntPtr stateItem;
content = (CachedContent) value;
content._spinLock.AcquireWriterLock();
try {
stateItem = content._stateItem;
content._content = null;
content._stateItem = IntPtr.Zero;
}
finally {
content._spinLock.ReleaseWriterLock();
}
UnsafeNativeMethods.STWNDDeleteStateItem(stateItem);
/* If _extraFlags have IgnoreCacheItemRemoved specified,
don't update the counters.
*/
if ((content._extraFlags & (int)SessionStateItemFlags.IgnoreCacheItemRemoved) != 0) {
Debug.Trace("OnCacheItemRemoved", "OnCacheItemRemoved ignored (item removed, but counters not updated)");
return;
}
switch (reason) {
case CacheItemRemovedReason.Expired:
IncrementStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_TIMED_OUT);
break;
case CacheItemRemovedReason.Removed:
IncrementStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_ABANDONED);
break;
default:
break;
}
DecrementStateServiceCounter(StateServicePerfCounter.STATE_SERVICE_SESSIONS_ACTIVE);
}
private void DecrementStateServiceCounter(StateServicePerfCounter counter) {
if (HttpRuntime.ShutdownInProgress) {
return;
}
PerfCounters.DecrementStateServiceCounter(counter);
}
private void IncrementStateServiceCounter(StateServicePerfCounter counter) {
if (HttpRuntime.ShutdownInProgress) {
return;
}
PerfCounters.IncrementStateServiceCounter(counter);
}
}
}
|