Welcome to mirror list, hosted at ThFree Co, Russian Federation.

_ContextAwareResult.cs « Net « System « net « System « referencesource « class « mcs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: c5c5dc428921ad89f7e45bcbcd18f4ec46b5c941 (plain)
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
//------------------------------------------------------------------------------
// <copyright file="_ContextAwareResult.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace System.Net {
    using System.Threading;
    using System.Security;
    using System.Security.Principal;
    using System.Security.Permissions;

    //
    // This is used by ContextAwareResult to cache callback closures between similar calls.  Create one of these and
    // pass it in to FinishPostingAsyncOp() to prevent the context from being captured in every iteration of a looped async call.
    //
    // I thought about making the delegate and state into weak references, but decided against it because:
    //    - The delegate is very likely to be abandoned by the user right after calling BeginXxx, making caching it useless. There's
    //         no easy way to weakly reference just the target.
    //    - We want to support identifying state via object.Equals() (especially value types), which means we need to keep a
    //         reference to the original.  Plus, if we're holding the target, might as well hold the state too.
    // The user will need to disable caching if they want their target/state to be instantly collected.
    //
    // For now the state is not included as part of the closure.  It is too common a pattern (for example with socket receive)
    // to have several pending IOs differentiated by their state object.  We don't want that pattern to break the cache.
    //
    internal class CallbackClosure
    {
        private AsyncCallback savedCallback;
        private ExecutionContext savedContext;

        internal CallbackClosure(ExecutionContext context, AsyncCallback callback)
        {
            if (callback != null)
            {
                savedCallback = callback;
                savedContext = context;
            }
        }

        internal bool IsCompatible(AsyncCallback callback)
        {
            if (callback == null || savedCallback == null)
                return false;

            // Delegates handle this ok.  AsyncCallback is sealed and immutable, so if this succeeds, we are safe to use
            // the passed-in instance.
            if (!object.Equals(savedCallback, callback))
                return false;

            return true;
        }

        internal AsyncCallback AsyncCallback
        {
            get
            {
                return savedCallback;
            }
        }

        internal ExecutionContext Context
        {
            get
            {
                return savedContext;
            }
        }
    }

    //
    // This class will ensure that the correct context is restored on the thread before invoking
    // a user callback.
    //
    internal class ContextAwareResult : LazyAsyncResult
    {
        [Flags]
        private enum StateFlags
        {
            None                  = 0x00,
            CaptureIdentity       = 0x01,
            CaptureContext        = 0x02,
            ThreadSafeContextCopy = 0x04,
            PostBlockStarted      = 0x08,
            PostBlockFinished     = 0x10,
        }

        // This needs to be volatile so it's sure to make it over to the completion thread in time.
        private volatile ExecutionContext _Context;
        private object _Lock;
        private StateFlags _Flags;

#if !FEATURE_PAL
        private WindowsIdentity  _Wi;
#endif

        internal ContextAwareResult(object myObject, object myState, AsyncCallback myCallBack) :
            this(false, false, myObject, myState, myCallBack)
        { }

        // Setting captureIdentity enables the Identity property.  This will be available even if ContextCopy isn't, either because
        // flow is suppressed or it wasn't needed.  (If ContextCopy isn't available, Identity may or may not be.  But if it is, it
        // should be used instead of ContextCopy for impersonation - ContextCopy might not include the identity.)
        //
        // Setting forceCaptureContext enables the ContextCopy property even when a null callback is specified.  (The context is
        // always captured if a callback is given.)
        internal ContextAwareResult(bool captureIdentity, bool forceCaptureContext, object myObject, object myState, AsyncCallback myCallBack) :
            this(captureIdentity, forceCaptureContext, false, myObject, myState, myCallBack)
        { }

        internal ContextAwareResult(bool captureIdentity, bool forceCaptureContext, bool threadSafeContextCopy, object myObject, object myState, AsyncCallback myCallBack) :
            base(myObject, myState, myCallBack)
        {
            if (forceCaptureContext)
            {
                _Flags = StateFlags.CaptureContext;
            }

            if (captureIdentity)
            {
                _Flags |= StateFlags.CaptureIdentity;
            }

            if (threadSafeContextCopy)
            {
                _Flags |= StateFlags.ThreadSafeContextCopy;
            }
        }

#if !FEATURE_PAL
        // Security: We need an assert for a call into WindowsIdentity.GetCurrent
        [SecurityPermissionAttribute(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)]
        private void SafeCaptureIdenity()
        {
            _Wi = WindowsIdentity.GetCurrent();
        }
#endif

        //
        // This can be used to establish a context during an async op for something like calling a delegate or demanding a permission.
        // May block briefly if the context is still being produced.
        //
        // Returns null if called from the posting thread.
        //
        internal ExecutionContext ContextCopy
        {
            get
            {
                GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::ContextCopy|Called on completed result.", ValidationHelper.HashString(this));
                if (InternalPeekCompleted)
                {
                    throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
                }

                ExecutionContext context = _Context;
                if (context != null)
                {
                    return context.CreateCopy();
                }

                // Make sure the context was requested.
                GlobalLog.Assert(AsyncCallback != null || (_Flags & StateFlags.CaptureContext) != 0, "ContextAwareResult#{0}::ContextCopy|No context captured - specify a callback or forceCaptureContext.", ValidationHelper.HashString(this));

                // Just use the lock to block.  We might be on the thread that owns the lock which is great, it means we
                // don't need a context anyway.
                if ((_Flags & StateFlags.PostBlockFinished) == 0)
                {
                    GlobalLog.Assert(_Lock != null, "ContextAwareResult#{0}::ContextCopy|Must lock (StartPostingAsyncOp()) { ... FinishPostingAsyncOp(); } when calling ContextCopy (unless it's only called after FinishPostingAsyncOp).", ValidationHelper.HashString(this));
                    lock (_Lock) { }
                }

                GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::ContextCopy|Result became completed during call.", ValidationHelper.HashString(this));
                if (InternalPeekCompleted)
                {
                    throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
                }

                context = _Context;
                return context == null ? null : context.CreateCopy();
            }
        }

#if !FEATURE_PAL
        //
        // Just like ContextCopy.
        //
        internal WindowsIdentity Identity
        {
            get
            {
                GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::Identity|Called on completed result.", ValidationHelper.HashString(this));
                if (InternalPeekCompleted)
                {
                    throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
                }

                if (_Wi != null)
                {
                    return _Wi;
                }

                // Make sure the identity was requested.
                GlobalLog.Assert((_Flags & StateFlags.CaptureIdentity) != 0, "ContextAwareResult#{0}::Identity|No identity captured - specify captureIdentity.", ValidationHelper.HashString(this));

                // Just use the lock to block.  We might be on the thread that owns the lock which is great, it means we
                // don't need an identity anyway.
                if ((_Flags & StateFlags.PostBlockFinished) == 0)
                {
                    GlobalLog.Assert(_Lock != null, "ContextAwareResult#{0}::Identity|Must lock (StartPostingAsyncOp()) { ... FinishPostingAsyncOp(); } when calling Identity (unless it's only called after FinishPostingAsyncOp).", ValidationHelper.HashString(this));
                    lock (_Lock) { }
                }

                GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::Identity|Result became completed during call.", ValidationHelper.HashString(this));
                if (InternalPeekCompleted)
                {
                    throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
                }

                return _Wi;
            }
        }
#endif

#if DEBUG
        // Want to be able to verify that the Identity was requested.  If it was requested but isn't available
        // on the Identity property, it's either available via ContextCopy or wasn't needed (synchronous).
        internal bool IdentityRequested
        {
            get
            {
                return (_Flags & StateFlags.CaptureIdentity) != 0;
            }
        }
#endif

        internal object StartPostingAsyncOp()
        {
            return StartPostingAsyncOp(true);
        }

        //
        // If ContextCopy or Identity will be used, the return value should be locked until FinishPostingAsyncOp() is called
        // or the operation has been aborted (e.g. by BeginXxx throwing).  Otherwise, this can be called with false to prevent the lock
        // object from being created.
        //
        internal object StartPostingAsyncOp(bool lockCapture)
        {
            GlobalLog.Assert(!InternalPeekCompleted, "ContextAwareResult#{0}::StartPostingAsyncOp|Called on completed result.", ValidationHelper.HashString(this));

            DebugProtectState(true);

            _Lock = lockCapture ? new object() : null;
            _Flags |= StateFlags.PostBlockStarted;
            return _Lock;
        }

        //
        // Call this when returning control to the user.
        //
        internal bool FinishPostingAsyncOp()
        {
            // Ignore this call if StartPostingAsyncOp() failed or wasn't called, or this has already been called.
            if ((_Flags & (StateFlags.PostBlockStarted | StateFlags.PostBlockFinished)) != StateFlags.PostBlockStarted)
            {
                return false;
            }
            _Flags |= StateFlags.PostBlockFinished;

            ExecutionContext cachedContext = null;
            return CaptureOrComplete(ref cachedContext, false);
        }

        //
        // Call this when returning control to the user.  Allows a cached Callback Closure to be supplied and used
        // as appropriate, and replaced with a new one.
        //
        internal bool FinishPostingAsyncOp(ref CallbackClosure closure)
        {
            // Ignore this call if StartPostingAsyncOp() failed or wasn't called, or this has already been called.
            if ((_Flags & (StateFlags.PostBlockStarted | StateFlags.PostBlockFinished)) != StateFlags.PostBlockStarted)
            {
                return false;
            }
            _Flags |= StateFlags.PostBlockFinished;

            // Need a copy of this ref argument since it can be used in many of these calls simultaneously.
            CallbackClosure closureCopy = closure;
            ExecutionContext cachedContext;
            if (closureCopy == null)
            {
                cachedContext = null;
            }
            else
            {
                if (!closureCopy.IsCompatible(AsyncCallback))
                {
                    // Clear the cache as soon as a method is called with incompatible parameters.
                    closure = null;
                    cachedContext = null;
                }
                else
                {
                    // If it succeeded, we want to replace our context/callback with the one from the closure.
                    // Using the closure's instance of the callback is probably overkill, but safer.
                    AsyncCallback = closureCopy.AsyncCallback;
                    cachedContext = closureCopy.Context;
                }
            }

            bool calledCallback = CaptureOrComplete(ref cachedContext, true);

            // Set up new cached context if we didn't use the previous one.
            if (closure == null && AsyncCallback != null && cachedContext != null)
            {
                closure = new CallbackClosure(cachedContext, AsyncCallback);
            }

            return calledCallback;
        }

/* enable when needed
        //
        // Use this to block until FinishPostingAsyncOp() completes.  Must check for null.
        //
        internal object PostingLock
        {
            get
            {
                return _Lock;
            }
        }

        //
        // Call this if you want to cancel any flowing.
        //
        internal void InvokeCallbackWithoutContext(object result)
        {
            ProtectedInvokeCallback(result, (IntPtr) 1);
        }
*/

        //
        protected override void Cleanup()
        {
            base.Cleanup();

            GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::Cleanup()");
#if !FEATURE_PAL
            if (_Wi != null)
            {
                _Wi.Dispose();
                _Wi = null;
            }
#endif
        }

        //
        // This must be called right before returning the result to the user.  It might call the callback itself,
        // to avoid flowing context.  Even if the operation completes before this call, the callback won't have been
        // called.
        //
        // Returns whether the operation completed [....] or not.
        //
        private bool CaptureOrComplete(ref ExecutionContext cachedContext, bool returnContext)
        {
            GlobalLog.Assert((_Flags & StateFlags.PostBlockStarted) != 0, "ContextAwareResult#{0}::CaptureOrComplete|Called without calling StartPostingAsyncOp.", ValidationHelper.HashString(this));

            // See if we're going to need to capture the context.
            bool capturingContext = AsyncCallback != null || (_Flags & StateFlags.CaptureContext) != 0;

#if !FEATURE_PAL
            // Peek if we've already completed, but don't fix CompletedSynchronously yet
            // Capture the identity if requested, unless we're going to capture the context anyway, unless
            // capturing the context won't be sufficient.
            if ((_Flags & StateFlags.CaptureIdentity) != 0 && !InternalPeekCompleted &&
                (!capturingContext || SecurityContext.IsWindowsIdentityFlowSuppressed()))
            {
                GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() starting identity capture");
                SafeCaptureIdenity();
            }
#endif

            // No need to flow if there's no callback, unless it's been specifically requested.
            // Note that Capture() can return null, for example if SuppressFlow() is in effect.
            if (capturingContext && !InternalPeekCompleted)
            {
                GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() starting capture");
                if (cachedContext == null)
                {
                    cachedContext = ExecutionContext.Capture();
                }
                if (cachedContext != null)
                {
                    if (!returnContext)
                    {
                        _Context = cachedContext;
                        cachedContext = null;
                    }
                    else
                    {
                        _Context = cachedContext.CreateCopy();
                    }
                }
                GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() _Context:" + ValidationHelper.HashString(_Context));
            }
            else
            {
                // Otherwise we have to have completed synchronously, or not needed the context.
                GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() skipping capture");
                cachedContext = null;
                GlobalLog.Assert(AsyncCallback == null || CompletedSynchronously, "ContextAwareResult#{0}::CaptureOrComplete|Didn't capture context, but didn't complete synchronously!", ValidationHelper.HashString(this));
            }

            // Now we want to see for sure what to do.  We might have just captured the context for no reason.
            // This has to be the first time the state has been queried "for real" (apart from InvokeCallback)
            // to guarantee synchronization with Complete() (otherwise, Complete() could try to call the
            // callback without the context having been gotten).
            DebugProtectState(false);
            if (CompletedSynchronously)
            {
                GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() completing synchronously");
                base.Complete(IntPtr.Zero);
                return true;
            }

            return false;
        }

        //
        // Is guaranteed to be called only once.  If called with a non-zero userToken, the context is not flowed.
        //
        protected override void Complete(IntPtr userToken)
        {
            GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::Complete() _Context(set):" + (_Context != null).ToString() + " userToken:" + userToken.ToString());

            // If no flowing, just complete regularly.
            if ((_Flags & StateFlags.PostBlockStarted) == 0)
            {
                base.Complete(userToken);
                return;
            }

            // At this point, IsCompleted is set and CompletedSynchronously is fixed.  If it's synchronous, then we want to hold
            // the completion for the CaptureOrComplete() call to avoid the context flow.  If not, we know CaptureOrComplete() has completed.
            if (CompletedSynchronously)
            {
                return;
            }

            ExecutionContext context = _Context;

            // If the context is being abandoned or wasn't captured (SuppressFlow, null AsyncCallback), just
            // complete regularly, as long as CaptureOrComplete() has finished.
            // 


            if (userToken != IntPtr.Zero || context == null)
            {
                base.Complete(userToken);
                return;
            }

            ExecutionContext.Run((_Flags & StateFlags.ThreadSafeContextCopy) != 0 ? context.CreateCopy() : context, 
                                 new ContextCallback(CompleteCallback), null);
        }

        private void CompleteCallback(object state)
        {
            GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CompleteCallback() Context set, calling callback.");
            base.Complete(IntPtr.Zero);
        }
    }
}