The Labs \ Source Viewer \ SSCLI \ System.Net \ ContextAwareResult

  1. //------------------------------------------------------------------------------
  2. // <copyright file="_ContextAwareResult.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. namespace System.Net
  16. {
  17.     using System.Threading;
  18.     using System.Security;
  19.     using System.Security.Principal;
  20.     using System.Security.Permissions;
  21.    
  22.     //
  23.     // This is used by ContextAwareResult to cache callback closures between similar calls. Create one of these and
  24.     // pass it in to FinishPostingAsyncOp() to prevent the context from being captured in every iteration of a looped async call.
  25.     //
  26.     // I thought about making the delegate and state into weak references, but decided against it because:
  27.     // - The delegate is very likely to be abandoned by the user right after calling BeginXxx, making caching it useless. There's
  28.     // no easy way to weakly reference just the target.
  29.     // - We want to support identifying state via object.Equals() (especially value types), which means we need to keep a
  30.     // reference to the original. Plus, if we're holding the target, might as well hold the state too.
  31.     // The user will need to disable caching if they want their target/state to be instantly collected.
  32.     //
  33.     // For now the state is not included as part of the closure. It is too common a pattern (for example with socket receive)
  34.     // to have several pending IOs differentiated by their state object. We don't want that pattern to break the cache.
  35.     //
  36.     internal class CallbackClosure
  37.     {
  38.         private AsyncCallback savedCallback;
  39.         private ExecutionContext savedContext;
  40.        
  41.         internal CallbackClosure(ExecutionContext context, AsyncCallback callback)
  42.         {
  43.             if (callback != null) {
  44.                 savedCallback = callback;
  45.                 savedContext = context;
  46.             }
  47.         }
  48.        
  49.         internal bool IsCompatible(AsyncCallback callback)
  50.         {
  51.             if (callback == null || savedCallback == null)
  52.                 return false;
  53.            
  54.             // Delegates handle this ok. AsyncCallback is sealed and immutable, so if this succeeds, we are safe to use
  55.             // the passed-in instance.
  56.             if (!object.Equals(savedCallback, callback))
  57.                 return false;
  58.            
  59.             return true;
  60.         }
  61.        
  62.         internal AsyncCallback AsyncCallback {
  63.             get { return savedCallback; }
  64.         }
  65.        
  66.         internal ExecutionContext Context {
  67.             get { return savedContext; }
  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 = 0,
  81.             CaptureIdentity = 1,
  82.             CaptureContext = 2,
  83.             ThreadSafeContextCopy = 4,
  84.             PostBlockStarted = 8,
  85.             PostBlockFinished = 16
  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.        
  94.         internal ContextAwareResult(object myObject, object myState, AsyncCallback myCallBack) : this(false, false, myObject, myState, myCallBack)
  95.         {
  96.         }
  97.        
  98.         // Setting captureIdentity enables the Identity property. This will be available even if ContextCopy isn't, either because
  99.         // flow is suppressed or it wasn't needed. (If ContextCopy isn't available, Identity may or may not be. But if it is, it
  100.         // should be used instead of ContextCopy for impersonation - ContextCopy might not include the identity.)
  101.         //
  102.         // Setting forceCaptureContext enables the ContextCopy property even when a null callback is specified. (The context is
  103.         // always captured if a callback is given.)
  104.         internal ContextAwareResult(bool captureIdentity, bool forceCaptureContext, object myObject, object myState, AsyncCallback myCallBack) : this(captureIdentity, forceCaptureContext, false, myObject, myState, myCallBack)
  105.         {
  106.         }
  107.        
  108.         internal ContextAwareResult(bool captureIdentity, bool forceCaptureContext, bool threadSafeContextCopy, object myObject, object myState, AsyncCallback myCallBack) : base(myObject, myState, myCallBack)
  109.         {
  110.             if (forceCaptureContext) {
  111.                 _Flags = StateFlags.CaptureContext;
  112.             }
  113.            
  114.             if (captureIdentity) {
  115.                 _Flags |= StateFlags.CaptureIdentity;
  116.             }
  117.            
  118.             if (threadSafeContextCopy) {
  119.                 _Flags |= StateFlags.ThreadSafeContextCopy;
  120.             }
  121.         }
  122.        
  123.        
  124.         //
  125.         // This can be used to establish a context during an async op for something like calling a delegate or demanding a permission.
  126.         // May block briefly if the context is still being produced.
  127.         //
  128.         // Returns null if called from the posting thread.
  129.         //
  130.         internal ExecutionContext ContextCopy {
  131.             get {
  132.                 GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::ContextCopy|Called on completed result.", ValidationHelper.HashString(this));
  133.                 if (InternalPeekCompleted) {
  134.                     throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
  135.                 }
  136.                
  137.                 ExecutionContext context = _Context;
  138.                 if (context != null) {
  139.                     return context.CreateCopy();
  140.                 }
  141.                
  142.                 // Make sure the context was requested.
  143.                 GlobalLog.Assert(AsyncCallback != null || (_Flags & StateFlags.CaptureContext) != 0, "ContextAwareResult#{0}::ContextCopy|No context captured - specify a callback or forceCaptureContext.", ValidationHelper.HashString(this));
  144.                
  145.                 // Just use the lock to block. We might be on the thread that owns the lock which is great, it means we
  146.                 // don't need a context anyway.
  147.                 if ((_Flags & StateFlags.PostBlockFinished) == 0) {
  148.                     GlobalLog.Assert(_Lock != null, "ContextAwareResult#{0}::ContextCopy|Must lock (StartPostingAsyncOp()) { ... FinishPostingAsyncOp(); } when calling ContextCopy (unless it's only called after FinishPostingAsyncOp).", ValidationHelper.HashString(this));
  149.                     lock (_Lock) {
  150.                     }
  151.                 }
  152.                
  153.                 GlobalLog.Assert(!InternalPeekCompleted || (_Flags & StateFlags.ThreadSafeContextCopy) != 0, "ContextAwareResult#{0}::ContextCopy|Result became completed during call.", ValidationHelper.HashString(this));
  154.                 if (InternalPeekCompleted) {
  155.                     throw new InvalidOperationException(SR.GetString(SR.net_completed_result));
  156.                 }
  157.                
  158.                 context = _Context;
  159.                 return context == null ? null : context.CreateCopy();
  160.             }
  161.         }
  162.        
  163.        
  164.         #if DEBUG
  165.         // Want to be able to verify that the Identity was requested. If it was requested but isn't available
  166.         // on the Identity property, it's either available via ContextCopy or wasn't needed (synchronous).
  167.         internal bool IdentityRequested {
  168.             get { return (_Flags & StateFlags.CaptureIdentity) != 0; }
  169.         }
  170.         #endif
  171.        
  172.         internal object StartPostingAsyncOp()
  173.         {
  174.             return StartPostingAsyncOp(true);
  175.         }
  176.        
  177.         //
  178.         // If ContextCopy or Identity will be used, the return value should be locked until FinishPostingAsyncOp() is called
  179.         // or the operation has been aborted (e.g. by BeginXxx throwing). Otherwise, this can be called with false to prevent the lock
  180.         // object from being created.
  181.         //
  182.         internal object StartPostingAsyncOp(bool lockCapture)
  183.         {
  184.             GlobalLog.Assert(!InternalPeekCompleted, "ContextAwareResult#{0}::StartPostingAsyncOp|Called on completed result.", ValidationHelper.HashString(this));
  185.            
  186.             DebugProtectState(true);
  187.            
  188.             _Lock = lockCapture ? new object() : null;
  189.             _Flags |= StateFlags.PostBlockStarted;
  190.             return _Lock;
  191.         }
  192.        
  193.         //
  194.         // Call this when returning control to the user.
  195.         //
  196.         internal bool FinishPostingAsyncOp()
  197.         {
  198.             // Ignore this call if StartPostingAsyncOp() failed or wasn't called, or this has already been called.
  199.             if ((_Flags & (StateFlags.PostBlockStarted | StateFlags.PostBlockFinished)) != StateFlags.PostBlockStarted) {
  200.                 return false;
  201.             }
  202.             _Flags |= StateFlags.PostBlockFinished;
  203.            
  204.             ExecutionContext cachedContext = null;
  205.             return CaptureOrComplete(ref cachedContext, false);
  206.         }
  207.        
  208.         //
  209.         // Call this when returning control to the user. Allows a cached Callback Closure to be supplied and used
  210.         // as appropriate, and replaced with a new one.
  211.         //
  212.         internal bool FinishPostingAsyncOp(ref CallbackClosure closure)
  213.         {
  214.             // Ignore this call if StartPostingAsyncOp() failed or wasn't called, or this has already been called.
  215.             if ((_Flags & (StateFlags.PostBlockStarted | StateFlags.PostBlockFinished)) != StateFlags.PostBlockStarted) {
  216.                 return false;
  217.             }
  218.             _Flags |= StateFlags.PostBlockFinished;
  219.            
  220.             // Need a copy of this ref argument since it can be used in many of these calls simultaneously.
  221.             CallbackClosure closureCopy = closure;
  222.             ExecutionContext cachedContext;
  223.             if (closureCopy == null) {
  224.                 cachedContext = null;
  225.             }
  226.             else {
  227.                 if (!closureCopy.IsCompatible(AsyncCallback)) {
  228.                     // Clear the cache as soon as a method is called with incompatible parameters.
  229.                     closure = null;
  230.                     cachedContext = null;
  231.                 }
  232.                 else {
  233.                     // If it succeeded, we want to replace our context/callback with the one from the closure.
  234.                     // Using the closure's instance of the callback is probably overkill, but safer.
  235.                     AsyncCallback = closureCopy.AsyncCallback;
  236.                     cachedContext = closureCopy.Context;
  237.                 }
  238.             }
  239.            
  240.             bool calledCallback = CaptureOrComplete(ref cachedContext, true);
  241.            
  242.             // Set up new cached context if we didn't use the previous one.
  243.             if (closure == null && AsyncCallback != null && cachedContext != null) {
  244.                 closure = new CallbackClosure(cachedContext, AsyncCallback);
  245.             }
  246.            
  247.             return calledCallback;
  248.         }
  249.        
  250. /* enable when needed
  251.         //
  252.         // Use this to block until FinishPostingAsyncOp() completes.  Must check for null.
  253.         //
  254.         internal object PostingLock
  255.         {
  256.             get
  257.             {
  258.                 return _Lock;
  259.             }
  260.         }
  261.         //
  262.         // Call this if you want to cancel any flowing.
  263.         //
  264.         internal void InvokeCallbackWithoutContext(object result)
  265.         {
  266.             ProtectedInvokeCallback(result, (IntPtr) 1);
  267.         }
  268. */       
  269.        
  270.         //
  271.         protected override void Cleanup()
  272.         {
  273.             base.Cleanup();
  274.            
  275.             GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::Cleanup()");
  276.         }
  277.        
  278.         //
  279.         // This must be called right before returning the result to the user. It might call the callback itself,
  280.         // to avoid flowing context. Even if the operation completes before this call, the callback won't have been
  281.         // called.
  282.         //
  283.         // Returns whether the operation completed sync or not.
  284.         //
  285.         private bool CaptureOrComplete(ref ExecutionContext cachedContext, bool returnContext)
  286.         {
  287.             GlobalLog.Assert((_Flags & StateFlags.PostBlockStarted) != 0, "ContextAwareResult#{0}::CaptureOrComplete|Called without calling StartPostingAsyncOp.", ValidationHelper.HashString(this));
  288.            
  289.             // See if we're going to need to capture the context.
  290.             bool capturingContext = AsyncCallback != null || (_Flags & StateFlags.CaptureContext) != 0;
  291.            
  292.            
  293.             // No need to flow if there's no callback, unless it's been specifically requested.
  294.             // Note that Capture() can return null, for example if SuppressFlow() is in effect.
  295.             if (capturingContext && !InternalPeekCompleted) {
  296.                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() starting capture");
  297.                 if (cachedContext == null) {
  298.                     cachedContext = ExecutionContext.Capture();
  299.                 }
  300.                 if (cachedContext != null) {
  301.                     if (!returnContext) {
  302.                         _Context = cachedContext;
  303.                         cachedContext = null;
  304.                     }
  305.                     else {
  306.                         _Context = cachedContext.CreateCopy();
  307.                     }
  308.                 }
  309.                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() _Context:" + ValidationHelper.HashString(_Context));
  310.             }
  311.             else {
  312.                 // Otherwise we have to have completed synchronously, or not needed the context.
  313.                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() skipping capture");
  314.                 cachedContext = null;
  315.                 GlobalLog.Assert(AsyncCallback == null || CompletedSynchronously, "ContextAwareResult#{0}::CaptureOrComplete|Didn't capture context, but didn't complete synchronously!", ValidationHelper.HashString(this));
  316.             }
  317.            
  318.             // Now we want to see for sure what to do. We might have just captured the context for no reason.
  319.             // This has to be the first time the state has been queried "for real" (apart from InvokeCallback)
  320.             // to guarantee synchronization with Complete() (otherwise, Complete() could try to call the
  321.             // callback without the context having been gotten).
  322.             DebugProtectState(false);
  323.             if (CompletedSynchronously) {
  324.                 GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CaptureOrComplete() completing synchronously");
  325.                 base.Complete(IntPtr.Zero);
  326.                 return true;
  327.             }
  328.            
  329.             return false;
  330.         }
  331.        
  332.         //
  333.         // Is guaranteed to be called only once. If called with a non-zero userToken, the context is not flowed.
  334.         //
  335.         protected override void Complete(IntPtr userToken)
  336.         {
  337.             GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::Complete() _Context(set):" + (_Context != null).ToString() + " userToken:" + userToken.ToString());
  338.            
  339.             // If no flowing, just complete regularly.
  340.             if ((_Flags & StateFlags.PostBlockStarted) == 0) {
  341.                 base.Complete(userToken);
  342.                 return;
  343.             }
  344.            
  345.             // At this point, IsCompleted is set and CompletedSynchronously is fixed. If it's synchronous, then we want to hold
  346.             // the completion for the CaptureOrComplete() call to avoid the context flow. If not, we know CaptureOrComplete() has completed.
  347.             if (CompletedSynchronously) {
  348.                 return;
  349.             }
  350.            
  351.             ExecutionContext context = _Context;
  352.            
  353.             if (userToken != IntPtr.Zero || context == null) {
  354.                 base.Complete(userToken);
  355.                 return;
  356.             }
  357.            
  358.             ExecutionContext.Run((_Flags & StateFlags.ThreadSafeContextCopy) != 0 ? context.CreateCopy() : context, new ContextCallback(CompleteCallback), null);
  359.         }
  360.        
  361.         private void CompleteCallback(object state)
  362.         {
  363.             GlobalLog.Print("ContextAwareResult#" + ValidationHelper.HashString(this) + "::CompleteCallback() Context set, calling callback.");
  364.             base.Complete(IntPtr.Zero);
  365.         }
  366.     }
  367. }

Developer Fusion