source: TFP-WebServer/SpaceWizards.HttpListener/src/System/Net/LazyAsyncResult.cs

Last change on this file was 377, checked in by alloc, 2 years ago

Made SpaceWizards.HttpListener compilable against .NET 4.8 with preprocessor define UNITY_NETFRAMEWORK

File size: 17.6 KB
Line 
1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3
4#nullable enable
5// ReSharper disable ConstantConditionalAccessQualifier
6
7using System.Diagnostics;
8using System.Threading;
9using System.Threading.Tasks;
10
11namespace System.Net
12{
13 // LazyAsyncResult - Base class for all IAsyncResult classes that want to take advantage of
14 // lazily-allocated event handles.
15 internal class LazyAsyncResult : IAsyncResult
16 {
17 private const int HighBit = unchecked((int)0x80000000);
18 private const int ForceAsyncCount = 50;
19
20 // This is to avoid user mistakes when they queue another async op from a callback the completes sync.
21 [ThreadStatic]
22 private static ThreadContext? t_threadContext;
23
24 private static ThreadContext CurrentThreadContext
25 {
26 get
27 {
28 ThreadContext? threadContext = t_threadContext;
29 if (threadContext == null)
30 {
31 threadContext = new ThreadContext();
32 t_threadContext = threadContext;
33 }
34
35 return threadContext;
36 }
37 }
38
39 private class ThreadContext
40 {
41 internal int _nestedIOCount;
42 }
43
44#if DEBUG
45 private bool _protectState; // Used by ContextAwareResult to prevent some calls.
46#endif
47
48 private readonly object? _asyncObject; // Caller's async object.
49 private readonly object? _asyncState; // Caller's state object.
50 private AsyncCallback? _asyncCallback; // Caller's callback method.
51 private object? _result; // Final IO result to be returned byt the End*() method.
52 private int _errorCode; // Win32 error code for Win32 IO async calls (that want to throw).
53 private int _intCompleted; // Sign bit indicates synchronous completion if set.
54 // Remaining bits count the number of InvokeCallbak() calls.
55
56 private bool _endCalled; // True if the user called the End*() method.
57 private bool _userEvent; // True if the event has been (or is about to be) handed to the user
58
59 private object? _event; // Lazy allocated event to be returned in the IAsyncResult for the client to wait on.
60
61 internal LazyAsyncResult(object? myObject, object? myState, AsyncCallback? myCallBack)
62 {
63 _asyncObject = myObject;
64 _asyncState = myState;
65 _asyncCallback = myCallBack;
66 _result = DBNull.Value;
67 if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this);
68 }
69
70 // Interface method to return the original async object.
71 internal object? AsyncObject
72 {
73 get
74 {
75 return _asyncObject;
76 }
77 }
78
79 // Interface method to return the caller's state object.
80 public object? AsyncState
81 {
82 get
83 {
84 return _asyncState;
85 }
86 }
87
88 protected AsyncCallback? AsyncCallback
89 {
90 get
91 {
92 return _asyncCallback;
93 }
94
95 set
96 {
97 _asyncCallback = value;
98 }
99 }
100
101 // Interface property to return a WaitHandle that can be waited on for I/O completion.
102 //
103 // This property implements lazy event creation.
104 //
105 // If this is used, the event cannot be disposed because it is under the control of the
106 // application. Internal should use InternalWaitForCompletion instead - never AsyncWaitHandle.
107 public WaitHandle AsyncWaitHandle
108 {
109 get
110 {
111#if DEBUG
112 // Can't be called when state is protected.
113 if (_protectState)
114 {
115 throw new InvalidOperationException("get_AsyncWaitHandle called in protected state");
116 }
117#endif
118
119 // Indicates that the user has seen the event; it can't be disposed.
120 _userEvent = true;
121
122 // The user has access to this object. Lock-in CompletedSynchronously.
123 if (_intCompleted == 0)
124 {
125 Interlocked.CompareExchange(ref _intCompleted, HighBit, 0);
126 }
127
128 // Because InternalWaitForCompletion() tries to dispose this event, it's
129 // possible for _event to become null immediately after being set, but only if
130 // IsCompleted has become true. Therefore it's possible for this property
131 // to give different (set) events to different callers when IsCompleted is true.
132 ManualResetEvent? asyncEvent = (ManualResetEvent?)_event;
133 while (asyncEvent == null)
134 {
135 LazilyCreateEvent(out asyncEvent);
136 }
137
138 return asyncEvent;
139 }
140 }
141
142 // Returns true if this call created the event.
143 // May return with a null handle. That means it thought it got one, but it was disposed in the mean time.
144 private bool LazilyCreateEvent(out ManualResetEvent waitHandle)
145 {
146 waitHandle = new ManualResetEvent(false);
147 try
148 {
149 if (Interlocked.CompareExchange(ref _event, waitHandle, null) == null)
150 {
151 if (InternalPeekCompleted)
152 {
153 waitHandle.Set();
154 }
155 return true;
156 }
157 else
158 {
159 waitHandle.Dispose();
160 waitHandle = (ManualResetEvent)_event;
161
162 // There's a chance here that _event became null. But the only way is if another thread completed
163 // in InternalWaitForCompletion and disposed it. If we're in InternalWaitForCompletion, we now know
164 // IsCompleted is set, so we can avoid the wait when waitHandle comes back null. AsyncWaitHandle
165 // will try again in this case.
166 return false;
167 }
168 }
169 catch
170 {
171 // This should be very rare, but doing this will reduce the chance of deadlock.
172 _event = null;
173 waitHandle?.Dispose();
174
175 throw;
176 }
177 }
178
179 // This allows ContextAwareResult to not let anyone trigger the CompletedSynchronously tripwire while the context is being captured.
180 [Conditional("DEBUG")]
181 protected void DebugProtectState(bool protect)
182 {
183#if DEBUG
184 _protectState = protect;
185#endif
186 }
187
188 // Interface property, returning synchronous completion status.
189 public bool CompletedSynchronously
190 {
191 get
192 {
193#if DEBUG
194 // Can't be called when state is protected.
195 if (_protectState)
196 {
197 throw new InvalidOperationException("get_CompletedSynchronously called in protected state");
198 }
199#endif
200
201 // If this returns greater than zero, it means it was incremented by InvokeCallback before anyone ever saw it.
202 int result = _intCompleted;
203 if (result == 0)
204 {
205 result = Interlocked.CompareExchange(ref _intCompleted, HighBit, 0);
206 }
207
208 return result > 0;
209 }
210 }
211
212 // Interface property, returning completion status.
213 public bool IsCompleted
214 {
215 get
216 {
217#if DEBUG
218 // Can't be called when state is protected.
219 if (_protectState)
220 {
221 throw new InvalidOperationException("get_IsCompleted called in protected state");
222 }
223#endif
224
225 // Verify low bits to see if it's been incremented. If it hasn't, set the high bit
226 // to show that it's been looked at.
227 int result = _intCompleted;
228 if (result == 0)
229 {
230 result = Interlocked.CompareExchange(ref _intCompleted, HighBit, 0);
231 }
232
233 return (result & ~HighBit) != 0;
234 }
235 }
236
237 // Use to see if something's completed without fixing CompletedSynchronously.
238 internal bool InternalPeekCompleted
239 {
240 get
241 {
242 return (_intCompleted & ~HighBit) != 0;
243 }
244 }
245
246 // Internal property for setting the IO result.
247 internal object? Result
248 {
249 get
250 {
251 return _result == DBNull.Value ? null : _result;
252 }
253 set
254 {
255 // Ideally this should never be called, since setting
256 // the result object really makes sense when the IO completes.
257 //
258 // But if the result was set here (as a preemptive error or for some other reason),
259 // then the "result" parameter passed to InvokeCallback() will be ignored.
260
261 // It's an error to call after the result has been completed or with DBNull.
262 Debug.Assert(value != DBNull.Value, "Result can't be set to DBNull - it's a special internal value.");
263
264 Debug.Assert(!InternalPeekCompleted, "Called on completed result.");
265 _result = value;
266 }
267 }
268
269 internal bool EndCalled
270 {
271 get
272 {
273 return _endCalled;
274 }
275 set
276 {
277 _endCalled = value;
278 }
279 }
280
281 // Internal property for setting the Win32 IO async error code.
282 internal int ErrorCode
283 {
284 get
285 {
286 return _errorCode;
287 }
288 set
289 {
290 _errorCode = value;
291 }
292 }
293
294 // A method for completing the IO with a result and invoking the user's callback.
295 // Used by derived classes to pass context into an overridden Complete(). Useful
296 // for determining the 'winning' thread in case several may simultaneously call
297 // the equivalent of InvokeCallback().
298 protected void ProtectedInvokeCallback(object? result, IntPtr userToken)
299 {
300 // Critical to disallow DBNull here - it could result in a stuck spinlock in WaitForCompletion.
301 if (result == DBNull.Value)
302 {
303 throw new ArgumentNullException(nameof(result));
304 }
305
306#if DEBUG
307 // Always safe to ask for the state now.
308 _protectState = false;
309#endif
310
311 if ((_intCompleted & ~HighBit) == 0 && (Interlocked.Increment(ref _intCompleted) & ~HighBit) == 1)
312 {
313 // DBNull.Value is used to guarantee that the first caller wins,
314 // even if the result was set to null.
315 if (_result == DBNull.Value)
316 {
317 _result = result;
318 }
319
320 ManualResetEvent? asyncEvent = (ManualResetEvent?)_event;
321 if (asyncEvent != null)
322 {
323 try
324 {
325 asyncEvent.Set();
326 }
327 catch (ObjectDisposedException)
328 {
329 // Simply ignore this exception - There is apparently a rare race condition
330 // where the event is disposed before the completion method is called.
331 }
332 }
333
334 Complete(userToken);
335 }
336 }
337
338 // Completes the IO with a result and invoking the user's callback.
339 internal void InvokeCallback(object? result)
340 {
341 ProtectedInvokeCallback(result, IntPtr.Zero);
342 }
343
344 // Completes the IO without a result and invoking the user's callback.
345 internal void InvokeCallback()
346 {
347 ProtectedInvokeCallback(null, IntPtr.Zero);
348 }
349
350 // NOTE: THIS METHOD MUST NOT BE CALLED DIRECTLY.
351 //
352 // This method does the callback's job and is guaranteed to be called exactly once.
353 // A derived overriding method must call the base class somewhere or the completion is lost.
354 protected virtual void Complete(IntPtr userToken)
355 {
356 bool offloaded = false;
357 ThreadContext threadContext = CurrentThreadContext;
358 try
359 {
360 ++threadContext._nestedIOCount;
361 if (_asyncCallback != null)
362 {
363 if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Invoking callback");
364
365 if (threadContext._nestedIOCount >= ForceAsyncCount)
366 {
367 if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "*** OFFLOADED the user callback ****");
368
369 Task.Factory.StartNew(
370 s => WorkerThreadComplete(s!),
371 this,
372 CancellationToken.None,
373 TaskCreationOptions.DenyChildAttach,
374 TaskScheduler.Default);
375
376 offloaded = true;
377 }
378 else
379 {
380 _asyncCallback(this);
381 }
382 }
383 else
384 {
385 if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "No callback to invoke");
386 }
387 }
388 finally
389 {
390 --threadContext._nestedIOCount;
391
392 // Never call this method unless interlocked _intCompleted check has succeeded (like in this case).
393 if (!offloaded)
394 {
395 Cleanup();
396 }
397 }
398 }
399
400 // Only called by the above method.
401 private static void WorkerThreadComplete(object state)
402 {
403 Debug.Assert(state is LazyAsyncResult);
404 LazyAsyncResult thisPtr = (LazyAsyncResult)state;
405
406 try
407 {
408 thisPtr._asyncCallback!(thisPtr);
409 }
410 finally
411 {
412 thisPtr.Cleanup();
413 }
414 }
415
416 // Custom instance cleanup method.
417 //
418 // Derived types override this method to release unmanaged resources associated with an IO request.
419 protected virtual void Cleanup()
420 {
421 }
422
423 internal object? InternalWaitForCompletion()
424 {
425 return WaitForCompletion(true);
426 }
427
428 private object? WaitForCompletion(bool snap)
429 {
430 ManualResetEvent? waitHandle = null;
431 bool createdByMe = false;
432 bool complete = snap ? IsCompleted : InternalPeekCompleted;
433
434 if (!complete)
435 {
436 // Not done yet, so wait:
437 waitHandle = (ManualResetEvent?)_event;
438 if (waitHandle == null)
439 {
440 createdByMe = LazilyCreateEvent(out waitHandle);
441 }
442 }
443
444 if (waitHandle != null)
445 {
446 try
447 {
448 if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Waiting for completion event {waitHandle}");
449 waitHandle.WaitOne(Timeout.Infinite);
450 }
451 catch (ObjectDisposedException)
452 {
453 // This can occur if this method is called from two different threads.
454 // This possibility is the trade-off for not locking.
455 }
456 finally
457 {
458 // We also want to dispose the event although we can't unless we did wait on it here.
459 if (createdByMe && !_userEvent)
460 {
461 // Does _userEvent need to be volatile (or _event set via Interlocked) in order
462 // to avoid giving a user a disposed event?
463 ManualResetEvent? oldEvent = (ManualResetEvent?)_event;
464 _event = null;
465 if (!_userEvent)
466 {
467 oldEvent?.Dispose();
468 }
469 }
470 }
471 }
472
473 // A race condition exists because InvokeCallback sets _intCompleted before _result (so that _result
474 // can benefit from the synchronization of _intCompleted). That means you can get here before _result got
475 // set (although rarely - once every eight hours of stress). Handle that case with a spin-lock.
476
477 SpinWait sw = default;
478 while (_result == DBNull.Value)
479 {
480 sw.SpinOnce();
481 }
482
483 return _result;
484 }
485
486 // A general interface that is called to release unmanaged resources associated with the class.
487 // It completes the result but doesn't do any of the notifications.
488 internal void InternalCleanup()
489 {
490 if ((_intCompleted & ~HighBit) == 0 && (Interlocked.Increment(ref _intCompleted) & ~HighBit) == 1)
491 {
492 // Set no result so that just in case there are waiters, they don't get stuck in the spin lock.
493 _result = null;
494 Cleanup();
495 }
496 }
497 }
498}
Note: See TracBrowser for help on using the repository browser.