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