@@ -82,7 +82,7 @@ internal sealed class DebuggingSession : IDisposable
82
82
/// read lock is acquired before every operation that may access a baseline module/symbol reader
83
83
/// and write lock when the baseline readers are being disposed.
84
84
/// </summary>
85
- private readonly ReaderWriterLockSlim _baselineAccessLock = new ( ) ;
85
+ private readonly ReaderWriterLockSlim _baselineContentAccessLock = new ( ) ;
86
86
private bool _isDisposed ;
87
87
88
88
internal EditSession EditSession { get ; private set ; }
@@ -168,7 +168,7 @@ public void Dispose()
168
168
_cancellationSource . Dispose ( ) ;
169
169
170
170
// Wait for all operations on baseline to finish before we dispose the readers.
171
- _baselineAccessLock . EnterWriteLock ( ) ;
171
+ _baselineContentAccessLock . EnterWriteLock ( ) ;
172
172
173
173
lock ( _projectEmitBaselinesGuard )
174
174
{
@@ -179,8 +179,8 @@ public void Dispose()
179
179
}
180
180
}
181
181
182
- _baselineAccessLock . ExitWriteLock ( ) ;
183
- _baselineAccessLock . Dispose ( ) ;
182
+ _baselineContentAccessLock . ExitWriteLock ( ) ;
183
+ _baselineContentAccessLock . Dispose ( ) ;
184
184
185
185
if ( Interlocked . Exchange ( ref _pendingUpdate , null ) != null )
186
186
{
@@ -312,7 +312,7 @@ internal ImmutableList<ProjectBaseline> GetOrCreateEmitBaselines(
312
312
ArrayBuilder < Diagnostic > diagnostics ,
313
313
out ReaderWriterLockSlim baselineAccessLock )
314
314
{
315
- baselineAccessLock = _baselineAccessLock ;
315
+ baselineAccessLock = _baselineContentAccessLock ;
316
316
317
317
ImmutableList < ProjectBaseline > ? existingBaselines ;
318
318
lock ( _projectEmitBaselinesGuard )
@@ -521,53 +521,74 @@ public async ValueTask<EmitSolutionUpdateResults> EmitSolutionUpdateAsync(
521
521
// Make sure the solution snapshot has all source-generated documents up-to-date.
522
522
solution = solution . WithUpToDateSourceGeneratorDocuments ( solution . ProjectIds ) ;
523
523
524
- var solutionUpdate = await EditSession . EmitSolutionUpdateAsync ( solution , activeStatementSpanProvider , updateId , cancellationToken ) . ConfigureAwait ( false ) ;
524
+ var solutionUpdate = await EditSession . EmitSolutionUpdateAsync ( solution , activeStatementSpanProvider , updateId , runningProjects , cancellationToken ) . ConfigureAwait ( false ) ;
525
+
526
+ var allowPartialUpdates = runningProjects . Any ( p => p . Value . AllowPartialUpdate ) ;
525
527
526
528
solutionUpdate . Log ( SessionLog , updateId ) ;
527
529
_lastModuleUpdatesLog = solutionUpdate . ModuleUpdates . Updates ;
528
530
529
531
switch ( solutionUpdate . ModuleUpdates . Status )
530
532
{
531
533
case ModuleUpdateStatus . Ready :
532
- // We have updates to be applied. The debugger will call Commit/Discard on the solution
534
+ Contract . ThrowIfTrue ( solutionUpdate . ModuleUpdates . Updates . IsEmpty && solutionUpdate . ProjectsToRebuild . IsEmpty ) ;
535
+
536
+ // We have updates to be applied or processes to restart. The debugger will call Commit/Discard on the solution
533
537
// based on whether the updates will be applied successfully or not.
534
- StorePendingUpdate ( new PendingSolutionUpdate (
535
- solution ,
536
- solutionUpdate . ProjectsToStale ,
537
- solutionUpdate . ProjectBaselines ,
538
- solutionUpdate . ModuleUpdates . Updates ,
539
- solutionUpdate . NonRemappableRegions ) ) ;
538
+
539
+ if ( allowPartialUpdates )
540
+ {
541
+ StorePendingUpdate ( new PendingSolutionUpdate (
542
+ solution ,
543
+ solutionUpdate . ProjectsToStale ,
544
+ solutionUpdate . ProjectsToRebuild ,
545
+ solutionUpdate . ProjectBaselines ,
546
+ solutionUpdate . ModuleUpdates . Updates ,
547
+ solutionUpdate . NonRemappableRegions ) ) ;
548
+ }
549
+ else if ( solutionUpdate . ProjectsToRebuild . IsEmpty )
550
+ {
551
+ // no rude edits
552
+
553
+ StorePendingUpdate ( new PendingSolutionUpdate (
554
+ solution ,
555
+ solutionUpdate . ProjectsToStale ,
556
+ // if partial updates are not allowed we don't treat rebuild as part of solution update:
557
+ projectsToRebuild : [ ] ,
558
+ solutionUpdate . ProjectBaselines ,
559
+ solutionUpdate . ModuleUpdates . Updates ,
560
+ solutionUpdate . NonRemappableRegions ) ) ;
561
+ }
540
562
541
563
break ;
542
564
543
565
case ModuleUpdateStatus . None :
544
566
Contract . ThrowIfFalse ( solutionUpdate . ModuleUpdates . Updates . IsEmpty ) ;
545
567
Contract . ThrowIfFalse ( solutionUpdate . NonRemappableRegions . IsEmpty ) ;
546
568
569
+ // Insignificant changes should not cause rebuilds/restarts:
570
+ Contract . ThrowIfFalse ( solutionUpdate . ProjectsToRestart . IsEmpty ) ;
571
+ Contract . ThrowIfFalse ( solutionUpdate . ProjectsToRebuild . IsEmpty ) ;
572
+
547
573
// No significant changes have been made.
548
574
// Commit the solution to apply any insignificant changes that do not generate updates.
549
575
LastCommittedSolution . CommitChanges ( solution , projectsToStale : solutionUpdate . ProjectsToStale , projectsToUnstale : [ ] ) ;
550
576
break ;
551
577
}
552
578
553
- EmitSolutionUpdateResults . GetProjectsToRebuildAndRestart (
554
- solution ,
555
- solutionUpdate . ModuleUpdates ,
556
- solutionUpdate . Diagnostics ,
557
- runningProjects ,
558
- out var projectsToRestart ,
559
- out var projectsToRebuild ) ;
560
-
561
579
// Note that we may return empty deltas if all updates have been deferred.
562
580
// The debugger will still call commit or discard on the update batch.
563
581
return new EmitSolutionUpdateResults ( )
564
582
{
565
583
Solution = solution ,
566
- ModuleUpdates = solutionUpdate . ModuleUpdates ,
584
+ // If partial updates are disabled the debugger does not expect module updates when rude edits are reported:
585
+ ModuleUpdates = allowPartialUpdates || solutionUpdate . ProjectsToRebuild . IsEmpty
586
+ ? solutionUpdate . ModuleUpdates
587
+ : new ModuleUpdates ( solutionUpdate . ModuleUpdates . Status , [ ] ) ,
567
588
Diagnostics = solutionUpdate . Diagnostics ,
568
589
SyntaxError = solutionUpdate . SyntaxError ,
569
- ProjectsToRestart = projectsToRestart ,
570
- ProjectsToRebuild = projectsToRebuild
590
+ ProjectsToRestart = solutionUpdate . ProjectsToRestart ,
591
+ ProjectsToRebuild = solutionUpdate . ProjectsToRebuild
571
592
} ;
572
593
}
573
594
@@ -576,6 +597,7 @@ public void CommitSolutionUpdate()
576
597
ThrowIfDisposed ( ) ;
577
598
578
599
ImmutableDictionary < ManagedMethodId , ImmutableArray < NonRemappableRegion > > ? newNonRemappableRegions = null ;
600
+ using var _ = PooledHashSet < ProjectId > . GetInstance ( out var projectsToRebuildTransitive ) ;
579
601
580
602
var pendingUpdate = RetrievePendingUpdate ( ) ;
581
603
if ( pendingUpdate is PendingSolutionUpdate pendingSolutionUpdate )
@@ -591,18 +613,44 @@ from region in moduleRegions.Regions
591
613
if ( newNonRemappableRegions . IsEmpty )
592
614
newNonRemappableRegions = null ;
593
615
594
- LastCommittedSolution . CommitChanges ( pendingSolutionUpdate . Solution , projectsToStale : pendingSolutionUpdate . ProjectsToStale , projectsToUnstale : [ ] ) ;
616
+ var solution = pendingSolutionUpdate . Solution ;
617
+
618
+ // Once the project is rebuilt all its dependencies are going to be up-to-date.
619
+ var dependencyGraph = solution . GetProjectDependencyGraph ( ) ;
620
+ foreach ( var projectId in pendingSolutionUpdate . ProjectsToRebuild )
621
+ {
622
+ projectsToRebuildTransitive . Add ( projectId ) ;
623
+ projectsToRebuildTransitive . AddRange ( dependencyGraph . GetProjectsThatThisProjectTransitivelyDependsOn ( projectId ) ) ;
624
+ }
625
+
626
+ // Unstale all projects that will be up-to-date after rebuild.
627
+ LastCommittedSolution . CommitChanges ( solution , projectsToStale : pendingSolutionUpdate . ProjectsToStale , projectsToUnstale : projectsToRebuildTransitive ) ;
628
+
629
+ foreach ( var projectId in projectsToRebuildTransitive )
630
+ {
631
+ _editSessionTelemetry . LogUpdatedBaseline ( solution . GetRequiredProject ( projectId ) . State . ProjectInfo . Attributes . TelemetryId ) ;
632
+ }
595
633
}
596
634
597
635
// update baselines:
636
+
637
+ // Wait for all operations on baseline content to finish before we dispose the readers.
638
+ _baselineContentAccessLock . EnterWriteLock ( ) ;
639
+
598
640
lock ( _projectEmitBaselinesGuard )
599
641
{
600
642
foreach ( var updatedBaseline in pendingUpdate . ProjectBaselines )
601
643
{
602
644
_projectBaselines [ updatedBaseline . ProjectId ] = [ .. _projectBaselines [ updatedBaseline . ProjectId ] . Select ( existingBaseline => existingBaseline . ModuleId == updatedBaseline . ModuleId ? updatedBaseline : existingBaseline ) ] ;
603
645
}
646
+
647
+ // Discard any open baseline readers for projects that need to be rebuilt,
648
+ // so that the build can overwrite the underlying files.
649
+ DiscardProjectBaselinesNoLock ( projectsToRebuildTransitive ) ;
604
650
}
605
651
652
+ _baselineContentAccessLock . ExitWriteLock ( ) ;
653
+
606
654
_editSessionTelemetry . LogCommitted ( ) ;
607
655
608
656
// Restart edit session with no active statements (switching to run mode).
@@ -615,6 +663,28 @@ public void DiscardSolutionUpdate()
615
663
_ = RetrievePendingUpdate ( ) ;
616
664
}
617
665
666
+ private void DiscardProjectBaselinesNoLock ( IEnumerable < ProjectId > projects )
667
+ {
668
+ foreach ( var projectId in projects )
669
+ {
670
+ if ( _projectBaselines . TryGetValue ( projectId , out var projectBaselines ) )
671
+ {
672
+ // remove all versions of modules associated with the project:
673
+ _projectBaselines . Remove ( projectId ) ;
674
+
675
+ foreach ( var projectBaseline in projectBaselines )
676
+ {
677
+ var ( metadata , pdb ) = _initialBaselineModuleReaders [ projectBaseline . ModuleId ] ;
678
+ metadata . Dispose ( ) ;
679
+ pdb . Dispose ( ) ;
680
+
681
+ _initialBaselineModuleReaders . Remove ( projectBaseline . ModuleId ) ;
682
+ }
683
+ }
684
+ }
685
+ }
686
+
687
+ // TODO: remove once the debugger implements https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2459003
618
688
public void UpdateBaselines ( Solution solution , ImmutableArray < ProjectId > rebuiltProjects )
619
689
{
620
690
ThrowIfDisposed ( ) ;
@@ -625,30 +695,15 @@ public void UpdateBaselines(Solution solution, ImmutableArray<ProjectId> rebuilt
625
695
LastCommittedSolution . CommitChanges ( solution , projectsToStale : [ ] , projectsToUnstale : rebuiltProjects ) ;
626
696
627
697
// Wait for all operations on baseline to finish before we dispose the readers.
628
- _baselineAccessLock . EnterWriteLock ( ) ;
698
+
699
+ _baselineContentAccessLock . EnterWriteLock ( ) ;
629
700
630
701
lock ( _projectEmitBaselinesGuard )
631
702
{
632
- foreach ( var projectId in rebuiltProjects )
633
- {
634
- if ( _projectBaselines . TryGetValue ( projectId , out var projectBaselines ) )
635
- {
636
- // remove all versions of modules associated with the project:
637
- _projectBaselines . Remove ( projectId ) ;
638
-
639
- foreach ( var projectBaseline in projectBaselines )
640
- {
641
- var ( metadata , pdb ) = _initialBaselineModuleReaders [ projectBaseline . ModuleId ] ;
642
- metadata . Dispose ( ) ;
643
- pdb . Dispose ( ) ;
644
-
645
- _initialBaselineModuleReaders . Remove ( projectBaseline . ModuleId ) ;
646
- }
647
- }
648
- }
703
+ DiscardProjectBaselinesNoLock ( rebuiltProjects ) ;
649
704
}
650
705
651
- _baselineAccessLock . ExitWriteLock ( ) ;
706
+ _baselineContentAccessLock . ExitWriteLock ( ) ;
652
707
653
708
foreach ( var projectId in rebuiltProjects )
654
709
{
0 commit comments