Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System / net / System / Net / _ContextAwareResult.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="_ContextAwareResult.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Net {
8     using System.Threading;
9     using System.Security;
10     using System.Security.Principal;
11     using System.Security.Permissions;
12
13     //
14     // This is used by ContextAwareResult to cache callback closures between similar calls.  Create one of these and
15     // pass it in to FinishPostingAsyncOp() to prevent the context from being captured in every iteration of a looped async call.
16     //
17     // I thought about making the delegate and state into weak references, but decided against it because:
18     //    - The delegate is very likely to be abandoned by the user right after calling BeginXxx, making caching it useless. There's
19     //         no easy way to weakly reference just the target.
20     //    - We want to support identifying state via object.Equals() (especially value types), which means we need to keep a
21     //         reference to the original.  Plus, if we're holding the target, might as well hold the state too.
22     // The user will need to disable caching if they want their target/state to be instantly collected.
23     //
24     // For now the state is not included as part of the closure.  It is too common a pattern (for example with socket receive)
25     // to have several pending IOs differentiated by their state object.  We don't want that pattern to break the cache.
26     //
27     internal class CallbackClosure
28     {
29         private AsyncCallback savedCallback;
30         private ExecutionContext savedContext;
31
32         internal CallbackClosure(ExecutionContext context, AsyncCallback callback)
33         {
34             if (callback != null)
35             {
36                 savedCallback = callback;
37                 savedContext = context;
38             }
39         }
40
41         internal bool IsCompatible(AsyncCallback callback)
42         {
43             if (callback == null || savedCallback == null)
44                 return false;
45
46             // Delegates handle this ok.  AsyncCallback is sealed and immutable, so if this succeeds, we are safe to use
47             // the passed-in instance.
48             if (!object.Equals(savedCallback, callback))
49                 return false;
50
51             return true;
52         }
53
54         internal AsyncCallback AsyncCallback
55         {
56             get
57             {
58                 return savedCallback;
59             }
60         }
61
62         internal ExecutionContext Context
63         {
64             get
65             {
66                 return savedContext;
67             }
68         }
69     }
70
71     //
72     // This class will ensure that the correct context is restored on the thread before invoking
73     // a user callback.
74     //
75     internal class ContextAwareResult : LazyAsyncResult
76     {
77         [Flags]
78         private enum StateFlags
79         {
80             None                  = 0x00,
81             CaptureIdentity       = 0x01,
82             CaptureContext        = 0x02,
83             ThreadSafeContextCopy = 0x04,
84             PostBlockStarted      = 0x08,
85             PostBlockFinished     = 0x10,
86         }
87
88         // This needs to be volatile so it's sure to make it over to the completion thread in time.
89         private volatile ExecutionContext _Context;
90         private object _Lock;
91         private StateFlags _Flags;
92
93 #if !FEATURE_PAL
94         private WindowsIdentity  _Wi;
95 #endif
96
97         internal ContextAwareResult(object myObject, object myState, AsyncCallback myCallBack) :
98             this(false, false, myObject, myState, myCallBack)
99         { }
100
101         // Setting captureIdentity enables the Identity property.  This will be available even if ContextCopy isn't, either because
102         // flow is suppressed or it wasn't needed.  (If ContextCopy isn't available, Identity may or may not be.  But if it is, it
103         // should be used instead of ContextCopy for impersonation - ContextCopy might not include the identity.)
104         //
105         // Setting forceCaptureContext enables the ContextCopy property even when a null callback is specified.  (The context is
106         // always captured if a callback is given.)
107         internal ContextAwareResult(bool captureIdentity, bool forceCaptureContext, object myObject, object myState, AsyncCallback myCallBack) :
108             this(captureIdentity, forceCaptureContext, false, myObject, myState, myCallBack)
109         { }
110
111         internal ContextAwareResult(bool captureIdentity, bool forceCaptureContext, bool threadSafeContextCopy, object myObject, object myState, AsyncCallback myCallBack) :
112             base(myObject, myState, myCallBack)
113         {
114             if (forceCaptureContext)
115             {
116                 _Flags = StateFlags.CaptureContext;
117             }
118
119             if (captureIdentity)
120             {
121                 _Flags |= StateFlags.CaptureIdentity;
122             }
123
124             if (threadSafeContextCopy)
125             {
126                 _Flags |= StateFlags.ThreadSafeContextCopy;
127             }
128         }
129
130 #if !FEATURE_PAL
131         // Security: We need an assert for a call into WindowsIdentity.GetCurrent
132         [SecurityPermissionAttribute(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)]
133         private void SafeCaptureIdenity()
134         {
135             _Wi = WindowsIdentity.GetCurrent();
136         }
137 #endif
138
139         //
140         // This can be used to establish a context during an async op for something like calling a delegate or demanding a permission.
141         // May block briefly if the context is still being produced.
142         //
143         // Returns null if called from the posting thread.
144         //
145         internal ExecutionContext ContextCopy
146         {
147             get
148             {
149                 GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::ContextCopy|Called on completed result.", ValidationHelper.HashString(this));
150                 if (InternalPeekCompleted)
151                 {
152                     throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
153                 }
154
155                 ExecutionContext context = _Context;
156                 if (context != null)
157                 {
158                     return context.CreateCopy();
159                 }
160
161                 // Make sure the context was requested.
162                 GlobalLog.Assert(AsyncCallback != null || (_Flags & StateFlags.CaptureContext) != 0, "ContextAwareResult#{0}::ContextCopy|No context captured - specify a callback or forceCaptureContext.", ValidationHelper.HashString(this));
163
164                 // Just use the lock to block.  We might be on the thread that owns the lock which is great, it means we
165                 // don't need a context anyway.
166                 if ((_Flags & StateFlags.PostBlockFinished) == 0)
167                 {
168                     GlobalLog.Assert(_Lock != null, "ContextAwareResult#{0}::ContextCopy|Must lock (StartPostingAsyncOp()) { ... FinishPostingAsyncOp(); } when calling ContextCopy (unless it's only called after FinishPostingAsyncOp).", ValidationHelper.HashString(this));
169                     lock (_Lock) { }
170                 }
171
172                 GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::ContextCopy|Result became completed during call.", ValidationHelper.HashString(this));
173                 if (InternalPeekCompleted)
174                 {
175                     throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
176                 }
177
178                 context = _Context;
179                 return context == null ? null : context.CreateCopy();
180             }
181         }
182
183 #if !FEATURE_PAL
184         //
185         // Just like ContextCopy.
186         //
187         internal WindowsIdentity Identity
188         {
189             get
190             {
191                 GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::Identity|Called on completed result.", ValidationHelper.HashString(this));
192                 if (InternalPeekCompleted)
193                 {
194                     throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
195                 }
196
197                 if (_Wi != null)
198                 {
199                     return _Wi;
200                 }
201
202                 // Make sure the identity was requested.
203                 GlobalLog.Assert((_Flags & StateFlags.CaptureIdentity) != 0, "ContextAwareResult#{0}::Identity|No identity captured - specify captureIdentity.", ValidationHelper.HashString(this));
204
205                 // Just use the lock to block.  We might be on the thread that owns the lock which is great, it means we
206                 // don't need an identity anyway.
207                 if ((_Flags & StateFlags.PostBlockFinished) == 0)
208                 {
209                     GlobalLog.Assert(_Lock != null, "ContextAwareResult#{0}::Identity|Must lock (StartPostingAsyncOp()) { ... FinishPostingAsyncOp(); } when calling Identity (unless it's only called after FinishPostingAsyncOp).", ValidationHelper.HashString(this));
210                     lock (_Lock) { }
211                 }
212
213                 GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::Identity|Result became completed during call.", ValidationHelper.HashString(this));
214                 if (InternalPeekCompleted)
215                 {
216                     throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
217                 }
218
219                 return _Wi;
220             }
221         }
222 #endif
223
224 #if DEBUG
225         // Want to be able to verify that the Identity was requested.  If it was requested but isn't available
226         // on the Identity property, it's either available via ContextCopy or wasn't needed (synchronous).
227         internal bool IdentityRequested
228         {
229             get
230             {
231                 return (_Flags & StateFlags.CaptureIdentity) != 0;
232             }
233         }
234 #endif
235
236         internal object StartPostingAsyncOp()
237         {
238             return StartPostingAsyncOp(true);
239         }
240
241         //
242         // If ContextCopy or Identity will be used, the return value should be locked until FinishPostingAsyncOp() is called
243         // or the operation has been aborted (e.g. by BeginXxx throwing).  Otherwise, this can be called with false to prevent the lock
244         // object from being created.
245         //
246         internal object StartPostingAsyncOp(bool lockCapture)
247         {
248             GlobalLog.Assert(!InternalPeekCompleted, "ContextAwareResult#{0}::StartPostingAsyncOp|Called on completed result.", ValidationHelper.HashString(this));
249
250             DebugProtectState(true);
251
252             _Lock = lockCapture ? new object() : null;
253             _Flags |= StateFlags.PostBlockStarted;
254             return _Lock;
255         }
256
257         //
258         // Call this when returning control to the user.
259         //
260         internal bool FinishPostingAsyncOp()
261         {
262             // Ignore this call if StartPostingAsyncOp() failed or wasn't called, or this has already been called.
263             if ((_Flags & (StateFlags.PostBlockStarted | StateFlags.PostBlockFinished)) != StateFlags.PostBlockStarted)
264             {
265                 return false;
266             }
267             _Flags |= StateFlags.PostBlockFinished;
268
269             ExecutionContext cachedContext = null;
270             return CaptureOrComplete(ref cachedContext, false);
271         }
272
273         //
274         // Call this when returning control to the user.  Allows a cached Callback Closure to be supplied and used
275         // as appropriate, and replaced with a new one.
276         //
277         internal bool FinishPostingAsyncOp(ref CallbackClosure closure)
278         {
279             // Ignore this call if StartPostingAsyncOp() failed or wasn't called, or this has already been called.
280             if ((_Flags & (StateFlags.PostBlockStarted | StateFlags.PostBlockFinished)) != StateFlags.PostBlockStarted)
281             {
282                 return false;
283             }
284             _Flags |= StateFlags.PostBlockFinished;
285
286             // Need a copy of this ref argument since it can be used in many of these calls simultaneously.
287             CallbackClosure closureCopy = closure;
288             ExecutionContext cachedContext;
289             if (closureCopy == null)
290             {
291                 cachedContext = null;
292             }
293             else
294             {
295                 if (!closureCopy.IsCompatible(AsyncCallback))
296                 {
297                     // Clear the cache as soon as a method is called with incompatible parameters.
298                     closure = null;
299                     cachedContext = null;
300                 }
301                 else
302                 {
303                     // If it succeeded, we want to replace our context/callback with the one from the closure.
304                     // Using the closure's instance of the callback is probably overkill, but safer.
305                     AsyncCallback = closureCopy.AsyncCallback;
306                     cachedContext = closureCopy.Context;
307                 }
308             }
309
310             bool calledCallback = CaptureOrComplete(ref cachedContext, true);
311
312             // Set up new cached context if we didn't use the previous one.
313             if (closure == null && AsyncCallback != null && cachedContext != null)
314             {
315                 closure = new CallbackClosure(cachedContext, AsyncCallback);
316             }
317
318             return calledCallback;
319         }
320
321 /* enable when needed
322         //
323         // Use this to block until FinishPostingAsyncOp() completes.  Must check for null.
324         //
325         internal object PostingLock
326         {
327             get
328             {
329                 return _Lock;
330             }
331         }
332
333         //
334         // Call this if you want to cancel any flowing.
335         //
336         internal void InvokeCallbackWithoutContext(object result)
337         {
338             ProtectedInvokeCallback(result, (IntPtr) 1);
339         }
340 */
341
342         //
343         protected override void Cleanup()
344         {
345             base.Cleanup();
346
347             GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::Cleanup()");
348 #if !FEATURE_PAL
349             if (_Wi != null)
350             {
351                 _Wi.Dispose();
352                 _Wi = null;
353             }
354 #endif
355         }
356
357         //
358         // This must be called right before returning the result to the user.  It might call the callback itself,
359         // to avoid flowing context.  Even if the operation completes before this call, the callback won't have been
360         // called.
361         //
362         // Returns whether the operation completed sync or not.
363         //
364         private bool CaptureOrComplete(ref ExecutionContext cachedContext, bool returnContext)
365         {
366             GlobalLog.Assert((_Flags & StateFlags.PostBlockStarted) != 0, "ContextAwareResult#{0}::CaptureOrComplete|Called without calling StartPostingAsyncOp.", ValidationHelper.HashString(this));
367
368             // See if we're going to need to capture the context.
369             bool capturingContext = AsyncCallback != null || (_Flags & StateFlags.CaptureContext) != 0;
370
371 #if !FEATURE_PAL
372             // Peek if we've already completed, but don't fix CompletedSynchronously yet
373             // Capture the identity if requested, unless we're going to capture the context anyway, unless
374             // capturing the context won't be sufficient.
375             if ((_Flags & StateFlags.CaptureIdentity) != 0 && !InternalPeekCompleted &&
376                 (!capturingContext || SecurityContext.IsWindowsIdentityFlowSuppressed()))
377             {
378                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() starting identity capture");
379                 SafeCaptureIdenity();
380             }
381 #endif
382
383             // No need to flow if there's no callback, unless it's been specifically requested.
384             // Note that Capture() can return null, for example if SuppressFlow() is in effect.
385             if (capturingContext && !InternalPeekCompleted)
386             {
387                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() starting capture");
388                 if (cachedContext == null)
389                 {
390                     cachedContext = ExecutionContext.Capture();
391                 }
392                 if (cachedContext != null)
393                 {
394                     if (!returnContext)
395                     {
396                         _Context = cachedContext;
397                         cachedContext = null;
398                     }
399                     else
400                     {
401                         _Context = cachedContext.CreateCopy();
402                     }
403                 }
404                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() _Context:" + ValidationHelper.HashString(_Context));
405             }
406             else
407             {
408                 // Otherwise we have to have completed synchronously, or not needed the context.
409                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() skipping capture");
410                 cachedContext = null;
411                 GlobalLog.Assert(AsyncCallback == null || CompletedSynchronously, "ContextAwareResult#{0}::CaptureOrComplete|Didn't capture context, but didn't complete synchronously!", ValidationHelper.HashString(this));
412             }
413
414             // Now we want to see for sure what to do.  We might have just captured the context for no reason.
415             // This has to be the first time the state has been queried "for real" (apart from InvokeCallback)
416             // to guarantee synchronization with Complete() (otherwise, Complete() could try to call the
417             // callback without the context having been gotten).
418             DebugProtectState(false);
419             if (CompletedSynchronously)
420             {
421                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() completing synchronously");
422                 base.Complete(IntPtr.Zero);
423                 return true;
424             }
425
426             return false;
427         }
428
429         //
430         // Is guaranteed to be called only once.  If called with a non-zero userToken, the context is not flowed.
431         //
432         protected override void Complete(IntPtr userToken)
433         {
434             GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::Complete() _Context(set):" + (_Context != null).ToString() + " userToken:" + userToken.ToString());
435
436             // If no flowing, just complete regularly.
437             if ((_Flags & StateFlags.PostBlockStarted) == 0)
438             {
439                 base.Complete(userToken);
440                 return;
441             }
442
443             // At this point, IsCompleted is set and CompletedSynchronously is fixed.  If it's synchronous, then we want to hold
444             // the completion for the CaptureOrComplete() call to avoid the context flow.  If not, we know CaptureOrComplete() has completed.
445             if (CompletedSynchronously)
446             {
447                 return;
448             }
449
450             ExecutionContext context = _Context;
451
452             // If the context is being abandoned or wasn't captured (SuppressFlow, null AsyncCallback), just
453             // complete regularly, as long as CaptureOrComplete() has finished.
454             // 
455
456
457             if (userToken != IntPtr.Zero || context == null)
458             {
459                 base.Complete(userToken);
460                 return;
461             }
462
463             ExecutionContext.Run((_Flags & StateFlags.ThreadSafeContextCopy) != 0 ? context.CreateCopy() : context, 
464                                  new ContextCallback(CompleteCallback), null);
465         }
466
467         private void CompleteCallback(object state)
468         {
469             GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CompleteCallback() Context set, calling callback.");
470             base.Complete(IntPtr.Zero);
471         }
472     }
473 }