Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit c716b5d

Browse files
authored
Merge pull request #23390 from stephentoub/port23152
[release/2.2] Add stack depth check to all Task continuations
2 parents b4e920d + 54030ff commit c716b5d

File tree

2 files changed

+18
-95
lines changed

2 files changed

+18
-95
lines changed

src/mscorlib/src/System/Threading/Tasks/Task.cs

Lines changed: 11 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,6 @@ public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
134134
{
135135
[ThreadStatic]
136136
internal static Task t_currentTask; // The currently executing task.
137-
[ThreadStatic]
138-
private static StackGuard t_stackGuard; // The stack guard object for this thread
139137

140138
internal static int s_taskIdCounter; //static counter used to generate unique task IDs
141139

@@ -1276,23 +1274,6 @@ internal static Task InternalCurrentIfAttached(TaskCreationOptions creationOptio
12761274
return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null;
12771275
}
12781276

1279-
/// <summary>
1280-
/// Gets the StackGuard object assigned to the current thread.
1281-
/// </summary>
1282-
internal static StackGuard CurrentStackGuard
1283-
{
1284-
get
1285-
{
1286-
StackGuard sg = t_stackGuard;
1287-
if (sg == null)
1288-
{
1289-
t_stackGuard = sg = new StackGuard();
1290-
}
1291-
return sg;
1292-
}
1293-
}
1294-
1295-
12961277
/// <summary>
12971278
/// Gets the <see cref="T:System.AggregateException">Exception</see> that caused the <see
12981279
/// cref="Task">Task</see> to end prematurely. If the <see
@@ -3263,7 +3244,8 @@ private void RunContinuations(object continuationObject) // separated out of Fin
32633244
// Skip synchronous execution of continuations if this task's thread was aborted
32643245
bool canInlineContinuations = !(((m_stateFlags & TASK_STATE_THREAD_WAS_ABORTED) != 0) ||
32653246
(RuntimeThread.CurrentThread.ThreadState == ThreadState.AbortRequested) ||
3266-
((m_stateFlags & (int)TaskCreationOptions.RunContinuationsAsynchronously) != 0));
3247+
((m_stateFlags & (int)TaskCreationOptions.RunContinuationsAsynchronously) != 0))
3248+
&& RuntimeHelpers.TryEnsureSufficientExecutionStack();
32673249

32683250
switch (continuationObject)
32693251
{
@@ -6415,56 +6397,6 @@ public enum TaskContinuationOptions
64156397
ExecuteSynchronously = 0x80000
64166398
}
64176399

6418-
/// <summary>
6419-
/// Internal helper class to keep track of stack depth and decide whether we should inline or not.
6420-
/// </summary>
6421-
internal class StackGuard
6422-
{
6423-
// current thread's depth of nested inline task executions
6424-
private int m_inliningDepth = 0;
6425-
6426-
// For relatively small inlining depths we don't want to get into the business of stack probing etc.
6427-
// This clearly leaves a window of opportunity for the user code to SO. However a piece of code
6428-
// that can SO in 20 inlines on a typical 1MB stack size probably needs to be revisited anyway.
6429-
private const int MAX_UNCHECKED_INLINING_DEPTH = 20;
6430-
6431-
/// <summary>
6432-
/// This method needs to be called before attempting inline execution on the current thread.
6433-
/// If false is returned, it means we are too close to the end of the stack and should give up inlining.
6434-
/// Each call to TryBeginInliningScope() that returns true must be matched with a
6435-
/// call to EndInliningScope() regardless of whether inlining actually took place.
6436-
/// </summary>
6437-
internal bool TryBeginInliningScope()
6438-
{
6439-
// If we're still under the 'safe' limit we'll just skip the stack probe to save p/invoke calls
6440-
if (m_inliningDepth < MAX_UNCHECKED_INLINING_DEPTH || CheckForSufficientStack())
6441-
{
6442-
m_inliningDepth++;
6443-
return true;
6444-
}
6445-
else
6446-
return false;
6447-
}
6448-
6449-
/// <summary>
6450-
/// This needs to be called once for each previous successful TryBeginInliningScope() call after
6451-
/// inlining related logic runs.
6452-
/// </summary>
6453-
internal void EndInliningScope()
6454-
{
6455-
m_inliningDepth--;
6456-
Debug.Assert(m_inliningDepth >= 0, "Inlining depth count should never go negative.");
6457-
6458-
// do the right thing just in case...
6459-
if (m_inliningDepth < 0) m_inliningDepth = 0;
6460-
}
6461-
6462-
private unsafe bool CheckForSufficientStack()
6463-
{
6464-
return RuntimeHelpers.TryEnsureSufficientExecutionStack();
6465-
}
6466-
}
6467-
64686400
// Special internal struct that we use to signify that we are not interested in
64696401
// a Task<VoidTaskResult>'s result.
64706402
internal struct VoidTaskResult { }
@@ -6546,18 +6478,17 @@ public UnwrapPromise(Task outerTask, bool lookForOce)
65466478
// For ITaskCompletionAction
65476479
public void Invoke(Task completingTask)
65486480
{
6549-
// Check the current stack guard. If we're ok to inline,
6550-
// process the task, and reset the guard when we're done.
6551-
var sg = Task.CurrentStackGuard;
6552-
if (sg.TryBeginInliningScope())
6481+
// If we're ok to inline, process the task. Otherwise, we're too deep on the stack, and
6482+
// we shouldn't run the continuation chain here, so queue a work item to call back here
6483+
// to Invoke asynchronously.
6484+
if (RuntimeHelpers.TryEnsureSufficientExecutionStack())
6485+
{
6486+
InvokeCore(completingTask);
6487+
}
6488+
else
65536489
{
6554-
try { InvokeCore(completingTask); }
6555-
finally { sg.EndInliningScope(); }
6490+
InvokeCoreAsync(completingTask);
65566491
}
6557-
// Otherwise, we're too deep on the stack, and
6558-
// we shouldn't run the continuation chain here, so queue a work
6559-
// item to call back here to Invoke asynchronously.
6560-
else InvokeCoreAsync(completingTask);
65616492
}
65626493

65636494
/// <summary>

src/mscorlib/src/System/Threading/Tasks/TaskScheduler.cs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -186,41 +186,33 @@ internal bool TryRunInline(Task task, bool taskWasPreviouslyQueued)
186186
// Delegate cross-scheduler inlining requests to target scheduler
187187
if (ets != this && ets != null) return ets.TryRunInline(task, taskWasPreviouslyQueued);
188188

189-
StackGuard currentStackGuard;
190189
if ((ets == null) ||
191190
(task.m_action == null) ||
192191
task.IsDelegateInvoked ||
193192
task.IsCanceled ||
194-
(currentStackGuard = Task.CurrentStackGuard).TryBeginInliningScope() == false)
193+
!RuntimeHelpers.TryEnsureSufficientExecutionStack())
195194
{
196195
return false;
197196
}
198197

199198
// Task class will still call into TaskScheduler.TryRunInline rather than TryExecuteTaskInline() so that
200199
// 1) we can adjust the return code from TryExecuteTaskInline in case a buggy custom scheduler lies to us
201200
// 2) we maintain a mechanism for the TLS lookup optimization that we used to have for the ConcRT scheduler (will potentially introduce the same for TP)
202-
bool bInlined = false;
203-
try
204-
{
205-
if (TplEtwProvider.Log.IsEnabled())
206-
{
207-
task.FireTaskScheduledIfNeeded(this);
208-
}
209-
bInlined = TryExecuteTaskInline(task, taskWasPreviouslyQueued);
210-
}
211-
finally
201+
if (TplEtwProvider.Log.IsEnabled())
212202
{
213-
currentStackGuard.EndInliningScope();
203+
task.FireTaskScheduledIfNeeded(this);
214204
}
215205

206+
bool inlined = TryExecuteTaskInline(task, taskWasPreviouslyQueued);
207+
216208
// If the custom scheduler returned true, we should either have the TASK_STATE_DELEGATE_INVOKED or TASK_STATE_CANCELED bit set
217209
// Otherwise the scheduler is buggy
218-
if (bInlined && !(task.IsDelegateInvoked || task.IsCanceled))
210+
if (inlined && !(task.IsDelegateInvoked || task.IsCanceled))
219211
{
220212
throw new InvalidOperationException(SR.TaskScheduler_InconsistentStateAfterTryExecuteTaskInline);
221213
}
222214

223-
return bInlined;
215+
return inlined;
224216
}
225217

226218
/// <summary>

0 commit comments

Comments
 (0)