Skip to content

Commit df65f8f

Browse files
committed
Revert "Fix slow execution when many breakpoints are used (#14953)"
This reverts commit d8decdc. This commit broke the VS Code extension's debugger, and should be reverted until such time that the root cause is found and a fix applied.
1 parent 2ad1245 commit df65f8f

File tree

3 files changed

+44
-76
lines changed

3 files changed

+44
-76
lines changed

src/System.Management.Automation/engine/debugger/Breakpoint.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ internal bool TrySetBreakpoint(string scriptFile, FunctionContext functionContex
482482
{
483483
Diagnostics.Assert(SequencePointIndex == -1, "shouldn't be trying to set on a pending breakpoint");
484484

485+
if (!scriptFile.Equals(this.Script, StringComparison.OrdinalIgnoreCase))
486+
return false;
487+
485488
// A quick check to see if the breakpoint is within the scriptblock.
486489
bool couldBeInNestedScriptBlock;
487490
var scriptBlock = functionContext._scriptBlock;
@@ -592,11 +595,11 @@ internal override bool RemoveSelf(ScriptDebugger debugger)
592595
var boundBreakPoints = debugger.GetBoundBreakpoints(this.SequencePoints);
593596
if (boundBreakPoints != null)
594597
{
595-
Diagnostics.Assert(boundBreakPoints[this.SequencePointIndex].Contains(this),
598+
Diagnostics.Assert(boundBreakPoints.Contains(this),
596599
"If we set _scriptBlock, we should have also added the breakpoint to the bound breakpoint list");
597-
boundBreakPoints[this.SequencePointIndex].Remove(this);
600+
boundBreakPoints.Remove(this);
598601

599-
if (boundBreakPoints[this.SequencePointIndex].All(breakpoint => breakpoint.SequencePointIndex != this.SequencePointIndex))
602+
if (boundBreakPoints.All(breakpoint => breakpoint.SequencePointIndex != this.SequencePointIndex))
600603
{
601604
// No other line breakpoints are at the same sequence point, so disable the breakpoint so
602605
// we don't go looking for breakpoints the next time we hit the sequence point.

src/System.Management.Automation/engine/debugger/debugger.cs

Lines changed: 37 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -981,8 +981,7 @@ internal ScriptDebugger(ExecutionContext context)
981981
_context = context;
982982
_inBreakpoint = false;
983983
_idToBreakpoint = new ConcurrentDictionary<int, Breakpoint>();
984-
// The string key is function context file path. The int key is sequencePoint index.
985-
_pendingBreakpoints = new ConcurrentDictionary<string, ConcurrentDictionary<int, LineBreakpoint>>();
984+
_pendingBreakpoints = new ConcurrentDictionary<int, LineBreakpoint>();
986985
_boundBreakpoints = new ConcurrentDictionary<string, Tuple<WeakReference, ConcurrentDictionary<int, LineBreakpoint>>>(StringComparer.OrdinalIgnoreCase);
987986
_commandBreakpoints = new ConcurrentDictionary<int, CommandBreakpoint>();
988987
_variableBreakpoints = new ConcurrentDictionary<string, ConcurrentDictionary<int, VariableBreakpoint>>(StringComparer.OrdinalIgnoreCase);
@@ -1185,7 +1184,7 @@ internal void EnterScriptFunction(FunctionContext functionContext)
11851184
private void SetupBreakpoints(FunctionContext functionContext)
11861185
{
11871186
var scriptDebugData = _mapScriptToBreakpoints.GetValue(functionContext._sequencePoints,
1188-
_ => Tuple.Create(new Dictionary<int, List<LineBreakpoint>>(),
1187+
_ => Tuple.Create(new List<LineBreakpoint>(),
11891188
new BitArray(functionContext._sequencePoints.Length)));
11901189
functionContext._boundBreakpoints = scriptDebugData.Item1;
11911190
functionContext._breakPoints = scriptDebugData.Item2;
@@ -1265,19 +1264,10 @@ private CommandBreakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint)
12651264
private LineBreakpoint AddLineBreakpoint(LineBreakpoint breakpoint)
12661265
{
12671266
AddBreakpointCommon(breakpoint);
1268-
AddPendingBreakpoint(breakpoint);
1269-
1267+
_pendingBreakpoints[breakpoint.Id] = breakpoint;
12701268
return breakpoint;
12711269
}
12721270

1273-
private void AddPendingBreakpoint(LineBreakpoint breakpoint)
1274-
{
1275-
_pendingBreakpoints.AddOrUpdate(
1276-
breakpoint.Script,
1277-
new ConcurrentDictionary<int, LineBreakpoint> { [breakpoint.Id] = breakpoint },
1278-
(_, dictionary) => { dictionary.TryAdd(breakpoint.Id, breakpoint); return dictionary; });
1279-
}
1280-
12811271
private void AddNewBreakpoint(Breakpoint breakpoint)
12821272
{
12831273
LineBreakpoint lineBreakpoint = breakpoint as LineBreakpoint;
@@ -1330,9 +1320,13 @@ private void UpdateBreakpoints(FunctionContext functionContext)
13301320
return;
13311321
}
13321322

1333-
if (_pendingBreakpoints.TryGetValue(functionContext._file, out var dictionary) && !dictionary.IsEmpty)
1323+
foreach ((int breakpointId, LineBreakpoint item) in _pendingBreakpoints)
13341324
{
1335-
SetPendingBreakpoints(functionContext);
1325+
if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase))
1326+
{
1327+
SetPendingBreakpoints(functionContext);
1328+
break;
1329+
}
13361330
}
13371331
}
13381332
}
@@ -1358,11 +1352,7 @@ internal bool RemoveCommandBreakpoint(CommandBreakpoint breakpoint) =>
13581352

13591353
internal bool RemoveLineBreakpoint(LineBreakpoint breakpoint)
13601354
{
1361-
bool removed = false;
1362-
if (_pendingBreakpoints.TryGetValue(breakpoint.Script, out var dictionary))
1363-
{
1364-
removed = dictionary.Remove(breakpoint.Id, out _);
1365-
}
1355+
bool removed = _pendingBreakpoints.Remove(breakpoint.Id, out _);
13661356

13671357
Tuple<WeakReference, ConcurrentDictionary<int, LineBreakpoint>> value;
13681358
if (_boundBreakpoints.TryGetValue(breakpoint.Script, out value))
@@ -1380,8 +1370,8 @@ internal bool RemoveLineBreakpoint(LineBreakpoint breakpoint)
13801370
// The bit array is used to detect if a breakpoint is set or not for a given scriptblock. This bit array
13811371
// is checked when hitting sequence points. Enabling/disabling a line breakpoint is as simple as flipping
13821372
// the bit.
1383-
private readonly ConditionalWeakTable<IScriptExtent[], Tuple<Dictionary<int, List<LineBreakpoint>>, BitArray>> _mapScriptToBreakpoints =
1384-
new ConditionalWeakTable<IScriptExtent[], Tuple<Dictionary<int, List<LineBreakpoint>>, BitArray>>();
1373+
private readonly ConditionalWeakTable<IScriptExtent[], Tuple<List<LineBreakpoint>, BitArray>> _mapScriptToBreakpoints =
1374+
new ConditionalWeakTable<IScriptExtent[], Tuple<List<LineBreakpoint>, BitArray>>();
13851375

13861376
/// <summary>
13871377
/// Checks for command breakpoints.
@@ -1482,9 +1472,9 @@ internal void TriggerVariableBreakpoints(List<VariableBreakpoint> breakpoints)
14821472

14831473
// Return the line breakpoints bound in a specific script block (used when a sequence point
14841474
// is hit, to find which breakpoints are set on that sequence point.)
1485-
internal Dictionary<int, List<LineBreakpoint>> GetBoundBreakpoints(IScriptExtent[] sequencePoints)
1475+
internal List<LineBreakpoint> GetBoundBreakpoints(IScriptExtent[] sequencePoints)
14861476
{
1487-
Tuple<Dictionary<int, List<LineBreakpoint>>, BitArray> tuple;
1477+
Tuple<List<LineBreakpoint>, BitArray> tuple;
14881478
if (_mapScriptToBreakpoints.TryGetValue(sequencePoints, out tuple))
14891479
{
14901480
return tuple.Item1;
@@ -1570,25 +1560,16 @@ internal void OnSequencePointHit(FunctionContext functionContext)
15701560
{
15711561
if (functionContext._breakPoints[functionContext._currentSequencePointIndex])
15721562
{
1573-
if (functionContext._boundBreakpoints.TryGetValue(functionContext._currentSequencePointIndex, out var sequencePointBreakpoints))
1563+
var breakpoints = (from breakpoint in functionContext._boundBreakpoints
1564+
where
1565+
breakpoint.SequencePointIndex == functionContext._currentSequencePointIndex &&
1566+
breakpoint.Enabled
1567+
select breakpoint).ToList<Breakpoint>();
1568+
1569+
breakpoints = TriggerBreakpoints(breakpoints);
1570+
if (breakpoints.Count > 0)
15741571
{
1575-
var enabledBreakpoints = new List<Breakpoint>();
1576-
foreach (Breakpoint breakpoint in sequencePointBreakpoints)
1577-
{
1578-
if (breakpoint.Enabled)
1579-
{
1580-
enabledBreakpoints.Add(breakpoint);
1581-
}
1582-
}
1583-
1584-
if (enabledBreakpoints.Count > 0)
1585-
{
1586-
enabledBreakpoints = TriggerBreakpoints(enabledBreakpoints);
1587-
if (enabledBreakpoints.Count > 0)
1588-
{
1589-
StopOnSequencePoint(functionContext, enabledBreakpoints);
1590-
}
1591-
}
1572+
StopOnSequencePoint(functionContext, breakpoints);
15921573
}
15931574
}
15941575
}
@@ -1702,7 +1683,7 @@ internal void Clear()
17021683
}
17031684

17041685
private readonly ExecutionContext _context;
1705-
private readonly ConcurrentDictionary<string, ConcurrentDictionary<int, LineBreakpoint>> _pendingBreakpoints;
1686+
private ConcurrentDictionary<int, LineBreakpoint> _pendingBreakpoints;
17061687
private readonly ConcurrentDictionary<string, Tuple<WeakReference, ConcurrentDictionary<int, LineBreakpoint>>> _boundBreakpoints;
17071688
private readonly ConcurrentDictionary<int, CommandBreakpoint> _commandBreakpoints;
17081689
private readonly ConcurrentDictionary<string, ConcurrentDictionary<int, VariableBreakpoint>> _variableBreakpoints;
@@ -2010,53 +1991,48 @@ private void UnbindBoundBreakpoints(List<LineBreakpoint> boundBreakpoints)
20101991
foreach (var breakpoint in boundBreakpoints)
20111992
{
20121993
// Also remove unbound breakpoints from the script to breakpoint map.
2013-
Tuple<Dictionary<int, List<LineBreakpoint>>, BitArray> lineBreakTuple;
1994+
Tuple<List<LineBreakpoint>, BitArray> lineBreakTuple;
20141995
if (_mapScriptToBreakpoints.TryGetValue(breakpoint.SequencePoints, out lineBreakTuple))
20151996
{
2016-
if (lineBreakTuple.Item1.TryGetValue(breakpoint.SequencePointIndex, out var lineBreakList))
2017-
{
2018-
lineBreakList.Remove(breakpoint);
2019-
}
1997+
lineBreakTuple.Item1.Remove(breakpoint);
20201998
}
20211999

20222000
breakpoint.SequencePoints = null;
20232001
breakpoint.SequencePointIndex = -1;
20242002
breakpoint.BreakpointBitArray = null;
2025-
2026-
AddPendingBreakpoint(breakpoint);
2003+
_pendingBreakpoints[breakpoint.Id] = breakpoint;
20272004
}
20282005

20292006
boundBreakpoints.Clear();
20302007
}
20312008

20322009
private void SetPendingBreakpoints(FunctionContext functionContext)
20332010
{
2011+
if (_pendingBreakpoints.IsEmpty)
2012+
return;
2013+
2014+
var newPendingBreakpoints = new Dictionary<int, LineBreakpoint>();
20342015
var currentScriptFile = functionContext._file;
20352016

20362017
// If we're not in a file, we can't have any line breakpoints.
20372018
if (currentScriptFile == null)
20382019
return;
20392020

2040-
if (!_pendingBreakpoints.TryGetValue(currentScriptFile, out var breakpoints) || breakpoints.IsEmpty)
2041-
{
2042-
return;
2043-
}
2044-
20452021
// Normally we register a script file when the script is run or the module is imported,
20462022
// but if there weren't any breakpoints when the script was run and the script was dotted,
20472023
// we will end up here with pending breakpoints, but we won't have cached the list of
20482024
// breakpoints in the script.
20492025
RegisterScriptFile(currentScriptFile, functionContext.CurrentPosition.StartScriptPosition.GetFullScript());
20502026

2051-
Tuple<Dictionary<int, List<LineBreakpoint>>, BitArray> tuple;
2027+
Tuple<List<LineBreakpoint>, BitArray> tuple;
20522028
if (!_mapScriptToBreakpoints.TryGetValue(functionContext._sequencePoints, out tuple))
20532029
{
20542030
Diagnostics.Assert(false, "If the script block is still alive, the entry should not be collected.");
20552031
}
20562032

20572033
Diagnostics.Assert(tuple.Item1 == functionContext._boundBreakpoints, "What's up?");
20582034

2059-
foreach ((int breakpointId, LineBreakpoint breakpoint) in breakpoints)
2035+
foreach ((int breakpointId, LineBreakpoint breakpoint) in _pendingBreakpoints)
20602036
{
20612037
bool bound = false;
20622038
if (breakpoint.TrySetBreakpoint(currentScriptFile, functionContext))
@@ -2067,32 +2043,21 @@ private void SetPendingBreakpoints(FunctionContext functionContext)
20672043
}
20682044

20692045
bound = true;
2046+
tuple.Item1.Add(breakpoint);
20702047

2071-
if (tuple.Item1.TryGetValue(breakpoint.SequencePointIndex, out var list))
2072-
{
2073-
list.Add(breakpoint);
2074-
}
2075-
else
2076-
{
2077-
tuple.Item1.Add(breakpoint.SequencePointIndex, new List<LineBreakpoint> { breakpoint });
2078-
}
2079-
20802048
// We need to keep track of any breakpoints that are bound in each script because they may
20812049
// need to be rebound if the script changes.
20822050
var boundBreakpoints = _boundBreakpoints[currentScriptFile].Item2;
20832051
boundBreakpoints[breakpoint.Id] = breakpoint;
20842052
}
20852053

2086-
if (bound)
2054+
if (!bound)
20872055
{
2088-
breakpoints.TryRemove(breakpointId, out _);
2056+
newPendingBreakpoints.Add(breakpoint.Id, breakpoint);
20892057
}
20902058
}
20912059

2092-
// Here could check if all breakpoints for the current functionContext were bound, but because there is no atomic
2093-
// api for conditional removal we either need to lock, or do some trickery that has possibility of race conditions.
2094-
// Instead we keep the item in the dictionary with 0 breakpoint count. This should not be a big issue,
2095-
// because it is single entry per file that had breakpoints, so there won't be thousands of files in a session.
2060+
_pendingBreakpoints = new ConcurrentDictionary<int, LineBreakpoint>(newPendingBreakpoints);
20962061
}
20972062

20982063
private void StopOnSequencePoint(FunctionContext functionContext, List<Breakpoint> breakpoints)

src/System.Management.Automation/engine/parser/Compiler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,7 @@ internal class FunctionContext
782782
internal ExecutionContext _executionContext;
783783
internal Pipe _outputPipe;
784784
internal BitArray _breakPoints;
785-
internal Dictionary<int, List<LineBreakpoint>> _boundBreakpoints;
785+
internal List<LineBreakpoint> _boundBreakpoints;
786786
internal int _currentSequencePointIndex;
787787
internal MutableTuple _localsTuple;
788788
internal List<Tuple<Type[], Action<FunctionContext>[], Type[]>> _traps = new List<Tuple<Type[], Action<FunctionContext>[], Type[]>>();

0 commit comments

Comments
 (0)