Merge pull request #10948 from eerhardt/GetPreviousAdjRulePerf
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Threading / ExecutionContext.cs
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 // See the LICENSE file in the project root for more information.
4
5 /*============================================================
6 **
7 **
8 **
9 ** Purpose: Capture execution  context for a thread
10 **
11 **
12 ===========================================================*/
13
14 using System.Diagnostics;
15 using System.Diagnostics.Contracts;
16 using System.Runtime.ExceptionServices;
17 using System.Runtime.Serialization;
18
19 using Thread = Internal.Runtime.Augments.RuntimeThread;
20
21 namespace System.Threading
22 {
23     public delegate void ContextCallback(Object state);
24
25     internal struct ExecutionContextSwitcher
26     {
27         internal ExecutionContext m_ec;
28         internal SynchronizationContext m_sc;
29
30         internal void Undo(Thread currentThread)
31         {
32             Debug.Assert(currentThread == Thread.CurrentThread);
33
34             // The common case is that these have not changed, so avoid the cost of a write if not needed.
35             if (currentThread.SynchronizationContext != m_sc)
36             {
37                 currentThread.SynchronizationContext = m_sc;
38             }
39
40             if (currentThread.ExecutionContext != m_ec)
41             {
42                 ExecutionContext.Restore(currentThread, m_ec);
43             }
44         }
45     }
46
47     [Serializable]
48     public sealed class ExecutionContext : IDisposable, ISerializable
49     {
50         internal static readonly ExecutionContext Default = new ExecutionContext();
51
52         private readonly IAsyncLocalValueMap m_localValues;
53         private readonly IAsyncLocal[] m_localChangeNotifications;
54         private readonly bool m_isFlowSuppressed;
55
56         private ExecutionContext()
57         {
58             m_localValues = AsyncLocalValueMap.Empty;
59             m_localChangeNotifications = Array.Empty<IAsyncLocal>();
60         }
61
62         private ExecutionContext(
63             IAsyncLocalValueMap localValues,
64             IAsyncLocal[] localChangeNotifications,
65             bool isFlowSuppressed)
66         {
67             m_localValues = localValues;
68             m_localChangeNotifications = localChangeNotifications;
69             m_isFlowSuppressed = isFlowSuppressed;
70         }
71
72         public void GetObjectData(SerializationInfo info, StreamingContext context)
73         {
74             if (info == null)
75             {
76                 throw new ArgumentNullException(nameof(info));
77             }
78             Contract.EndContractBlock();
79         }
80
81         private ExecutionContext(SerializationInfo info, StreamingContext context)
82         {
83         }
84
85         public static ExecutionContext Capture()
86         {
87             ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
88             return
89                 executionContext == null ? Default :
90                 executionContext.m_isFlowSuppressed ? null :
91                 executionContext;
92         }
93
94         private ExecutionContext ShallowClone(bool isFlowSuppressed)
95         {
96             Debug.Assert(isFlowSuppressed != m_isFlowSuppressed);
97
98             if (!isFlowSuppressed &&
99                 m_localValues == Default.m_localValues &&
100                 m_localChangeNotifications == Default.m_localChangeNotifications)
101             {
102                 return null; // implies the default context
103             }
104             return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed);
105         }
106
107         public static AsyncFlowControl SuppressFlow()
108         {
109             Thread currentThread = Thread.CurrentThread;
110             ExecutionContext executionContext = currentThread.ExecutionContext ?? Default;
111             if (executionContext.m_isFlowSuppressed)
112             {
113                 throw new InvalidOperationException(SR.InvalidOperation_CannotSupressFlowMultipleTimes);
114             }
115             Contract.EndContractBlock();
116
117             executionContext = executionContext.ShallowClone(isFlowSuppressed: true);
118             var asyncFlowControl = new AsyncFlowControl();
119             currentThread.ExecutionContext = executionContext;
120             asyncFlowControl.Initialize(currentThread);
121             return asyncFlowControl;
122         }
123
124         public static void RestoreFlow()
125         {
126             Thread currentThread = Thread.CurrentThread;
127             ExecutionContext executionContext = currentThread.ExecutionContext;
128             if (executionContext == null || !executionContext.m_isFlowSuppressed)
129             {
130                 throw new InvalidOperationException(SR.InvalidOperation_CannotRestoreUnsupressedFlow);
131             }
132             Contract.EndContractBlock();
133
134             currentThread.ExecutionContext = executionContext.ShallowClone(isFlowSuppressed: false);
135         }
136
137         public static bool IsFlowSuppressed()
138         {
139             ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext;
140             return executionContext != null && executionContext.m_isFlowSuppressed;
141         }
142
143         public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state)
144         {
145             if (executionContext == null)
146                 throw new InvalidOperationException(SR.InvalidOperation_NullContext);
147
148             Thread currentThread = Thread.CurrentThread;
149             ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher);
150             try
151             {
152                 EstablishCopyOnWriteScope(currentThread, ref ecsw);
153                 ExecutionContext.Restore(currentThread, executionContext);
154                 callback(state);
155             }
156             catch
157             {
158                 // Note: we have a "catch" rather than a "finally" because we want
159                 // to stop the first pass of EH here.  That way we can restore the previous
160                 // context before any of our callers' EH filters run.  That means we need to
161                 // end the scope separately in the non-exceptional case below.
162                 ecsw.Undo(currentThread);
163                 throw;
164             }
165             ecsw.Undo(currentThread);
166         }
167
168         internal static void Restore(Thread currentThread, ExecutionContext executionContext)
169         {
170             Debug.Assert(currentThread == Thread.CurrentThread);
171
172             ExecutionContext previous = currentThread.ExecutionContext ?? Default;
173             currentThread.ExecutionContext = executionContext;
174
175             // New EC could be null if that's what ECS.Undo saved off.
176             // For the purposes of dealing with context change, treat this as the default EC
177             executionContext = executionContext ?? Default;
178
179             if (previous != executionContext)
180             {
181                 OnContextChanged(previous, executionContext);
182             }
183         }
184
185         internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw)
186         {
187             Debug.Assert(currentThread == Thread.CurrentThread);
188
189             ecsw.m_ec = currentThread.ExecutionContext;
190             ecsw.m_sc = currentThread.SynchronizationContext;
191         }
192
193         private static void OnContextChanged(ExecutionContext previous, ExecutionContext current)
194         {
195             Debug.Assert(previous != null);
196             Debug.Assert(current != null);
197             Debug.Assert(previous != current);
198
199             foreach (IAsyncLocal local in previous.m_localChangeNotifications)
200             {
201                 object previousValue;
202                 object currentValue;
203                 previous.m_localValues.TryGetValue(local, out previousValue);
204                 current.m_localValues.TryGetValue(local, out currentValue);
205
206                 if (previousValue != currentValue)
207                     local.OnValueChanged(previousValue, currentValue, true);
208             }
209
210             if (current.m_localChangeNotifications != previous.m_localChangeNotifications)
211             {
212                 try
213                 {
214                     foreach (IAsyncLocal local in current.m_localChangeNotifications)
215                     {
216                         // If the local has a value in the previous context, we already fired the event for that local
217                         // in the code above.
218                         object previousValue;
219                         if (!previous.m_localValues.TryGetValue(local, out previousValue))
220                         {
221                             object currentValue;
222                             current.m_localValues.TryGetValue(local, out currentValue);
223
224                             if (previousValue != currentValue)
225                                 local.OnValueChanged(previousValue, currentValue, true);
226                         }
227                     }
228                 }
229                 catch (Exception ex)
230                 {
231                     Environment.FailFast(
232                         SR.ExecutionContext_ExceptionInAsyncLocalNotification,
233                         ex);
234                 }
235             }
236         }
237
238         internal static object GetLocalValue(IAsyncLocal local)
239         {
240             ExecutionContext current = Thread.CurrentThread.ExecutionContext;
241             if (current == null)
242                 return null;
243
244             object value;
245             current.m_localValues.TryGetValue(local, out value);
246             return value;
247         }
248
249         internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications)
250         {
251             ExecutionContext current = Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default;
252
253             object previousValue;
254             bool hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
255
256             if (previousValue == newValue)
257                 return;
258
259             IAsyncLocalValueMap newValues = current.m_localValues.Set(local, newValue);
260
261             //
262             // Either copy the change notification array, or create a new one, depending on whether we need to add a new item.
263             //
264             IAsyncLocal[] newChangeNotifications = current.m_localChangeNotifications;
265             if (needChangeNotifications)
266             {
267                 if (hadPreviousValue)
268                 {
269                     Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
270                 }
271                 else
272                 {
273                     int newNotificationIndex = newChangeNotifications.Length;
274                     Array.Resize(ref newChangeNotifications, newNotificationIndex + 1);
275                     newChangeNotifications[newNotificationIndex] = local;
276                 }
277             }
278
279             Thread.CurrentThread.ExecutionContext =
280                 new ExecutionContext(newValues, newChangeNotifications, current.m_isFlowSuppressed);
281
282             if (needChangeNotifications)
283             {
284                 local.OnValueChanged(previousValue, newValue, false);
285             }
286         }
287
288         public ExecutionContext CreateCopy()
289         {
290             return this; // since CoreCLR's ExecutionContext is immutable, we don't need to create copies.
291         }
292
293         public void Dispose()
294         {
295             // For CLR compat only
296         }
297     }
298
299     public struct AsyncFlowControl : IDisposable
300     {
301         private Thread _thread;
302
303         internal void Initialize(Thread currentThread)
304         {
305             Debug.Assert(currentThread == Thread.CurrentThread);
306             _thread = currentThread;
307         }
308
309         public void Undo()
310         {
311             if (_thread == null)
312             {
313                 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCMultiple);
314             }
315             if (Thread.CurrentThread != _thread)
316             {
317                 throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCOtherThread);
318             }
319
320             // An async flow control cannot be undone when a different execution context is applied. The desktop framework
321             // mutates the execution context when its state changes, and only changes the instance when an execution context
322             // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution
323             // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution
324             // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async
325             // local's value, the desktop framework verifies that a different execution context has not been applied by
326             // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core,
327             // since the execution context instance will change after changing the async local's value, it verifies that a
328             // different execution context has not been applied, by instead ensuring that the current execution context's
329             // flow is suppressed.
330             if (!ExecutionContext.IsFlowSuppressed())
331             {
332                 throw new InvalidOperationException(SR.InvalidOperation_AsyncFlowCtrlCtxMismatch);
333             }
334             Contract.EndContractBlock();
335
336             _thread = null;
337             ExecutionContext.RestoreFlow();
338         }
339
340         public void Dispose()
341         {
342             Undo();
343         }
344
345         public override bool Equals(object obj)
346         {
347             return obj is AsyncFlowControl && Equals((AsyncFlowControl)obj);
348         }
349
350         public bool Equals(AsyncFlowControl obj)
351         {
352             return _thread == obj._thread;
353         }
354
355         public override int GetHashCode()
356         {
357             return _thread?.GetHashCode() ?? 0;
358         }
359
360         public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b)
361         {
362             return a.Equals(b);
363         }
364
365         public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b)
366         {
367             return !(a == b);
368         }
369     }
370 }