|
//------------------------------------------------------------------------------
// <copyright file="RootedObjects.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web {
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Security.Principal;
using System.Web.Hosting;
using System.Web.Management;
using System.Web.Security;
using System.Web.Util;
// Used by the IIS integrated pipeline to reference managed objects so that they're not claimed by the GC while unmanaged code is executing.
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
internal sealed class RootedObjects : IPrincipalContainer {
// These two fields are for ETW scenarios. In .NET 4.5.1, the API SetCurrentThreadActivityId
// can be used to correlate operations back to a given activity ID, where [in our case] an
// activity ID corresponds to a single request. We need to ref count since we have multiple
// threads operating on (or destroying) the request at once, and we need to know when the
// managed request has actually finished. For example, we can't just release an activity ID
// inside of our Destroy method since Destroy might be called on a thread pool thread while
// some other managed thread is still unwinding inside of PipelineRuntime. When this counter
// hits zero, we can fully release the activity ID.
private readonly bool _activityIdTracingIsEnabled;
private readonly Guid _requestActivityId;
private int _requestActivityIdRefCount = 1;
private SubscriptionQueue<IDisposable> _pipelineCompletedQueue;
private GCHandle _handle;
private RootedObjects() {
_handle = GCHandle.Alloc(this);
Pointer = (IntPtr)_handle;
// Increment active request count as soon as possible to prevent
// shutdown of the appdomain while requests are in flight. It
// is decremented in Destroy().
HttpRuntime.IncrementActivePipelineCount();
// this is an instance field instead of a static field since ETW can be enabled at any time
_activityIdTracingIsEnabled = ActivityIdHelper.Instance != null && AspNetEventSource.Instance.IsEnabled();
if (_activityIdTracingIsEnabled) {
_requestActivityId = ActivityIdHelper.UnsafeCreateNewActivityId();
}
}
// The HttpContext associated with this request.
// May be null if this is a WebSocket request or if the request has completed.
public HttpContext HttpContext {
get;
set;
}
// The principal associated with this request.
// May be null if there is no associated principal or if the request has completed.
public IPrincipal Principal {
get;
set;
}
// A pointer that can be used (via FromPointer) to reference this RootedObjects instance.
public IntPtr Pointer {
get;
private set;
}
// The WebSocketPipeline that's associated with this request.
// May be null if this request won't be transitioned to a WebSocket request or if it has completed.
public WebSocketPipeline WebSocketPipeline {
get;
set;
}
// The worker request (IIS7+) associated with this request.
// May be null if the request has completed.
public IIS7WorkerRequest WorkerRequest {
get;
set;
}
// Using a static factory instead of a public constructor since there's a side effect.
// Namely, the new object will never be garbage collected unless Destroy() is called.
public static RootedObjects Create() {
return new RootedObjects();
}
// Fully releases all managed resources associated with this request, including
// the HttpContext, WebSocketContext, principal, worker request, etc.
public void Destroy() {
Debug.Trace("RootedObjects", "Destroy");
// 'isDestroying = true' means that we'll release the implicit 'this' ref in _requestActivityIdRefCount
using (WithinTraceBlock(isDestroying: true)) {
try {
ReleaseHttpContext();
ReleaseWebSocketPipeline();
ReleaseWorkerRequest();
ReleasePrincipal();
// need to raise OnPipelineCompleted outside of the ThreadContext so that HttpContext.Current, User, etc. are unavailable
RaiseOnPipelineCompleted();
PerfCounters.DecrementCounter(AppPerfCounter.REQUESTS_EXECUTING);
}
finally {
if (_handle.IsAllocated) {
_handle.Free();
}
Pointer = IntPtr.Zero;
HttpRuntime.DecrementActivePipelineCount();
AspNetEventSource.Instance.RequestCompleted();
}
}
}
// Analog of HttpContext.DisposeOnPipelineCompleted for the integrated pipeline
internal ISubscriptionToken DisposeOnPipelineCompleted(IDisposable target) {
return _pipelineCompletedQueue.Enqueue(target);
}
public static RootedObjects FromPointer(IntPtr pointer) {
GCHandle handle = (GCHandle)pointer;
return (RootedObjects)handle.Target;
}
internal void RaiseOnPipelineCompleted() {
// The callbacks really shouldn't throw exceptions, but we have a catch block just in case.
// Since there's nobody else that can listen for these errors (the request is unwinding and
// user code will no longer run), we'll just log the error.
try {
_pipelineCompletedQueue.FireAndComplete(disposable => disposable.Dispose());
}
catch (Exception e) {
WebBaseEvent.RaiseRuntimeError(e, null);
}
}
// Fully releases the HttpContext instance associated with this request.
public void ReleaseHttpContext() {
Debug.Trace("RootedObjects", "ReleaseHttpContext");
if (HttpContext != null) {
HttpContext.FinishPipelineRequest();
}
HttpContext = null;
}
// Disposes of the principal associated with this request.
public void ReleasePrincipal() {
Debug.Trace("RootedObjects", "ReleasePrincipal");
if (Principal != null && Principal != WindowsAuthenticationModule.AnonymousPrincipal) {
WindowsIdentity identity = Principal.Identity as WindowsIdentity; // original code only disposed of WindowsIdentity, not arbitrary IDisposable types
if (identity != null) {
Principal = null;
identity.Dispose();
}
}
// Fix Bug 640366: Setting the Principal to null (irrespective of Identity)
// only if framework version is above .NetFramework 4.5 as this change is new and
// we want to keep the functionality same for previous versions.
if (BinaryCompatibility.Current.TargetsAtLeastFramework45) {
Principal = null;
}
}
// Disposes of the WebSocketPipeline instance associated with this request.
public void ReleaseWebSocketPipeline() {
Debug.Trace("RootedObjects", "ReleaseWebSocketContext");
if (WebSocketPipeline != null) {
WebSocketPipeline.Dispose();
}
WebSocketPipeline = null;
}
// Disposes of the worker request associated with this request.
public void ReleaseWorkerRequest() {
Debug.Trace("RootedObjects", "ReleaseWorkerRequest");
if (WorkerRequest != null) {
WorkerRequest.Dispose();
}
WorkerRequest = null;
}
// Sets up the ETW Activity ID on the current thread; caller should be:
// using (rootedObjects.WithinTraceBlock()) {
// .. something that might require tracing ..
// }
//
// This is designed to be *very* cheap if tracing isn't enabled.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ActivityIdToken WithinTraceBlock() {
return WithinTraceBlock(isDestroying: false);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ActivityIdToken WithinTraceBlock(bool isDestroying) {
if (_activityIdTracingIsEnabled) {
return new ActivityIdToken(this, isDestroying);
}
else {
return default(ActivityIdToken);
}
}
// Called once per request; emits an ETW event saying that we transitioned from IIS -> ASP.NET
public void WriteTransferEventIfNecessary() {
Debug.Assert(WorkerRequest != null);
if (_activityIdTracingIsEnabled) {
Debug.Assert(_requestActivityId != Guid.Empty);
AspNetEventSource.Instance.RequestEnteredAspNetPipeline(WorkerRequest, _requestActivityId);
}
}
internal struct ActivityIdToken : IDisposable {
private readonly bool _isDestroying;
private readonly Guid _originalActivityId;
private readonly RootedObjects _rootedObjects; // might be null if this is a dummy token
internal ActivityIdToken(RootedObjects rootedObjects, bool isDestroying) {
Debug.Assert(ActivityIdHelper.Instance != null);
ActivityIdHelper.Instance.SetCurrentThreadActivityId(rootedObjects._requestActivityId, out _originalActivityId);
lock (rootedObjects) {
rootedObjects._requestActivityIdRefCount++;
Debug.Assert(rootedObjects._requestActivityIdRefCount >= 2, "The original ref count should have been 1 or higher, else the activity ID could already have been released.");
}
_rootedObjects = rootedObjects;
_isDestroying = isDestroying;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose() {
if (_rootedObjects == null) {
return; // this was a dummy token; no-op
}
DisposeImpl();
}
private void DisposeImpl() {
Debug.Assert(ActivityIdHelper.Instance != null);
Debug.Assert(ActivityIdHelper.Instance.CurrentThreadActivityId == _rootedObjects._requestActivityId, "Unexpected activity ID on current thread.");
// We use a lock instead of Interlocked.Decrement so that we can guarantee that no thread
// ever invokes the 'if' code path below before some other thread invokes the 'else' code
// path.
lock (_rootedObjects) {
_rootedObjects._requestActivityIdRefCount -= (_isDestroying) ? 2 : 1;
Debug.Assert(_rootedObjects._requestActivityIdRefCount >= 0, "Somebody called Dispose() too many times.");
if (_rootedObjects._requestActivityIdRefCount == 0) {
// this overload restores the original activity ID and releases the current activity ID
ActivityIdHelper.Instance.SetCurrentThreadActivityId(_originalActivityId);
}
else {
// this overload restores the original activity ID but preserves the current activity ID
Guid unused;
ActivityIdHelper.Instance.SetCurrentThreadActivityId(_originalActivityId, out unused);
}
}
}
}
}
}
|