1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 
5 namespace System.ServiceModel.Channels
6 {
7     using System.Runtime;
8     using System.Runtime.InteropServices;
9     using System.Security;
10     using System.Security.Permissions;
11     using System.Threading;
12 
OverlappedIOCompleteCallback(bool haveResult, int error, int bytesRead)13     delegate void OverlappedIOCompleteCallback(bool haveResult, int error, int bytesRead);
14 
15     unsafe class OverlappedContext
16     {
17         const int HandleOffsetFromOverlapped32 = -4;
18         const int HandleOffsetFromOverlapped64 = -3;
19 
20         static IOCompletionCallback completeCallback;
21         static WaitOrTimerCallback eventCallback;
22         static WaitOrTimerCallback cleanupCallback;
23         static byte[] dummyBuffer = new byte[0];
24 
25         object[] bufferHolder;
26         byte* bufferPtr;
27         NativeOverlapped* nativeOverlapped;
28         GCHandle pinnedHandle;
29         object pinnedTarget;
30         Overlapped overlapped;
31         RootedHolder rootedHolder;
32         OverlappedIOCompleteCallback pendingCallback;  // Null when no async I/O is pending.
33         bool deferredFree;
34         bool syncOperationPending;
35         ManualResetEvent completionEvent;
36         IntPtr eventHandle;
37 
38         // Only used by unbound I/O.
39         RegisteredWaitHandle registration;
40 
41 #if DEBUG_EXPENSIVE
42         StackTrace freeStack;
43 #endif
44 
45 
46         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
OverlappedContext()47         public OverlappedContext()
48         {
49             if (OverlappedContext.completeCallback == null)
50             {
51                 OverlappedContext.completeCallback = Fx.ThunkCallback(new IOCompletionCallback(CompleteCallback));
52             }
53             if (OverlappedContext.eventCallback == null)
54             {
55                 OverlappedContext.eventCallback = Fx.ThunkCallback(new WaitOrTimerCallback(EventCallback));
56             }
57             if (OverlappedContext.cleanupCallback == null)
58             {
59                 OverlappedContext.cleanupCallback = Fx.ThunkCallback(new WaitOrTimerCallback(CleanupCallback));
60             }
61 
62             this.bufferHolder = new object[] { OverlappedContext.dummyBuffer };
63             this.overlapped = new Overlapped();
64             this.nativeOverlapped = this.overlapped.UnsafePack(OverlappedContext.completeCallback, this.bufferHolder);
65 
66             // When replacing the buffer, we need to provoke the CLR to fix up the handle of the pin.
67             this.pinnedHandle = GCHandle.FromIntPtr(*((IntPtr*)nativeOverlapped +
68                 (IntPtr.Size == 4 ? HandleOffsetFromOverlapped32 : HandleOffsetFromOverlapped64)));
69             this.pinnedTarget = this.pinnedHandle.Target;
70 
71             // Create the permanently rooted holder and put it in the Overlapped.
72             this.rootedHolder = new RootedHolder();
73             this.overlapped.AsyncResult = rootedHolder;
74         }
75 
76         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
~OverlappedContext()77         ~OverlappedContext()
78         {
79             if (this.nativeOverlapped != null && !AppDomain.CurrentDomain.IsFinalizingForUnload() && !Environment.HasShutdownStarted)
80             {
81                 if (this.syncOperationPending)
82                 {
83                     Fx.Assert(this.rootedHolder != null, "rootedHolder null in Finalize.");
84                     Fx.Assert(this.rootedHolder.EventHolder != null, "rootedHolder.EventHolder null in Finalize.");
85                     Fx.Assert(OverlappedContext.cleanupCallback != null, "cleanupCallback null in Finalize.");
86 
87                     // Can't free the overlapped.  Register a callback to deal with this.
88                     // This will ressurect the OverlappedContext.
89                     // The completionEvent will still be alive (not finalized) since it's rooted by the pending Overlapped in the holder.
90                     // We own it now and will close it in the callback.
91                     ThreadPool.UnsafeRegisterWaitForSingleObject(this.rootedHolder.EventHolder, OverlappedContext.cleanupCallback, this, Timeout.Infinite, true);
92                 }
93                 else
94                 {
95                     Overlapped.Free(this.nativeOverlapped);
96                 }
97             }
98         }
99 
100         // None of the OverlappedContext methods are threadsafe.
101         // Free or FreeOrDefer can only be called once.  FreeIfDeferred can be called any number of times, as long as it's only
102         // called once after FreeOrDefer.
103         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
Free()104         public void Free()
105         {
106             if (this.pendingCallback != null)
107             {
108                 throw Fx.AssertAndThrow("OverlappedContext.Free called while async operation is pending.");
109             }
110             if (this.syncOperationPending)
111             {
112                 throw Fx.AssertAndThrow("OverlappedContext.Free called while sync operation is pending.");
113             }
114             if (this.nativeOverlapped == null)
115             {
116                 throw Fx.AssertAndThrow("OverlappedContext.Free called multiple times.");
117             }
118 
119 #if DEBUG_EXPENSIVE
120             this.freeStack = new StackTrace();
121 #endif
122 
123             // The OverlappedData is cached and reused.  It looks weird if there's still a reference to it form here.
124             this.pinnedTarget = null;
125 
126             NativeOverlapped* nativePtr = this.nativeOverlapped;
127             this.nativeOverlapped = null;
128             Overlapped.Free(nativePtr);
129 
130             if (this.completionEvent != null)
131             {
132                 this.completionEvent.Close();
133             }
134 
135             GC.SuppressFinalize(this);
136         }
137 
138         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
FreeOrDefer()139         public bool FreeOrDefer()
140         {
141             if (this.pendingCallback != null || this.syncOperationPending)
142             {
143                 this.deferredFree = true;
144                 return false;
145             }
146 
147             Free();
148             return true;
149         }
150 
151         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
FreeIfDeferred()152         public bool FreeIfDeferred()
153         {
154             if (this.deferredFree)
155             {
156                 return FreeOrDefer();
157             }
158 
159             return false;
160         }
161 
162         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
StartAsyncOperation(byte[] buffer, OverlappedIOCompleteCallback callback, bool bound)163         public void StartAsyncOperation(byte[] buffer, OverlappedIOCompleteCallback callback, bool bound)
164         {
165             if (callback == null)
166             {
167                 throw Fx.AssertAndThrow("StartAsyncOperation called with null callback.");
168             }
169             if (this.pendingCallback != null)
170             {
171                 throw Fx.AssertAndThrow("StartAsyncOperation called while another is in progress.");
172             }
173             if (this.syncOperationPending)
174             {
175                 throw Fx.AssertAndThrow("StartAsyncOperation called while a sync operation was already pending.");
176             }
177             if (this.nativeOverlapped == null)
178             {
179                 throw Fx.AssertAndThrow("StartAsyncOperation called on freed OverlappedContext.");
180             }
181 
182             this.pendingCallback = callback;
183 
184             if (buffer != null)
185             {
186                 Fx.Assert(object.ReferenceEquals(this.bufferHolder[0], OverlappedContext.dummyBuffer), "StartAsyncOperation: buffer holder corrupted.");
187                 this.bufferHolder[0] = buffer;
188                 this.pinnedHandle.Target = this.pinnedTarget;
189                 this.bufferPtr = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
190             }
191 
192             if (bound)
193             {
194                 this.overlapped.EventHandleIntPtr = IntPtr.Zero;
195 
196                 // For completion ports, the back-reference is this member.
197                 this.rootedHolder.ThisHolder = this;
198             }
199             else
200             {
201                 // Need to do this since we register the wait before posting the I/O.
202                 if (this.completionEvent != null)
203                 {
204                     this.completionEvent.Reset();
205                 }
206 
207                 this.overlapped.EventHandleIntPtr = EventHandle;
208 
209                 // For unbound, the back-reference is this registration.
210                 this.registration = ThreadPool.UnsafeRegisterWaitForSingleObject(this.completionEvent, OverlappedContext.eventCallback, this, Timeout.Infinite, true);
211             }
212         }
213 
214         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
CancelAsyncOperation()215         public void CancelAsyncOperation()
216         {
217             this.rootedHolder.ThisHolder = null;
218             if (this.registration != null)
219             {
220                 this.registration.Unregister(null);
221                 this.registration = null;
222             }
223             this.bufferPtr = null;
224             this.bufferHolder[0] = OverlappedContext.dummyBuffer;
225             this.pendingCallback = null;
226         }
227 
228         //  public void StartSyncOperation(byte[] buffer)
229         //  {
230         //      StartSyncOperation(buffer, ref this.bufferHolder[0], false);
231         //  }
232 
233         // The only holder allowed is Holder[0].  It can be passed in as a ref to prevent repeated expensive array lookups.
234         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
StartSyncOperation(byte[] buffer, ref object holder)235         public void StartSyncOperation(byte[] buffer, ref object holder)
236         {
237             if (this.syncOperationPending)
238             {
239                 throw Fx.AssertAndThrow("StartSyncOperation called while an operation was already pending.");
240             }
241             if (this.pendingCallback != null)
242             {
243                 throw Fx.AssertAndThrow("StartSyncOperation called while an async operation was already pending.");
244             }
245             if (this.nativeOverlapped == null)
246             {
247                 throw Fx.AssertAndThrow("StartSyncOperation called on freed OverlappedContext.");
248             }
249 
250             this.overlapped.EventHandleIntPtr = EventHandle;
251 
252             // Sync operations do NOT root this object.  If it gets finalized, we need to know not to free the buffer.
253             // We do root the event.
254             this.rootedHolder.EventHolder = this.completionEvent;
255             this.syncOperationPending = true;
256 
257             if (buffer != null)
258             {
259                 Fx.Assert(object.ReferenceEquals(holder, OverlappedContext.dummyBuffer), "StartSyncOperation: buffer holder corrupted.");
260                 holder = buffer;
261                 this.pinnedHandle.Target = this.pinnedTarget;
262                 this.bufferPtr = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
263             }
264         }
265 
266         // If this returns false, the OverlappedContext is no longer usable.  It shouldn't be freed or anything.
267         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
WaitForSyncOperation(TimeSpan timeout)268         public bool WaitForSyncOperation(TimeSpan timeout)
269         {
270             return WaitForSyncOperation(timeout, ref this.bufferHolder[0]);
271         }
272 
273         // The only holder allowed is Holder[0].  It can be passed in as a ref to prevent repeated expensive array lookups.
274         [SecurityCritical]
WaitForSyncOperation(TimeSpan timeout, ref object holder)275         public bool WaitForSyncOperation(TimeSpan timeout, ref object holder)
276         {
277             if (!this.syncOperationPending)
278             {
279                 throw Fx.AssertAndThrow("WaitForSyncOperation called while no operation was pending.");
280             }
281 
282             if (!UnsafeNativeMethods.HasOverlappedIoCompleted(this.nativeOverlapped))
283             {
284                 if (!TimeoutHelper.WaitOne(this.completionEvent, timeout))
285                 {
286                     // We can't free ourselves until the operation is done.  The only way to do that is register a callback.
287                     // This will root the object.  No longer any need for the finalizer.  This instance is unusable after this.
288                     GC.SuppressFinalize(this);
289                     ThreadPool.UnsafeRegisterWaitForSingleObject(this.completionEvent, OverlappedContext.cleanupCallback, this, Timeout.Infinite, true);
290                     return false;
291                 }
292             }
293 
294             Fx.Assert(this.bufferPtr == null || this.bufferPtr == (byte*)Marshal.UnsafeAddrOfPinnedArrayElement((byte[])holder, 0),
295                 "The buffer moved during a sync call!");
296 
297             CancelSyncOperation(ref holder);
298             return true;
299         }
300 
301         //  public void CancelSyncOperation()
302         //  {
303         //      CancelSyncOperation(ref this.bufferHolder[0]);
304         //  }
305 
306         // The only holder allowed is Holder[0].  It can be passed in as a ref to prevent repeated expensive array lookups.
307         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
CancelSyncOperation(ref object holder)308         public void CancelSyncOperation(ref object holder)
309         {
310             this.bufferPtr = null;
311             holder = OverlappedContext.dummyBuffer;
312             Fx.Assert(object.ReferenceEquals(this.bufferHolder[0], OverlappedContext.dummyBuffer), "Bad holder passed to CancelSyncOperation.");
313 
314             this.syncOperationPending = false;
315             this.rootedHolder.EventHolder = null;
316         }
317 
318         // This should ONLY be used to make a 'ref object' parameter to the zeroth element, to prevent repeated expensive array lookups.
319         public object[] Holder
320         {
321             [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
322             get
323             {
324                 return this.bufferHolder;
325             }
326         }
327 
328         public byte* BufferPtr
329         {
330             [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
331             get
332             {
333                 byte* ptr = this.bufferPtr;
334                 if (ptr == null)
335                 {
336 #pragma warning suppress 56503 // Microsoft, not a publicly accessible API
337                     throw Fx.AssertAndThrow("Pointer requested while no operation pending or no buffer provided.");
338                 }
339                 return ptr;
340             }
341         }
342 
343         public NativeOverlapped* NativeOverlapped
344         {
345             [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
346             get
347             {
348                 NativeOverlapped* ptr = this.nativeOverlapped;
349                 if (ptr == null)
350                 {
351 #pragma warning suppress 56503 // Microsoft, not a publicly accessible API
352                     throw Fx.AssertAndThrow("NativeOverlapped pointer requested after it was freed.");
353                 }
354                 return ptr;
355             }
356         }
357 
358         IntPtr EventHandle
359         {
360             get
361             {
362                 if (this.completionEvent == null)
363                 {
364                     this.completionEvent = new ManualResetEvent(false);
365                     this.eventHandle = (IntPtr)(1 | (long)this.completionEvent.SafeWaitHandle.DangerousGetHandle());
366                 }
367                 return this.eventHandle;
368             }
369         }
370 
371         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
CompleteCallback(uint error, uint numBytes, NativeOverlapped* nativeOverlapped)372         static void CompleteCallback(uint error, uint numBytes, NativeOverlapped* nativeOverlapped)
373         {
374             // Empty out the AsyncResult ASAP to close the leak window.
375             Overlapped overlapped = Overlapped.Unpack(nativeOverlapped);
376             OverlappedContext pThis = ((RootedHolder)overlapped.AsyncResult).ThisHolder;
377             Fx.Assert(pThis != null, "Overlapped.AsyncResult not set. I/O completed multiple times, or cancelled I/O completed.");
378             Fx.Assert(object.ReferenceEquals(pThis.overlapped, overlapped), "CompleteCallback completed with corrupt OverlappedContext.overlapped.");
379             Fx.Assert(object.ReferenceEquals(pThis.rootedHolder, overlapped.AsyncResult), "CompleteCallback completed with corrupt OverlappedContext.rootedHolder.");
380             pThis.rootedHolder.ThisHolder = null;
381 
382             Fx.Assert(pThis.bufferPtr == null || pThis.bufferPtr == (byte*)Marshal.UnsafeAddrOfPinnedArrayElement((byte[])pThis.bufferHolder[0], 0),
383                 "Buffer moved during bound async operation!");
384 
385             // Release the pin.
386             pThis.bufferPtr = null;
387             pThis.bufferHolder[0] = OverlappedContext.dummyBuffer;
388 
389             OverlappedIOCompleteCallback callback = pThis.pendingCallback;
390             pThis.pendingCallback = null;
391             Fx.Assert(callback != null, "PendingCallback not set. I/O completed multiple times, or cancelled I/O completed.");
392 
393             callback(true, (int)error, checked((int)numBytes));
394         }
395 
396         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
EventCallback(object state, bool timedOut)397         static void EventCallback(object state, bool timedOut)
398         {
399             OverlappedContext pThis = state as OverlappedContext;
400             Fx.Assert(pThis != null, "OverlappedContext.EventCallback registered wait doesn't have an OverlappedContext as state.");
401 
402             if (timedOut)
403             {
404                 Fx.Assert("OverlappedContext.EventCallback registered wait timed out.");
405 
406                 // Turn this into a leak.  Don't let ourselves get cleaned up - could scratch the heap.
407                 if (pThis == null || pThis.rootedHolder == null)
408                 {
409                     // We're doomed to do a wild write and corrupt the process.
410                     DiagnosticUtility.FailFast("Can't prevent heap corruption.");
411                 }
412                 pThis.rootedHolder.ThisHolder = pThis;
413                 return;
414             }
415 
416             pThis.registration = null;
417 
418             Fx.Assert(pThis.bufferPtr == null || pThis.bufferPtr == (byte*)Marshal.UnsafeAddrOfPinnedArrayElement((byte[])pThis.bufferHolder[0], 0),
419                 "Buffer moved during unbound async operation!");
420 
421             // Release the pin.
422             pThis.bufferPtr = null;
423             pThis.bufferHolder[0] = OverlappedContext.dummyBuffer;
424 
425             OverlappedIOCompleteCallback callback = pThis.pendingCallback;
426             pThis.pendingCallback = null;
427             Fx.Assert(callback != null, "PendingCallback not set. I/O completed multiple times, or cancelled I/O completed.");
428 
429             callback(false, 0, 0);
430         }
431 
432         [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
CleanupCallback(object state, bool timedOut)433         static void CleanupCallback(object state, bool timedOut)
434         {
435             OverlappedContext pThis = state as OverlappedContext;
436             Fx.Assert(pThis != null, "OverlappedContext.CleanupCallback registered wait doesn't have an OverlappedContext as state.");
437 
438             if (timedOut)
439             {
440                 Fx.Assert("OverlappedContext.CleanupCallback registered wait timed out.");
441 
442                 // Turn this into a leak.
443                 return;
444             }
445 
446             Fx.Assert(pThis.bufferPtr == null || pThis.bufferPtr == (byte*)Marshal.UnsafeAddrOfPinnedArrayElement((byte[])pThis.bufferHolder[0], 0),
447                 "Buffer moved during synchronous deferred cleanup!");
448 
449             Fx.Assert(pThis.syncOperationPending, "OverlappedContext.CleanupCallback called with no sync operation pending.");
450             pThis.pinnedTarget = null;
451             pThis.rootedHolder.EventHolder.Close();
452             Overlapped.Free(pThis.nativeOverlapped);
453         }
454 
455         // This class is always held onto (rooted) by the packed Overlapped.  The OverlappedContext instance moves itself in and out of
456         // this object to root itself.  It's also used to root the ManualResetEvent during sync operations.
457         // It needs to be an IAsyncResult since that's what Overlapped takes.
458         class RootedHolder : IAsyncResult
459         {
460             OverlappedContext overlappedBuffer;
461             ManualResetEvent eventHolder;
462 
463 
464             public OverlappedContext ThisHolder
465             {
466                 get
467                 {
468                     return this.overlappedBuffer;
469                 }
470 
471                 set
472                 {
473                     this.overlappedBuffer = value;
474                 }
475             }
476 
477             public ManualResetEvent EventHolder
478             {
479                 get
480                 {
481                     return this.eventHolder;
482                 }
483 
484                 set
485                 {
486                     this.eventHolder = value;
487                 }
488             }
489 
490 
491             // Unused IAsyncResult implementation.
492             object IAsyncResult.AsyncState
493             {
494                 get
495                 {
496 #pragma warning suppress 56503 // Microsoft, not a publicly accessible API
497                     throw Fx.AssertAndThrow("RootedHolder.AsyncState called.");
498                 }
499             }
500 
501             WaitHandle IAsyncResult.AsyncWaitHandle
502             {
503                 get
504                 {
505 #pragma warning suppress 56503 // Microsoft, not a publicly accessible API
506                     throw Fx.AssertAndThrow("RootedHolder.AsyncWaitHandle called.");
507                 }
508             }
509 
510             bool IAsyncResult.CompletedSynchronously
511             {
512                 get
513                 {
514 #pragma warning suppress 56503 // Microsoft, not a publicly accessible API
515                     throw Fx.AssertAndThrow("RootedHolder.CompletedSynchronously called.");
516                 }
517             }
518 
519             bool IAsyncResult.IsCompleted
520             {
521                 get
522                 {
523 #pragma warning suppress 56503 // Microsoft, not a publicly accessible API
524                     throw Fx.AssertAndThrow("RootedHolder.IsCompleted called.");
525                 }
526             }
527         }
528     }
529 }
530