519e1a7b93ecfa89cb2404468661ca78f8f735af
[mono.git] / mcs / class / referencesource / System.Web / RootedObjects.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="RootedObjects.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 namespace System.Web {
8     using System;
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;
17
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.
19
20     [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
21     internal sealed class RootedObjects : IPrincipalContainer {
22
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;
34
35         private SubscriptionQueue<IDisposable> _pipelineCompletedQueue;
36         private GCHandle _handle;
37
38         private RootedObjects() {
39             _handle = GCHandle.Alloc(this);
40             Pointer = (IntPtr)_handle;
41
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();
46
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();
51             }
52         }
53
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 {
57             get;
58             set;
59         }
60
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 {
64             get;
65             set;
66         }
67
68         // A pointer that can be used (via FromPointer) to reference this RootedObjects instance.
69         public IntPtr Pointer {
70             get;
71             private set;
72         }
73
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 {
77             get;
78             set;
79         }
80
81         // The worker request (IIS7+) associated with this request.
82         // May be null if the request has completed.
83         public IIS7WorkerRequest WorkerRequest {
84             get;
85             set;
86         }
87
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();
92         }
93
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");
98
99             // 'isDestroying = true' means that we'll release the implicit 'this' ref in _requestActivityIdRefCount
100             using (WithinTraceBlock(isDestroying: true)) {
101                 try {
102                     ReleaseHttpContext();
103                     ReleaseWebSocketPipeline();
104                     ReleaseWorkerRequest();
105                     ReleasePrincipal();
106
107                     // need to raise OnPipelineCompleted outside of the ThreadContext so that HttpContext.Current, User, etc. are unavailable
108                     RaiseOnPipelineCompleted();
109
110                     PerfCounters.DecrementCounter(AppPerfCounter.REQUESTS_EXECUTING);
111                 }
112                 finally {
113                     if (_handle.IsAllocated) {
114                         _handle.Free();
115                     }
116                     Pointer = IntPtr.Zero;
117                     HttpRuntime.DecrementActivePipelineCount();
118
119                     AspNetEventSource.Instance.RequestCompleted();
120                 }
121             }
122         }
123
124         // Analog of HttpContext.DisposeOnPipelineCompleted for the integrated pipeline
125         internal ISubscriptionToken DisposeOnPipelineCompleted(IDisposable target) {
126             return _pipelineCompletedQueue.Enqueue(target);
127         }
128
129         public static RootedObjects FromPointer(IntPtr pointer) {
130             GCHandle handle = (GCHandle)pointer;
131             return (RootedObjects)handle.Target;
132         }
133
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.
138             try {
139                 _pipelineCompletedQueue.FireAndComplete(disposable => disposable.Dispose());
140             }
141             catch (Exception e) {
142                 WebBaseEvent.RaiseRuntimeError(e, null);
143             }
144         }
145
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();
151             }
152
153             HttpContext = null;
154         }
155
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) {
162                     Principal = null;
163                     identity.Dispose();
164                 }
165             }
166
167             // Fix 
168
169
170             if (BinaryCompatibility.Current.TargetsAtLeastFramework45) {
171                 Principal = null;
172             }
173         }
174
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();
180             }
181
182             WebSocketPipeline = null;
183         }
184
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();
190             }
191
192             WorkerRequest = null;
193         }
194
195         // Sets up the ETW Activity ID on the current thread; caller should be:
196         // using (rootedObjects.WithinTraceBlock()) {
197         //   .. something that might require tracing ..
198         // }
199         //
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);
204         }
205
206         [MethodImpl(MethodImplOptions.AggressiveInlining)]
207         private ActivityIdToken WithinTraceBlock(bool isDestroying) {
208             if (_activityIdTracingIsEnabled) {
209                 return new ActivityIdToken(this, isDestroying);
210             }
211             else {
212                 return default(ActivityIdToken);
213             }
214         }
215
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);
222             }
223         }
224
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
229
230             internal ActivityIdToken(RootedObjects rootedObjects, bool isDestroying) {
231                 Debug.Assert(ActivityIdHelper.Instance != null);
232                 ActivityIdHelper.Instance.SetCurrentThreadActivityId(rootedObjects._requestActivityId, out _originalActivityId);
233
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.");
237                 }
238
239                 _rootedObjects = rootedObjects;
240                 _isDestroying = isDestroying;
241             }
242
243             [MethodImpl(MethodImplOptions.AggressiveInlining)]
244             public void Dispose() {
245                 if (_rootedObjects == null) {
246                     return; // this was a dummy token; no-op
247                 }
248
249                 DisposeImpl();
250             }
251
252             private void DisposeImpl() {
253                 Debug.Assert(ActivityIdHelper.Instance != null);
254                 Debug.Assert(ActivityIdHelper.Instance.CurrentThreadActivityId == _rootedObjects._requestActivityId, "Unexpected activity ID on current thread.");
255
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
258                 // path.
259                 lock (_rootedObjects) {
260                     _rootedObjects._requestActivityIdRefCount -= (_isDestroying) ? 2 : 1;
261                     Debug.Assert(_rootedObjects._requestActivityIdRefCount >= 0, "Somebody called Dispose() too many times.");
262
263                     if (_rootedObjects._requestActivityIdRefCount == 0) {
264                         // this overload restores the original activity ID and releases the current activity ID
265                         ActivityIdHelper.Instance.SetCurrentThreadActivityId(_originalActivityId);
266                     }
267                     else {
268                         // this overload restores the original activity ID but preserves the current activity ID
269                         Guid unused;
270                         ActivityIdHelper.Instance.SetCurrentThreadActivityId(_originalActivityId, out unused);
271                     }
272                 }
273             }
274         }
275     }
276 }