@@ -981,7 +981,8 @@ internal ScriptDebugger(ExecutionContext context)
981981 _context = context ;
982982 _inBreakpoint = false ;
983983 _idToBreakpoint = new ConcurrentDictionary < int , Breakpoint > ( ) ;
984- _pendingBreakpoints = new ConcurrentDictionary < int , LineBreakpoint > ( ) ;
984+ // The string key is function context file path. The int key is sequencePoint index.
985+ _pendingBreakpoints = new ConcurrentDictionary < string , ConcurrentDictionary < int , LineBreakpoint > > ( ) ;
985986 _boundBreakpoints = new ConcurrentDictionary < string , Tuple < WeakReference , ConcurrentDictionary < int , LineBreakpoint > > > ( StringComparer . OrdinalIgnoreCase ) ;
986987 _commandBreakpoints = new ConcurrentDictionary < int , CommandBreakpoint > ( ) ;
987988 _variableBreakpoints = new ConcurrentDictionary < string , ConcurrentDictionary < int , VariableBreakpoint > > ( StringComparer . OrdinalIgnoreCase ) ;
@@ -1184,7 +1185,7 @@ internal void EnterScriptFunction(FunctionContext functionContext)
11841185 private void SetupBreakpoints ( FunctionContext functionContext )
11851186 {
11861187 var scriptDebugData = _mapScriptToBreakpoints . GetValue ( functionContext . _sequencePoints ,
1187- _ => Tuple . Create ( new List < LineBreakpoint > ( ) ,
1188+ _ => Tuple . Create ( new Dictionary < int , List < LineBreakpoint > > ( ) ,
11881189 new BitArray ( functionContext . _sequencePoints . Length ) ) ) ;
11891190 functionContext . _boundBreakpoints = scriptDebugData . Item1 ;
11901191 functionContext . _breakPoints = scriptDebugData . Item2 ;
@@ -1264,10 +1265,19 @@ private CommandBreakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint)
12641265 private LineBreakpoint AddLineBreakpoint ( LineBreakpoint breakpoint )
12651266 {
12661267 AddBreakpointCommon ( breakpoint ) ;
1267- _pendingBreakpoints [ breakpoint . Id ] = breakpoint ;
1268+ AddPendingBreakpoint ( breakpoint ) ;
1269+
12681270 return breakpoint ;
12691271 }
12701272
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+
12711281 private void AddNewBreakpoint ( Breakpoint breakpoint )
12721282 {
12731283 LineBreakpoint lineBreakpoint = breakpoint as LineBreakpoint ;
@@ -1320,13 +1330,9 @@ private void UpdateBreakpoints(FunctionContext functionContext)
13201330 return ;
13211331 }
13221332
1323- foreach ( ( int breakpointId , LineBreakpoint item ) in _pendingBreakpoints )
1333+ if ( _pendingBreakpoints . TryGetValue ( functionContext . _file , out var dictionary ) && ! dictionary . IsEmpty )
13241334 {
1325- if ( item . IsScriptBreakpoint && item . Script . Equals ( functionContext . _file , StringComparison . OrdinalIgnoreCase ) )
1326- {
1327- SetPendingBreakpoints ( functionContext ) ;
1328- break ;
1329- }
1335+ SetPendingBreakpoints ( functionContext ) ;
13301336 }
13311337 }
13321338 }
@@ -1352,7 +1358,11 @@ internal bool RemoveCommandBreakpoint(CommandBreakpoint breakpoint) =>
13521358
13531359 internal bool RemoveLineBreakpoint ( LineBreakpoint breakpoint )
13541360 {
1355- bool removed = _pendingBreakpoints . Remove ( breakpoint . Id , out _ ) ;
1361+ bool removed = false ;
1362+ if ( _pendingBreakpoints . TryGetValue ( breakpoint . Script , out var dictionary ) )
1363+ {
1364+ removed = dictionary . Remove ( breakpoint . Id , out _ ) ;
1365+ }
13561366
13571367 Tuple < WeakReference , ConcurrentDictionary < int , LineBreakpoint > > value ;
13581368 if ( _boundBreakpoints . TryGetValue ( breakpoint . Script , out value ) )
@@ -1370,8 +1380,8 @@ internal bool RemoveLineBreakpoint(LineBreakpoint breakpoint)
13701380 // The bit array is used to detect if a breakpoint is set or not for a given scriptblock. This bit array
13711381 // is checked when hitting sequence points. Enabling/disabling a line breakpoint is as simple as flipping
13721382 // the bit.
1373- private readonly ConditionalWeakTable < IScriptExtent [ ] , Tuple < List < LineBreakpoint > , BitArray > > _mapScriptToBreakpoints =
1374- new ConditionalWeakTable < IScriptExtent [ ] , Tuple < List < LineBreakpoint > , BitArray > > ( ) ;
1383+ private readonly ConditionalWeakTable < IScriptExtent [ ] , Tuple < Dictionary < int , List < LineBreakpoint > > , BitArray > > _mapScriptToBreakpoints =
1384+ new ConditionalWeakTable < IScriptExtent [ ] , Tuple < Dictionary < int , List < LineBreakpoint > > , BitArray > > ( ) ;
13751385
13761386 /// <summary>
13771387 /// Checks for command breakpoints.
@@ -1472,9 +1482,9 @@ internal void TriggerVariableBreakpoints(List<VariableBreakpoint> breakpoints)
14721482
14731483 // Return the line breakpoints bound in a specific script block (used when a sequence point
14741484 // is hit, to find which breakpoints are set on that sequence point.)
1475- internal List < LineBreakpoint > GetBoundBreakpoints ( IScriptExtent [ ] sequencePoints )
1485+ internal Dictionary < int , List < LineBreakpoint > > GetBoundBreakpoints ( IScriptExtent [ ] sequencePoints )
14761486 {
1477- Tuple < List < LineBreakpoint > , BitArray > tuple ;
1487+ Tuple < Dictionary < int , List < LineBreakpoint > > , BitArray > tuple ;
14781488 if ( _mapScriptToBreakpoints . TryGetValue ( sequencePoints , out tuple ) )
14791489 {
14801490 return tuple . Item1 ;
@@ -1560,16 +1570,25 @@ internal void OnSequencePointHit(FunctionContext functionContext)
15601570 {
15611571 if ( functionContext . _breakPoints [ functionContext . _currentSequencePointIndex ] )
15621572 {
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 )
1573+ if ( functionContext . _boundBreakpoints . TryGetValue ( functionContext . _currentSequencePointIndex , out var sequencePointBreakpoints ) )
15711574 {
1572- StopOnSequencePoint ( functionContext , breakpoints ) ;
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+ }
15731592 }
15741593 }
15751594 }
@@ -1683,7 +1702,7 @@ internal void Clear()
16831702 }
16841703
16851704 private readonly ExecutionContext _context ;
1686- private ConcurrentDictionary < int , LineBreakpoint > _pendingBreakpoints ;
1705+ private readonly ConcurrentDictionary < string , ConcurrentDictionary < int , LineBreakpoint > > _pendingBreakpoints ;
16871706 private readonly ConcurrentDictionary < string , Tuple < WeakReference , ConcurrentDictionary < int , LineBreakpoint > > > _boundBreakpoints ;
16881707 private readonly ConcurrentDictionary < int , CommandBreakpoint > _commandBreakpoints ;
16891708 private readonly ConcurrentDictionary < string , ConcurrentDictionary < int , VariableBreakpoint > > _variableBreakpoints ;
@@ -1991,48 +2010,53 @@ private void UnbindBoundBreakpoints(List<LineBreakpoint> boundBreakpoints)
19912010 foreach ( var breakpoint in boundBreakpoints )
19922011 {
19932012 // Also remove unbound breakpoints from the script to breakpoint map.
1994- Tuple < List < LineBreakpoint > , BitArray > lineBreakTuple ;
2013+ Tuple < Dictionary < int , List < LineBreakpoint > > , BitArray > lineBreakTuple ;
19952014 if ( _mapScriptToBreakpoints . TryGetValue ( breakpoint . SequencePoints , out lineBreakTuple ) )
19962015 {
1997- lineBreakTuple . Item1 . Remove ( breakpoint ) ;
2016+ if ( lineBreakTuple . Item1 . TryGetValue ( breakpoint . SequencePointIndex , out var lineBreakList ) )
2017+ {
2018+ lineBreakList . Remove ( breakpoint ) ;
2019+ }
19982020 }
19992021
20002022 breakpoint . SequencePoints = null ;
20012023 breakpoint . SequencePointIndex = - 1 ;
20022024 breakpoint . BreakpointBitArray = null ;
2003- _pendingBreakpoints [ breakpoint . Id ] = breakpoint ;
2025+
2026+ AddPendingBreakpoint ( breakpoint ) ;
20042027 }
20052028
20062029 boundBreakpoints . Clear ( ) ;
20072030 }
20082031
20092032 private void SetPendingBreakpoints ( FunctionContext functionContext )
20102033 {
2011- if ( _pendingBreakpoints . IsEmpty )
2012- return ;
2013-
2014- var newPendingBreakpoints = new Dictionary < int , LineBreakpoint > ( ) ;
20152034 var currentScriptFile = functionContext . _file ;
20162035
20172036 // If we're not in a file, we can't have any line breakpoints.
20182037 if ( currentScriptFile == null )
20192038 return ;
20202039
2040+ if ( ! _pendingBreakpoints . TryGetValue ( currentScriptFile , out var breakpoints ) || breakpoints . IsEmpty )
2041+ {
2042+ return ;
2043+ }
2044+
20212045 // Normally we register a script file when the script is run or the module is imported,
20222046 // but if there weren't any breakpoints when the script was run and the script was dotted,
20232047 // we will end up here with pending breakpoints, but we won't have cached the list of
20242048 // breakpoints in the script.
20252049 RegisterScriptFile ( currentScriptFile , functionContext . CurrentPosition . StartScriptPosition . GetFullScript ( ) ) ;
20262050
2027- Tuple < List < LineBreakpoint > , BitArray > tuple ;
2051+ Tuple < Dictionary < int , List < LineBreakpoint > > , BitArray > tuple ;
20282052 if ( ! _mapScriptToBreakpoints . TryGetValue ( functionContext . _sequencePoints , out tuple ) )
20292053 {
20302054 Diagnostics . Assert ( false , "If the script block is still alive, the entry should not be collected." ) ;
20312055 }
20322056
20332057 Diagnostics . Assert ( tuple . Item1 == functionContext . _boundBreakpoints , "What's up?" ) ;
20342058
2035- foreach ( ( int breakpointId , LineBreakpoint breakpoint ) in _pendingBreakpoints )
2059+ foreach ( ( int breakpointId , LineBreakpoint breakpoint ) in breakpoints )
20362060 {
20372061 bool bound = false ;
20382062 if ( breakpoint . TrySetBreakpoint ( currentScriptFile , functionContext ) )
@@ -2043,21 +2067,32 @@ private void SetPendingBreakpoints(FunctionContext functionContext)
20432067 }
20442068
20452069 bound = true ;
2046- tuple . Item1 . Add ( breakpoint ) ;
20472070
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+
20482080 // We need to keep track of any breakpoints that are bound in each script because they may
20492081 // need to be rebound if the script changes.
20502082 var boundBreakpoints = _boundBreakpoints [ currentScriptFile ] . Item2 ;
20512083 boundBreakpoints [ breakpoint . Id ] = breakpoint ;
20522084 }
20532085
2054- if ( ! bound )
2086+ if ( bound )
20552087 {
2056- newPendingBreakpoints . Add ( breakpoint . Id , breakpoint ) ;
2088+ breakpoints . TryRemove ( breakpointId , out _ ) ;
20572089 }
20582090 }
20592091
2060- _pendingBreakpoints = new ConcurrentDictionary < int , LineBreakpoint > ( newPendingBreakpoints ) ;
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.
20612096 }
20622097
20632098 private void StopOnSequencePoint ( FunctionContext functionContext , List < Breakpoint > breakpoints )
0 commit comments