1 //------------------------------------------------------------------------------
2 // <copyright file="RootedObjects.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
9 using System.Runtime.CompilerServices;
10 using System.Runtime.InteropServices;
11 using System.Security.Permissions;
12 using System.Security.Principal;
13 using System.Web.Hosting;
14 using System.Web.Management;
15 using System.Web.Security;
16 using System.Web.Util;
18 // Used by the IIS integrated pipeline to reference managed objects so that they're not claimed by the GC while unmanaged code is executing.
20 [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
21 internal sealed class RootedObjects : IPrincipalContainer {
23 // These two fields are for ETW scenarios. In .NET 4.5.1, the API SetCurrentThreadActivityId
24 // can be used to correlate operations back to a given activity ID, where [in our case] an
25 // activity ID corresponds to a single request. We need to ref count since we have multiple
26 // threads operating on (or destroying) the request at once, and we need to know when the
27 // managed request has actually finished. For example, we can't just release an activity ID
28 // inside of our Destroy method since Destroy might be called on a thread pool thread while
29 // some other managed thread is still unwinding inside of PipelineRuntime. When this counter
30 // hits zero, we can fully release the activity ID.
31 private readonly bool _activityIdTracingIsEnabled;
32 private readonly Guid _requestActivityId;
33 private int _requestActivityIdRefCount = 1;
35 private SubscriptionQueue<IDisposable> _pipelineCompletedQueue;
36 private GCHandle _handle;
38 private RootedObjects() {
39 _handle = GCHandle.Alloc(this);
40 Pointer = (IntPtr)_handle;
42 // Increment active request count as soon as possible to prevent
43 // shutdown of the appdomain while requests are in flight. It
44 // is decremented in Destroy().
45 HttpRuntime.IncrementActivePipelineCount();
47 // this is an instance field instead of a static field since ETW can be enabled at any time
48 _activityIdTracingIsEnabled = ActivityIdHelper.Instance != null && AspNetEventSource.Instance.IsEnabled();
49 if (_activityIdTracingIsEnabled) {
50 _requestActivityId = ActivityIdHelper.UnsafeCreateNewActivityId();
54 // The HttpContext associated with this request.
55 // May be null if this is a WebSocket request or if the request has completed.
56 public HttpContext HttpContext {
61 // The principal associated with this request.
62 // May be null if there is no associated principal or if the request has completed.
63 public IPrincipal Principal {
68 // A pointer that can be used (via FromPointer) to reference this RootedObjects instance.
69 public IntPtr Pointer {
74 // The WebSocketPipeline that's associated with this request.
75 // May be null if this request won't be transitioned to a WebSocket request or if it has completed.
76 public WebSocketPipeline WebSocketPipeline {
81 // The worker request (IIS7+) associated with this request.
82 // May be null if the request has completed.
83 public IIS7WorkerRequest WorkerRequest {
88 // Using a static factory instead of a public constructor since there's a side effect.
89 // Namely, the new object will never be garbage collected unless Destroy() is called.
90 public static RootedObjects Create() {
91 return new RootedObjects();
94 // Fully releases all managed resources associated with this request, including
95 // the HttpContext, WebSocketContext, principal, worker request, etc.
96 public void Destroy() {
97 Debug.Trace("RootedObjects", "Destroy");
99 // 'isDestroying = true' means that we'll release the implicit 'this' ref in _requestActivityIdRefCount
100 using (WithinTraceBlock(isDestroying: true)) {
102 ReleaseHttpContext();
103 ReleaseWebSocketPipeline();
104 ReleaseWorkerRequest();
107 // need to raise OnPipelineCompleted outside of the ThreadContext so that HttpContext.Current, User, etc. are unavailable
108 RaiseOnPipelineCompleted();
110 PerfCounters.DecrementCounter(AppPerfCounter.REQUESTS_EXECUTING);
113 if (_handle.IsAllocated) {
116 Pointer = IntPtr.Zero;
117 HttpRuntime.DecrementActivePipelineCount();
119 AspNetEventSource.Instance.RequestCompleted();
124 // Analog of HttpContext.DisposeOnPipelineCompleted for the integrated pipeline
125 internal ISubscriptionToken DisposeOnPipelineCompleted(IDisposable target) {
126 return _pipelineCompletedQueue.Enqueue(target);
129 public static RootedObjects FromPointer(IntPtr pointer) {
130 GCHandle handle = (GCHandle)pointer;
131 return (RootedObjects)handle.Target;
134 internal void RaiseOnPipelineCompleted() {
135 // The callbacks really shouldn't throw exceptions, but we have a catch block just in case.
136 // Since there's nobody else that can listen for these errors (the request is unwinding and
137 // user code will no longer run), we'll just log the error.
139 _pipelineCompletedQueue.FireAndComplete(disposable => disposable.Dispose());
141 catch (Exception e) {
142 WebBaseEvent.RaiseRuntimeError(e, null);
146 // Fully releases the HttpContext instance associated with this request.
147 public void ReleaseHttpContext() {
148 Debug.Trace("RootedObjects", "ReleaseHttpContext");
149 if (HttpContext != null) {
150 HttpContext.FinishPipelineRequest();
156 // Disposes of the principal associated with this request.
157 public void ReleasePrincipal() {
158 Debug.Trace("RootedObjects", "ReleasePrincipal");
159 if (Principal != null && Principal != WindowsAuthenticationModule.AnonymousPrincipal) {
160 WindowsIdentity identity = Principal.Identity as WindowsIdentity; // original code only disposed of WindowsIdentity, not arbitrary IDisposable types
161 if (identity != null) {
170 if (BinaryCompatibility.Current.TargetsAtLeastFramework45) {
175 // Disposes of the WebSocketPipeline instance associated with this request.
176 public void ReleaseWebSocketPipeline() {
177 Debug.Trace("RootedObjects", "ReleaseWebSocketContext");
178 if (WebSocketPipeline != null) {
179 WebSocketPipeline.Dispose();
182 WebSocketPipeline = null;
185 // Disposes of the worker request associated with this request.
186 public void ReleaseWorkerRequest() {
187 Debug.Trace("RootedObjects", "ReleaseWorkerRequest");
188 if (WorkerRequest != null) {
189 WorkerRequest.Dispose();
192 WorkerRequest = null;
195 // Sets up the ETW Activity ID on the current thread; caller should be:
196 // using (rootedObjects.WithinTraceBlock()) {
197 // .. something that might require tracing ..
200 // This is designed to be *very* cheap if tracing isn't enabled.
201 [MethodImpl(MethodImplOptions.AggressiveInlining)]
202 public ActivityIdToken WithinTraceBlock() {
203 return WithinTraceBlock(isDestroying: false);
206 [MethodImpl(MethodImplOptions.AggressiveInlining)]
207 private ActivityIdToken WithinTraceBlock(bool isDestroying) {
208 if (_activityIdTracingIsEnabled) {
209 return new ActivityIdToken(this, isDestroying);
212 return default(ActivityIdToken);
216 // Called once per request; emits an ETW event saying that we transitioned from IIS -> ASP.NET
217 public void WriteTransferEventIfNecessary() {
218 Debug.Assert(WorkerRequest != null);
219 if (_activityIdTracingIsEnabled) {
220 Debug.Assert(_requestActivityId != Guid.Empty);
221 AspNetEventSource.Instance.RequestEnteredAspNetPipeline(WorkerRequest, _requestActivityId);
225 internal struct ActivityIdToken : IDisposable {
226 private readonly bool _isDestroying;
227 private readonly Guid _originalActivityId;
228 private readonly RootedObjects _rootedObjects; // might be null if this is a dummy token
230 internal ActivityIdToken(RootedObjects rootedObjects, bool isDestroying) {
231 Debug.Assert(ActivityIdHelper.Instance != null);
232 ActivityIdHelper.Instance.SetCurrentThreadActivityId(rootedObjects._requestActivityId, out _originalActivityId);
234 lock (rootedObjects) {
235 rootedObjects._requestActivityIdRefCount++;
236 Debug.Assert(rootedObjects._requestActivityIdRefCount >= 2, "The original ref count should have been 1 or higher, else the activity ID could already have been released.");
239 _rootedObjects = rootedObjects;
240 _isDestroying = isDestroying;
243 [MethodImpl(MethodImplOptions.AggressiveInlining)]
244 public void Dispose() {
245 if (_rootedObjects == null) {
246 return; // this was a dummy token; no-op
252 private void DisposeImpl() {
253 Debug.Assert(ActivityIdHelper.Instance != null);
254 Debug.Assert(ActivityIdHelper.Instance.CurrentThreadActivityId == _rootedObjects._requestActivityId, "Unexpected activity ID on current thread.");
256 // We use a lock instead of Interlocked.Decrement so that we can guarantee that no thread
257 // ever invokes the 'if' code path below before some other thread invokes the 'else' code
259 lock (_rootedObjects) {
260 _rootedObjects._requestActivityIdRefCount -= (_isDestroying) ? 2 : 1;
261 Debug.Assert(_rootedObjects._requestActivityIdRefCount >= 0, "Somebody called Dispose() too many times.");
263 if (_rootedObjects._requestActivityIdRefCount == 0) {
264 // this overload restores the original activity ID and releases the current activity ID
265 ActivityIdHelper.Instance.SetCurrentThreadActivityId(_originalActivityId);
268 // this overload restores the original activity ID but preserves the current activity ID
270 ActivityIdHelper.Instance.SetCurrentThreadActivityId(_originalActivityId, out unused);