1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
|
//------------------------------------------------------------------------------
// <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);
}
}
}
}
}
}
|