Skip to content

Commit

Permalink
Make SSESessionManager ctor free threaded (#4863)
Browse files Browse the repository at this point in the history
* Make SSESessionManager ctor free threaded

* pr fixes
  • Loading branch information
bigfluffycookie authored and ugras-ergun-sonarsource committed Oct 9, 2023
1 parent 081ae38 commit a55491a
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,30 @@ public void MefCtor_CheckIsExported()
activeSolutionBoundTrackerMock.SetupGet(tracker => tracker.CurrentConfiguration)
.Returns(BindingConfiguration.Standalone);

MefTestHelpers.CheckTypeCanBeImported<SSESessionManager, ISSESessionManager>(
MefTestHelpers.CheckTypeCanBeImported<SSESessionManager, SSESessionManager>(
MefTestHelpers.CreateExport<IActiveSolutionBoundTracker>(activeSolutionBoundTrackerMock.Object),
MefTestHelpers.CreateExport<ISSESessionFactory>(),
MefTestHelpers.CreateExport<ILogger>());
}

[TestMethod]
public void Ctor_DoesNotCallAnyServices_BesidesExpected()
{
var activeSolutionBoundTracker = new Mock<IActiveSolutionBoundTracker>();
var sseSessionFactory = new Mock<ISSESessionFactory>();
var logger = new Mock<ILogger>();

var _ = new SSESessionManager(activeSolutionBoundTracker.Object, sseSessionFactory.Object, logger.Object);

// The MEF constructor should be free-threaded, which it will be if
// it doesn't make any external calls.

activeSolutionBoundTracker.VerifyAdd(tracker => tracker.SolutionBindingChanged += It.IsAny<EventHandler<ActiveSolutionBindingEventArgs>>(), Times.Once);
activeSolutionBoundTracker.VerifyNoOtherCalls();
sseSessionFactory.Invocations.Should().BeEmpty();
logger.Invocations.Should().BeEmpty();
}

[TestMethod]
public void Ctor_SubscribesToBindingChangedEvent()
{
Expand All @@ -58,47 +76,34 @@ public void Ctor_SubscribesToBindingChangedEvent()
}

[TestMethod]
public void Ctor_ForceConnectsAfterSubscriptionToBindingChangedEvent()
public void CreateSessionIfInConnectedMode_WhenInStandaloneModeOnCreation_DoesNotCreateSession()
{
var isSubscribed = false;
var testScope = new TestScope(TestScope.CreateConnectedModeBindingConfiguration(DefaultProjectKey));
testScope.ActiveSolutionBoundTrackerMock
.SetupAdd(solutionTracker => solutionTracker.SolutionBindingChanged += It.IsAny<EventHandler<ActiveSolutionBindingEventArgs>>()).
Callback(() =>
{
isSubscribed.Should().BeFalse();
isSubscribed = true;
});
testScope.SetUpSSEFactoryToReturnNoOpSSESession(DefaultProjectKey, factoryMockCallback: () => isSubscribed.Should().BeTrue());
var bindingConfig = BindingConfiguration.Standalone;
var testScope = new TestScope(bindingConfig);

var _ = testScope.CreateTestSubject();
var testSubject = testScope.CreateTestSubject();

testScope.ActiveSolutionBoundTrackerMock.VerifyAdd(tracker => tracker.SolutionBindingChanged += It.IsAny<EventHandler<ActiveSolutionBindingEventArgs>>(), Times.Once);
testScope.SSESessionFactoryMock.Verify(factory => factory.Create(DefaultProjectKey, It.IsAny<OnSessionFailedAsync>()), Times.Once);
testSubject.CreateSessionIfInConnectedMode(bindingConfig);

testScope.SSESessionFactoryMock.Verify(factory => factory.Create(DefaultProjectKey, It.IsAny<OnSessionFailedAsync>()), Times.Never);
}

[TestMethod]
public void Ctor_WhenInConnectedModeOnCreation_CreatesSession()
public void CreateSessionIfInConnectedMode_WhenInConnectedModeOnCreation_CreatesSession()
{
var testScope = new TestScope(TestScope.CreateConnectedModeBindingConfiguration(DefaultProjectKey));
var bindingConfig = TestScope.CreateConnectedModeBindingConfiguration(DefaultProjectKey);

var testScope = new TestScope(bindingConfig);
var sessionMock = testScope.SetUpSSEFactoryToReturnNoOpSSESession(DefaultProjectKey);

var _ = testScope.CreateTestSubject();
var testSubject = testScope.CreateTestSubject();

testSubject.CreateSessionIfInConnectedMode(bindingConfig);
testScope.SSESessionFactoryMock.Verify(factory => factory.Create(DefaultProjectKey, It.IsAny<OnSessionFailedAsync>()), Times.Once);
sessionMock.Verify(session => session.PumpAllAsync(), Times.Once);
}

[TestMethod]
public void Ctor_WhenInStandaloneModeOnCreation_DoesNotCreateSession()
{
var testScope = new TestScope(BindingConfiguration.Standalone);

var _ = testScope.CreateTestSubject();

testScope.SSESessionFactoryMock.Verify(factory => factory.Create(DefaultProjectKey, It.IsAny<OnSessionFailedAsync>()), Times.Never);
}

[TestMethod]
public void OnSolutionChanged_WhenChangesFromStandaloneToConnected_CreatesSessionAndLaunchesIt()
{
Expand Down Expand Up @@ -153,11 +158,14 @@ public void OnSolutionChanged_WhenChangesFromConnectedToConnected_CancelsSession
[DataTestMethod]
public async Task OnSessionFailed_CancelsSessionAndStartsNewOne()
{
var testScope = new TestScope(TestScope.CreateConnectedModeBindingConfiguration(DefaultProjectKey));
var bindingConfig = TestScope.CreateConnectedModeBindingConfiguration(DefaultProjectKey);

var testScope = new TestScope(bindingConfig);

var sessionMock1 = testScope.SetUpSSEFactoryToReturnNoOpSSESession(DefaultProjectKey);
sessionMock1.Setup(session => session.Dispose());
var _ = testScope.CreateTestSubject();
var testSubject = testScope.CreateTestSubject();
testSubject.CreateSessionIfInConnectedMode(bindingConfig);

var sessionMock2 = testScope.SetUpSSEFactoryToReturnNoOpSSESession(DefaultProjectKey);

Expand Down
21 changes: 14 additions & 7 deletions src/ConnectedMode/ConnectedModePackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace SonarLint.VisualStudio.ConnectedMode
[Guid("dd3427e0-7bb2-4a51-b00a-ddae2c32c7ef")]
public sealed class ConnectedModePackage : AsyncPackage
{
private ISSESessionManager sseSessionManager;
private SSESessionManager sseSessionManager;
private IIssueServerEventsListener issueServerEventsListener;
private IQualityProfileServerEventsListener qualityProfileServerEventsListener;
private ServerSuppressionsChangedHandler serverSuppressionsChangedHandler;
Expand All @@ -64,7 +64,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke

logger.WriteLine(Resources.Package_Initializing);

sseSessionManager = componentModel.GetService<ISSESessionManager>();
LoadServicesAndDoInitialUpdates(componentModel);

issueServerEventsListener = componentModel.GetService<IIssueServerEventsListener>();
issueServerEventsListener.ListenAsync().Forget();
Expand All @@ -84,17 +84,24 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
hotspotStoreMonitor = componentModel.GetService<ILocalHotspotStoreMonitor>();
await hotspotStoreMonitor.InitializeAsync();

// Trigger an initial update of suppressions (These classes might have missed the initial solution binding
// event from the ActiveSolutionBoundTracker)
// See https://github.com/SonarSource/sonarlint-visualstudio/issues/3886
logger.WriteLine(Resources.Package_Initialized);
}

/// <summary>
/// Trigger an initial update of classes that need them. (These classes might have missed the initial solution binding
/// event from the ActiveSolutionBoundTracker)
/// See https://github.com/SonarSource/sonarlint-visualstudio/issues/3886
/// </summary>
private void LoadServicesAndDoInitialUpdates(IComponentModel componentModel)
{
sseSessionManager = componentModel.GetService<SSESessionManager>();
sseSessionManager.CreateSessionIfInConnectedMode();
importBeforeInstallTrigger = componentModel.GetService<ImportBeforeInstallTrigger>();
importBeforeInstallTrigger.TriggerUpdateAsync().Forget();
var updater = componentModel.GetService<ISuppressionIssueStoreUpdater>();
updater.UpdateAllServerSuppressionsAsync().Forget();
var hotspotsUpdater = componentModel.GetService<IServerHotspotStoreUpdater>();
hotspotsUpdater.UpdateAllServerHotspotsAsync().Forget();

logger.WriteLine(Resources.Package_Initialized);
}

protected override void Dispose(bool disposing)
Expand Down
21 changes: 9 additions & 12 deletions src/ConnectedMode/ServerSentEvents/SSESessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,9 @@

namespace SonarLint.VisualStudio.ConnectedMode.ServerSentEvents
{
/// <summary>
/// Reacts to project changes and opens/closes Server Sent Events sessions
/// </summary>
internal interface ISSESessionManager : IDisposable
{
}

[Export(typeof(ISSESessionManager))]
[Export(typeof(SSESessionManager))]
[PartCreationPolicy(CreationPolicy.Shared)]
internal sealed class SSESessionManager : ISSESessionManager
internal sealed class SSESessionManager : IDisposable
{
private const int DelayTimeBetweenRetriesInMilliseconds = 1000;

Expand All @@ -59,8 +52,6 @@ public SSESessionManager(IActiveSolutionBoundTracker activeSolutionBoundTracker,
this.logger = logger;

activeSolutionBoundTracker.SolutionBindingChanged += SolutionBindingChanged;

CreateSessionIfInConnectedMode(activeSolutionBoundTracker.CurrentConfiguration);
}

public void Dispose()
Expand All @@ -81,8 +72,14 @@ private void SolutionBindingChanged(object sender, ActiveSolutionBindingEventArg
CreateSessionIfInConnectedMode(activeSolutionBindingEventArgs.Configuration);
}

private void CreateSessionIfInConnectedMode(BindingConfiguration bindingConfiguration)
/// <summary>
/// Creates a new session if in connected mode. If no binding configuration is provided the
/// ActiveSolutionBoundTracker.CurrentConfiguration will be used.
/// </summary>
public void CreateSessionIfInConnectedMode(BindingConfiguration bindingConfiguration = null)
{
if (bindingConfiguration == null) { bindingConfiguration = activeSolutionBoundTracker.CurrentConfiguration; }

lock (syncRoot)
{
EndCurrentSession();
Expand Down

0 comments on commit a55491a

Please sign in to comment.