2016-08-03 10:59:49 +00:00
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace System.ServiceModel.Channels
{
using System.Runtime ;
using System.Runtime.InteropServices ;
using System.Security ;
using System.Security.Permissions ;
using System.Threading ;
delegate void OverlappedIOCompleteCallback ( bool haveResult , int error , int bytesRead ) ;
unsafe class OverlappedContext
{
const int HandleOffsetFromOverlapped32 = - 4 ;
const int HandleOffsetFromOverlapped64 = - 3 ;
static IOCompletionCallback completeCallback ;
static WaitOrTimerCallback eventCallback ;
static WaitOrTimerCallback cleanupCallback ;
static byte [ ] dummyBuffer = new byte [ 0 ] ;
object [ ] bufferHolder ;
byte * bufferPtr ;
NativeOverlapped * nativeOverlapped ;
GCHandle pinnedHandle ;
object pinnedTarget ;
Overlapped overlapped ;
RootedHolder rootedHolder ;
OverlappedIOCompleteCallback pendingCallback ; // Null when no async I/O is pending.
bool deferredFree ;
bool syncOperationPending ;
ManualResetEvent completionEvent ;
IntPtr eventHandle ;
// Only used by unbound I/O.
RegisteredWaitHandle registration ;
#if DEBUG_EXPENSIVE
StackTrace freeStack ;
#endif
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public OverlappedContext ( )
{
if ( OverlappedContext . completeCallback = = null )
{
OverlappedContext . completeCallback = Fx . ThunkCallback ( new IOCompletionCallback ( CompleteCallback ) ) ;
}
if ( OverlappedContext . eventCallback = = null )
{
OverlappedContext . eventCallback = Fx . ThunkCallback ( new WaitOrTimerCallback ( EventCallback ) ) ;
}
if ( OverlappedContext . cleanupCallback = = null )
{
OverlappedContext . cleanupCallback = Fx . ThunkCallback ( new WaitOrTimerCallback ( CleanupCallback ) ) ;
}
this . bufferHolder = new object [ ] { OverlappedContext . dummyBuffer } ;
this . overlapped = new Overlapped ( ) ;
this . nativeOverlapped = this . overlapped . UnsafePack ( OverlappedContext . completeCallback , this . bufferHolder ) ;
// When replacing the buffer, we need to provoke the CLR to fix up the handle of the pin.
this . pinnedHandle = GCHandle . FromIntPtr ( * ( ( IntPtr * ) nativeOverlapped +
( IntPtr . Size = = 4 ? HandleOffsetFromOverlapped32 : HandleOffsetFromOverlapped64 ) ) ) ;
this . pinnedTarget = this . pinnedHandle . Target ;
// Create the permanently rooted holder and put it in the Overlapped.
this . rootedHolder = new RootedHolder ( ) ;
this . overlapped . AsyncResult = rootedHolder ;
}
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
~ OverlappedContext ( )
{
if ( this . nativeOverlapped ! = null & & ! AppDomain . CurrentDomain . IsFinalizingForUnload ( ) & & ! Environment . HasShutdownStarted )
{
if ( this . syncOperationPending )
{
Fx . Assert ( this . rootedHolder ! = null , "rootedHolder null in Finalize." ) ;
Fx . Assert ( this . rootedHolder . EventHolder ! = null , "rootedHolder.EventHolder null in Finalize." ) ;
Fx . Assert ( OverlappedContext . cleanupCallback ! = null , "cleanupCallback null in Finalize." ) ;
// Can't free the overlapped. Register a callback to deal with this.
// This will ressurect the OverlappedContext.
// The completionEvent will still be alive (not finalized) since it's rooted by the pending Overlapped in the holder.
// We own it now and will close it in the callback.
ThreadPool . UnsafeRegisterWaitForSingleObject ( this . rootedHolder . EventHolder , OverlappedContext . cleanupCallback , this , Timeout . Infinite , true ) ;
}
else
{
Overlapped . Free ( this . nativeOverlapped ) ;
}
}
}
// None of the OverlappedContext methods are threadsafe.
// Free or FreeOrDefer can only be called once. FreeIfDeferred can be called any number of times, as long as it's only
// called once after FreeOrDefer.
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public void Free ( )
{
if ( this . pendingCallback ! = null )
{
throw Fx . AssertAndThrow ( "OverlappedContext.Free called while async operation is pending." ) ;
}
if ( this . syncOperationPending )
{
2017-08-21 15:34:15 +00:00
throw Fx . AssertAndThrow ( "OverlappedContext.Free called while sync operation is pending." ) ;
2016-08-03 10:59:49 +00:00
}
if ( this . nativeOverlapped = = null )
{
throw Fx . AssertAndThrow ( "OverlappedContext.Free called multiple times." ) ;
}
#if DEBUG_EXPENSIVE
this . freeStack = new StackTrace ( ) ;
#endif
// The OverlappedData is cached and reused. It looks weird if there's still a reference to it form here.
this . pinnedTarget = null ;
NativeOverlapped * nativePtr = this . nativeOverlapped ;
this . nativeOverlapped = null ;
Overlapped . Free ( nativePtr ) ;
if ( this . completionEvent ! = null )
{
this . completionEvent . Close ( ) ;
}
GC . SuppressFinalize ( this ) ;
}
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public bool FreeOrDefer ( )
{
if ( this . pendingCallback ! = null | | this . syncOperationPending )
{
this . deferredFree = true ;
return false ;
}
Free ( ) ;
return true ;
}
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public bool FreeIfDeferred ( )
{
if ( this . deferredFree )
{
return FreeOrDefer ( ) ;
}
return false ;
}
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public void StartAsyncOperation ( byte [ ] buffer , OverlappedIOCompleteCallback callback , bool bound )
{
if ( callback = = null )
{
throw Fx . AssertAndThrow ( "StartAsyncOperation called with null callback." ) ;
}
if ( this . pendingCallback ! = null )
{
throw Fx . AssertAndThrow ( "StartAsyncOperation called while another is in progress." ) ;
}
if ( this . syncOperationPending )
{
2017-08-21 15:34:15 +00:00
throw Fx . AssertAndThrow ( "StartAsyncOperation called while a sync operation was already pending." ) ;
2016-08-03 10:59:49 +00:00
}
if ( this . nativeOverlapped = = null )
{
throw Fx . AssertAndThrow ( "StartAsyncOperation called on freed OverlappedContext." ) ;
}
this . pendingCallback = callback ;
if ( buffer ! = null )
{
Fx . Assert ( object . ReferenceEquals ( this . bufferHolder [ 0 ] , OverlappedContext . dummyBuffer ) , "StartAsyncOperation: buffer holder corrupted." ) ;
this . bufferHolder [ 0 ] = buffer ;
this . pinnedHandle . Target = this . pinnedTarget ;
this . bufferPtr = ( byte * ) Marshal . UnsafeAddrOfPinnedArrayElement ( buffer , 0 ) ;
}
if ( bound )
{
this . overlapped . EventHandleIntPtr = IntPtr . Zero ;
// For completion ports, the back-reference is this member.
this . rootedHolder . ThisHolder = this ;
}
else
{
// Need to do this since we register the wait before posting the I/O.
if ( this . completionEvent ! = null )
{
this . completionEvent . Reset ( ) ;
}
this . overlapped . EventHandleIntPtr = EventHandle ;
// For unbound, the back-reference is this registration.
this . registration = ThreadPool . UnsafeRegisterWaitForSingleObject ( this . completionEvent , OverlappedContext . eventCallback , this , Timeout . Infinite , true ) ;
}
}
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public void CancelAsyncOperation ( )
{
this . rootedHolder . ThisHolder = null ;
if ( this . registration ! = null )
{
this . registration . Unregister ( null ) ;
this . registration = null ;
}
this . bufferPtr = null ;
this . bufferHolder [ 0 ] = OverlappedContext . dummyBuffer ;
this . pendingCallback = null ;
}
// public void StartSyncOperation(byte[] buffer)
// {
// StartSyncOperation(buffer, ref this.bufferHolder[0], false);
// }
// The only holder allowed is Holder[0]. It can be passed in as a ref to prevent repeated expensive array lookups.
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public void StartSyncOperation ( byte [ ] buffer , ref object holder )
{
if ( this . syncOperationPending )
{
throw Fx . AssertAndThrow ( "StartSyncOperation called while an operation was already pending." ) ;
}
if ( this . pendingCallback ! = null )
{
throw Fx . AssertAndThrow ( "StartSyncOperation called while an async operation was already pending." ) ;
}
if ( this . nativeOverlapped = = null )
{
throw Fx . AssertAndThrow ( "StartSyncOperation called on freed OverlappedContext." ) ;
}
this . overlapped . EventHandleIntPtr = EventHandle ;
2017-08-21 15:34:15 +00:00
// Sync operations do NOT root this object. If it gets finalized, we need to know not to free the buffer.
2016-08-03 10:59:49 +00:00
// We do root the event.
this . rootedHolder . EventHolder = this . completionEvent ;
this . syncOperationPending = true ;
if ( buffer ! = null )
{
Fx . Assert ( object . ReferenceEquals ( holder , OverlappedContext . dummyBuffer ) , "StartSyncOperation: buffer holder corrupted." ) ;
holder = buffer ;
this . pinnedHandle . Target = this . pinnedTarget ;
this . bufferPtr = ( byte * ) Marshal . UnsafeAddrOfPinnedArrayElement ( buffer , 0 ) ;
}
}
// If this returns false, the OverlappedContext is no longer usable. It shouldn't be freed or anything.
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public bool WaitForSyncOperation ( TimeSpan timeout )
{
return WaitForSyncOperation ( timeout , ref this . bufferHolder [ 0 ] ) ;
}
// The only holder allowed is Holder[0]. It can be passed in as a ref to prevent repeated expensive array lookups.
[SecurityCritical]
public bool WaitForSyncOperation ( TimeSpan timeout , ref object holder )
{
if ( ! this . syncOperationPending )
{
throw Fx . AssertAndThrow ( "WaitForSyncOperation called while no operation was pending." ) ;
}
if ( ! UnsafeNativeMethods . HasOverlappedIoCompleted ( this . nativeOverlapped ) )
{
if ( ! TimeoutHelper . WaitOne ( this . completionEvent , timeout ) )
{
// We can't free ourselves until the operation is done. The only way to do that is register a callback.
// This will root the object. No longer any need for the finalizer. This instance is unusable after this.
GC . SuppressFinalize ( this ) ;
ThreadPool . UnsafeRegisterWaitForSingleObject ( this . completionEvent , OverlappedContext . cleanupCallback , this , Timeout . Infinite , true ) ;
return false ;
}
}
Fx . Assert ( this . bufferPtr = = null | | this . bufferPtr = = ( byte * ) Marshal . UnsafeAddrOfPinnedArrayElement ( ( byte [ ] ) holder , 0 ) ,
2017-08-21 15:34:15 +00:00
"The buffer moved during a sync call!" ) ;
2016-08-03 10:59:49 +00:00
CancelSyncOperation ( ref holder ) ;
return true ;
}
// public void CancelSyncOperation()
// {
// CancelSyncOperation(ref this.bufferHolder[0]);
// }
// The only holder allowed is Holder[0]. It can be passed in as a ref to prevent repeated expensive array lookups.
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
public void CancelSyncOperation ( ref object holder )
{
this . bufferPtr = null ;
holder = OverlappedContext . dummyBuffer ;
Fx . Assert ( object . ReferenceEquals ( this . bufferHolder [ 0 ] , OverlappedContext . dummyBuffer ) , "Bad holder passed to CancelSyncOperation." ) ;
this . syncOperationPending = false ;
this . rootedHolder . EventHolder = null ;
}
// This should ONLY be used to make a 'ref object' parameter to the zeroth element, to prevent repeated expensive array lookups.
public object [ ] Holder
{
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
get
{
return this . bufferHolder ;
}
}
public byte * BufferPtr
{
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
get
{
byte * ptr = this . bufferPtr ;
if ( ptr = = null )
{
2017-08-21 15:34:15 +00:00
#pragma warning suppress 56503 // Microsoft, not a publicly accessible API
2016-08-03 10:59:49 +00:00
throw Fx . AssertAndThrow ( "Pointer requested while no operation pending or no buffer provided." ) ;
}
return ptr ;
}
}
public NativeOverlapped * NativeOverlapped
{
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
get
{
NativeOverlapped * ptr = this . nativeOverlapped ;
if ( ptr = = null )
{
2017-08-21 15:34:15 +00:00
#pragma warning suppress 56503 // Microsoft, not a publicly accessible API
2016-08-03 10:59:49 +00:00
throw Fx . AssertAndThrow ( "NativeOverlapped pointer requested after it was freed." ) ;
}
return ptr ;
}
}
IntPtr EventHandle
{
get
{
if ( this . completionEvent = = null )
{
this . completionEvent = new ManualResetEvent ( false ) ;
this . eventHandle = ( IntPtr ) ( 1 | ( long ) this . completionEvent . SafeWaitHandle . DangerousGetHandle ( ) ) ;
}
return this . eventHandle ;
}
}
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
static void CompleteCallback ( uint error , uint numBytes , NativeOverlapped * nativeOverlapped )
{
// Empty out the AsyncResult ASAP to close the leak window.
Overlapped overlapped = Overlapped . Unpack ( nativeOverlapped ) ;
OverlappedContext pThis = ( ( RootedHolder ) overlapped . AsyncResult ) . ThisHolder ;
Fx . Assert ( pThis ! = null , "Overlapped.AsyncResult not set. I/O completed multiple times, or cancelled I/O completed." ) ;
Fx . Assert ( object . ReferenceEquals ( pThis . overlapped , overlapped ) , "CompleteCallback completed with corrupt OverlappedContext.overlapped." ) ;
Fx . Assert ( object . ReferenceEquals ( pThis . rootedHolder , overlapped . AsyncResult ) , "CompleteCallback completed with corrupt OverlappedContext.rootedHolder." ) ;
pThis . rootedHolder . ThisHolder = null ;
Fx . Assert ( pThis . bufferPtr = = null | | pThis . bufferPtr = = ( byte * ) Marshal . UnsafeAddrOfPinnedArrayElement ( ( byte [ ] ) pThis . bufferHolder [ 0 ] , 0 ) ,
"Buffer moved during bound async operation!" ) ;
// Release the pin.
pThis . bufferPtr = null ;
pThis . bufferHolder [ 0 ] = OverlappedContext . dummyBuffer ;
OverlappedIOCompleteCallback callback = pThis . pendingCallback ;
pThis . pendingCallback = null ;
Fx . Assert ( callback ! = null , "PendingCallback not set. I/O completed multiple times, or cancelled I/O completed." ) ;
callback ( true , ( int ) error , checked ( ( int ) numBytes ) ) ;
}
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
static void EventCallback ( object state , bool timedOut )
{
OverlappedContext pThis = state as OverlappedContext ;
Fx . Assert ( pThis ! = null , "OverlappedContext.EventCallback registered wait doesn't have an OverlappedContext as state." ) ;
if ( timedOut )
{
Fx . Assert ( "OverlappedContext.EventCallback registered wait timed out." ) ;
// Turn this into a leak. Don't let ourselves get cleaned up - could scratch the heap.
if ( pThis = = null | | pThis . rootedHolder = = null )
{
// We're doomed to do a wild write and corrupt the process.
DiagnosticUtility . FailFast ( "Can't prevent heap corruption." ) ;
}
pThis . rootedHolder . ThisHolder = pThis ;
return ;
}
pThis . registration = null ;
Fx . Assert ( pThis . bufferPtr = = null | | pThis . bufferPtr = = ( byte * ) Marshal . UnsafeAddrOfPinnedArrayElement ( ( byte [ ] ) pThis . bufferHolder [ 0 ] , 0 ) ,
"Buffer moved during unbound async operation!" ) ;
// Release the pin.
pThis . bufferPtr = null ;
pThis . bufferHolder [ 0 ] = OverlappedContext . dummyBuffer ;
OverlappedIOCompleteCallback callback = pThis . pendingCallback ;
pThis . pendingCallback = null ;
Fx . Assert ( callback ! = null , "PendingCallback not set. I/O completed multiple times, or cancelled I/O completed." ) ;
callback ( false , 0 , 0 ) ;
}
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
static void CleanupCallback ( object state , bool timedOut )
{
OverlappedContext pThis = state as OverlappedContext ;
Fx . Assert ( pThis ! = null , "OverlappedContext.CleanupCallback registered wait doesn't have an OverlappedContext as state." ) ;
if ( timedOut )
{
Fx . Assert ( "OverlappedContext.CleanupCallback registered wait timed out." ) ;
// Turn this into a leak.
return ;
}
Fx . Assert ( pThis . bufferPtr = = null | | pThis . bufferPtr = = ( byte * ) Marshal . UnsafeAddrOfPinnedArrayElement ( ( byte [ ] ) pThis . bufferHolder [ 0 ] , 0 ) ,
"Buffer moved during synchronous deferred cleanup!" ) ;
2017-08-21 15:34:15 +00:00
Fx . Assert ( pThis . syncOperationPending , "OverlappedContext.CleanupCallback called with no sync operation pending." ) ;
2016-08-03 10:59:49 +00:00
pThis . pinnedTarget = null ;
pThis . rootedHolder . EventHolder . Close ( ) ;
Overlapped . Free ( pThis . nativeOverlapped ) ;
}
// This class is always held onto (rooted) by the packed Overlapped. The OverlappedContext instance moves itself in and out of
2017-08-21 15:34:15 +00:00
// this object to root itself. It's also used to root the ManualResetEvent during sync operations.
2016-08-03 10:59:49 +00:00
// It needs to be an IAsyncResult since that's what Overlapped takes.
class RootedHolder : IAsyncResult
{
OverlappedContext overlappedBuffer ;
ManualResetEvent eventHolder ;
public OverlappedContext ThisHolder
{
get
{
return this . overlappedBuffer ;
}
set
{
this . overlappedBuffer = value ;
}
}
public ManualResetEvent EventHolder
{
get
{
return this . eventHolder ;
}
set
{
this . eventHolder = value ;
}
}
// Unused IAsyncResult implementation.
object IAsyncResult . AsyncState
{
get
{
2017-08-21 15:34:15 +00:00
#pragma warning suppress 56503 // Microsoft, not a publicly accessible API
2016-08-03 10:59:49 +00:00
throw Fx . AssertAndThrow ( "RootedHolder.AsyncState called." ) ;
}
}
WaitHandle IAsyncResult . AsyncWaitHandle
{
get
{
2017-08-21 15:34:15 +00:00
#pragma warning suppress 56503 // Microsoft, not a publicly accessible API
2016-08-03 10:59:49 +00:00
throw Fx . AssertAndThrow ( "RootedHolder.AsyncWaitHandle called." ) ;
}
}
bool IAsyncResult . CompletedSynchronously
{
get
{
2017-08-21 15:34:15 +00:00
#pragma warning suppress 56503 // Microsoft, not a publicly accessible API
2016-08-03 10:59:49 +00:00
throw Fx . AssertAndThrow ( "RootedHolder.CompletedSynchronously called." ) ;
}
}
bool IAsyncResult . IsCompleted
{
get
{
2017-08-21 15:34:15 +00:00
#pragma warning suppress 56503 // Microsoft, not a publicly accessible API
2016-08-03 10:59:49 +00:00
throw Fx . AssertAndThrow ( "RootedHolder.IsCompleted called." ) ;
}
}
}
}
}