You've already forked linux-packaging-mono
474 lines
20 KiB
C#
474 lines
20 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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);
|
|
}
|
|
}
|
|
}
|