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