e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
277 lines
12 KiB
C#
277 lines
12 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|