Skip to content

Commit

Permalink
SLI-1603 Ensure UI thread does not call backend
Browse files Browse the repository at this point in the history
  • Loading branch information
eray-felek-sonarsource committed Sep 18, 2024
1 parent eb522ee commit 9215cee
Show file tree
Hide file tree
Showing 14 changed files with 108 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import org.sonarlint.intellij.ui.resolve.MarkAsResolvedDialog
import org.sonarlint.intellij.util.DataKeys.Companion.ISSUE_DATA_KEY
import org.sonarlint.intellij.util.DataKeys.Companion.TAINT_VULNERABILITY_DATA_KEY
import org.sonarlint.intellij.util.SonarLintAppUtils.findModuleForFile
import org.sonarlint.intellij.util.computeOnPooledThread
import org.sonarlint.intellij.util.runOnPooledThread
import org.sonarsource.sonarlint.core.client.utils.IssueResolutionStatus
import org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckStatusChangePermittedResponse
Expand Down Expand Up @@ -87,7 +88,9 @@ class MarkAsResolvedAction(
project, "No module could be found for this file"
)
val serverKey = issue.getServerKey() ?: issue.getId().toString()
val response = checkPermission(project, connection, serverKey) ?: return
val response = computeOnPooledThread(project, "Checking permission to mark issue as resolved") {
checkPermission(project, connection, serverKey)
} ?: return

if (response.isPermitted) {
runOnUiThread(project) {
Expand All @@ -97,7 +100,9 @@ class MarkAsResolvedAction(
response,
).chooseResolution() ?: return@runOnUiThread
if (confirm(project, connection.productName, resolution.newStatus)) {
markAsResolved(project, module, issue, resolution, serverKey)
runOnPooledThread(project) {
markAsResolved(project, module, issue, resolution, serverKey)
}
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.sonarlint.intellij.core.BackendService
import org.sonarlint.intellij.core.ProjectBindingManager
import org.sonarlint.intellij.finding.hotspot.LiveSecurityHotspot
import org.sonarlint.intellij.util.SonarLintAppUtils.findModuleForFile
import org.sonarlint.intellij.util.runOnPooledThread

class OpenSecurityHotspotInBrowserAction : AbstractSonarAction(
"Open In Browser",
Expand Down Expand Up @@ -57,7 +58,9 @@ class OpenSecurityHotspotInBrowserAction : AbstractSonarAction(
val key = securityHotspot?.getServerKey() ?: return
val localFile = securityHotspot.file()
val localFileModule = findModuleForFile(localFile, project) ?: return
getService(BackendService::class.java).openHotspotInBrowser(localFileModule, key)
runOnPooledThread(project) {
getService(BackendService::class.java).openHotspotInBrowser(localFileModule, key)
}
}

private fun serverConnection(project: Project): ServerConnection? = getService(project, ProjectBindingManager::class.java).tryGetServerConnection().orElse(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import org.sonarlint.intellij.notifications.SonarLintProjectNotifications
import org.sonarlint.intellij.ui.UiUtils
import org.sonarlint.intellij.util.DataKeys
import org.sonarlint.intellij.util.SonarLintAppUtils.findModuleForFile
import org.sonarlint.intellij.util.runOnPooledThread

private const val SKIP_CONFIRM_REOPEN_DIALOG_PROPERTY = "SonarLint.reopenIssue.hideConfirmation"

Expand Down Expand Up @@ -75,7 +76,7 @@ class ReopenIssueAction(private var issue: LiveIssue? = null) : AbstractSonarAct
serverKey ?: return displayNotificationError(project, "The issue key could not be found")

if (confirm(project, connection.productName)) {
reopenFinding(project, module, issue, serverKey)
runOnPooledThread(project) {reopenFinding(project, module, issue, serverKey)}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package org.sonarlint.intellij.actions
import com.intellij.openapi.actionSystem.AnActionEvent
import org.sonarlint.intellij.common.util.SonarLintUtils
import org.sonarlint.intellij.core.BackendService
import org.sonarlint.intellij.util.runOnPooledThread

class RestartBackendAction : AbstractSonarAction("Restart SonarLint Service") {

Expand All @@ -30,7 +31,9 @@ class RestartBackendAction : AbstractSonarAction("Restart SonarLint Service") {
}

override fun actionPerformed(e: AnActionEvent) {
SonarLintUtils.getService(BackendService::class.java).restartBackendService()
runOnPooledThread() {
SonarLintUtils.getService(BackendService::class.java).restartBackendService()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import org.sonarlint.intellij.tasks.FutureAwaitingTask
import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread
import org.sonarlint.intellij.ui.review.ReviewSecurityHotspotDialog
import org.sonarlint.intellij.util.SonarLintAppUtils.findModuleForFile
import org.sonarlint.intellij.util.computeOnPooledThread
import org.sonarlint.intellij.util.runOnPooledThread
import org.sonarsource.sonarlint.core.commons.HotspotReviewStatus
import org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedResponse
Expand Down Expand Up @@ -103,7 +104,10 @@ class ReviewSecurityHotspotAction(private var serverFindingKey: String? = null,
"No module could be found for this file"
)

val response = checkPermission(project, connection, hotspotKey) ?: return
val response = computeOnPooledThread(project, "Checking permission to mark issue as resolved") {
checkPermission(project, connection, hotspotKey)
} ?: return

val newStatus = HotspotStatus.valueOf(currentStatus.name)
runOnUiThread(project) {
if (ReviewSecurityHotspotDialog(project, connection.productName, module, hotspotKey, response, newStatus).showAndGet()) {
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/org/sonarlint/intellij/cayc/NewCodePeriodCache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,22 @@ import com.intellij.openapi.project.Project
import java.util.Locale
import org.sonarlint.intellij.common.util.SonarLintUtils.getService
import org.sonarlint.intellij.core.BackendService
import org.sonarlint.intellij.util.runOnPooledThread

@Service(Service.Level.PROJECT)
class NewCodePeriodCache(private val project: Project) {
var periodAsString: String = "(unknown code period)"

fun refreshAsync() {
getService(BackendService::class.java).getNewCodePeriodText(project)
.thenAccept { period ->
periodAsString = period.replaceFirstChar { char ->
char.lowercase(
Locale.getDefault()
)
runOnPooledThread(project) {
getService(BackendService::class.java).getNewCodePeriodText(project)
.thenAccept { period ->
periodAsString = period.replaceFirstChar { char ->
char.lowercase(
Locale.getDefault()
)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
import static org.sonarlint.intellij.config.Settings.getGlobalSettings;
import static org.sonarlint.intellij.telemetry.LinkTelemetry.RULE_SELECTION_PAGE;
import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread;
import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread;

public class RuleConfigurationPanel implements Disposable, ConfigurationPanel<SonarLintGlobalSettings> {
private static final String MAIN_SPLITTER_KEY = "sonarlint_rule_configuration_splitter";
Expand Down Expand Up @@ -308,7 +309,7 @@ private void restoreDefaults() {
r.getCustomParams().clear();
});
updateModel();
recomputeDirtyState();
runOnPooledThread(this::recomputeDirtyState);
}

private void updateModel() {
Expand Down Expand Up @@ -444,7 +445,7 @@ private JScrollPane initTreeScrollPane() {
// create tree table
model = new RulesTreeTableModel(new RulesTreeNode.Root());
table = new RulesTreeTable(model);
table.getModel().addTableModelListener(e -> recomputeDirtyState());
table.getModel().addTableModelListener(e ->runOnPooledThread(this::recomputeDirtyState));
table.setTreeCellRenderer(new RulesTreeTableRenderer(filterModel::getText));
table.setRootVisible(false);
TreeUtil.installActions(table.getTree());
Expand Down Expand Up @@ -507,42 +508,47 @@ private void updateParamsAndDescriptionPanel() {
}

private void updateParamsAndDescriptionPanel(RulesTreeNode.Rule singleNode) {
ruleHeaderPanel.updateForRuleConfiguration(singleNode.getKey(), singleNode.type(), singleNode.severity(), singleNode.attribute(), singleNode.impacts());
ruleHeaderPanel.updateForRuleConfiguration(singleNode.getKey(), singleNode.type(), singleNode.severity(), singleNode.attribute(),
singleNode.impacts());
var fileType = RuleLanguages.Companion.findFileTypeByRuleLanguage(singleNode.language());

getService(BackendService.class).getStandaloneRuleDetails(new GetStandaloneRuleDescriptionParams(singleNode.getKey()))
.thenAcceptAsync(details -> runOnUiThread(project, ModalityState.stateForComponent(getComponent()), () -> {
details.getDescription().map(
monolithDescription -> {
ruleDescription.addMonolith(monolithDescription, fileType);
return null;
},
withSections -> {
ruleDescription.addSections(withSections, fileType);
return null;
});

myParamsPanel.removeAll();
final var configPanelAnchor = new JBPanel<>(new GridLayout());
setConfigPanel(configPanelAnchor, singleNode, details.getRuleDefinition().getParamsByKey());
if (configPanelAnchor.getComponentCount() != 0) {
rulesParamsSeparator = new RulesParamsSeparator();
myParamsPanel.add(rulesParamsSeparator,
new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
JBUI.emptyInsets(), 0, 0));
myParamsPanel.add(configPanelAnchor, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH,
JBUI.insetsLeft(2), 0, 0));
} else {
myParamsPanel.add(configPanelAnchor, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH,
JBUI.insetsLeft(2), 0, 0));
}
myParamsPanel.revalidate();
myParamsPanel.repaint();
}))
.exceptionally(error -> {
GlobalLogOutput.get().log("Could not retrieve rule description", ClientLogOutput.Level.ERROR);
return null;
});
runOnPooledThread(project, () ->
getService(BackendService.class).getStandaloneRuleDetails(new GetStandaloneRuleDescriptionParams(singleNode.getKey()))
.thenAcceptAsync(details -> runOnUiThread(project, ModalityState.stateForComponent(getComponent()), () -> {
details.getDescription().map(
monolithDescription -> {
ruleDescription.addMonolith(monolithDescription, fileType);
return null;
},
withSections -> {
ruleDescription.addSections(withSections, fileType);
return null;
});

myParamsPanel.removeAll();
final var configPanelAnchor = new JBPanel<>(new GridLayout());
setConfigPanel(configPanelAnchor, singleNode, details.getRuleDefinition().getParamsByKey());
if (configPanelAnchor.getComponentCount() != 0) {
rulesParamsSeparator = new RulesParamsSeparator();
myParamsPanel.add(rulesParamsSeparator,
new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
JBUI.emptyInsets(), 0, 0));
myParamsPanel.add(configPanelAnchor, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST,
GridBagConstraints.BOTH,
JBUI.insetsLeft(2), 0, 0));
} else {
myParamsPanel.add(configPanelAnchor, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST,
GridBagConstraints.BOTH,
JBUI.insetsLeft(2), 0, 0));
}
myParamsPanel.revalidate();
myParamsPanel.repaint();
}))
.exceptionally(error -> {
GlobalLogOutput.get().log("Could not retrieve rule description", ClientLogOutput.Level.ERROR);
return null;
})
);
}

private void setConfigPanel(final JPanel configPanelAnchor, RulesTreeNode.Rule rule, Map<String, RuleParamDefinitionDto> paramsByKey) {
Expand Down Expand Up @@ -642,7 +648,7 @@ private void createBooleanParam(RulesTreeNode.Rule rule, JPanel panel, GridBagCo
} else {
rule.getCustomParams().remove(param.getKey());
}
recomputeDirtyState();
runOnPooledThread(this::recomputeDirtyState);
rulesParamsSeparator.updateDefaultLinkVisibility();
});
constraints.gridwidth = 2;
Expand All @@ -662,7 +668,8 @@ public void textChanged(DocumentEvent e) {
} else {
rule.getCustomParams().remove(param.getKey());
}
recomputeDirtyState();
runOnPooledThread(project, () -> recomputeDirtyState());

rulesParamsSeparator.updateDefaultLinkVisibility();
}
});
Expand Down Expand Up @@ -690,7 +697,7 @@ public void textChanged(DocumentEvent e) {
} else {
rule.getCustomParams().remove(param.getKey());
}
recomputeDirtyState();
runOnPooledThread(project, () -> recomputeDirtyState());
rulesParamsSeparator.updateDefaultLinkVisibility();
} catch (ParseException e1) {
// No luck this time
Expand Down Expand Up @@ -803,7 +810,7 @@ private class RulesParamsSeparator extends JPanel {
});
myDefaultsLink.setToolTipText("Restore current rule parameters to default values");
add(myDefaultsLink, defaultLabelConstraints);
recomputeDirtyState();
runOnPooledThread(project, RuleConfigurationPanel.this::recomputeDirtyState);
updateDefaultLinkVisibility();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.validate.ValidateConnectionResponse;

import static org.sonarlint.intellij.util.ThreadUtilsKt.computeOnPooledThread;
import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread;

public class AuthStep extends AbstractWizardStepEx {
private static final String LOGIN_ITEM = "Login / Password";
Expand Down Expand Up @@ -199,7 +200,13 @@ public boolean isComplete() {
public void commit(CommitType commitType) throws CommitStepException {
if (commitType == CommitType.Finish || commitType == CommitType.Next) {
save();
checkConnection();
runOnPooledThread(() -> {
try {
checkConnection();
} catch (CommitStepException e) {
throw new RuntimeException(e);
}
});
tryQueryIfNotificationsSupported();
tryQueryOrganizations();
}
Expand Down Expand Up @@ -234,6 +241,7 @@ private void checkConnection() throws CommitStepException {
ConnectionTestTask test = new ConnectionTestTask(tmpServer);
var msg = "Failed to connect to the server. Please check the configuration.";
ValidateConnectionResponse result;

try {
result = ProgressManager.getInstance().run(test);
} catch (Exception e) {
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/sonarlint/intellij/core/BackendService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ class BackendService : Disposable {
}

private fun notifyBackend(action: (SonarLintRpcServer) -> Unit) {
ensureBackendInitialized().thenAcceptAsync(action)
runOnPooledThread() {
ensureBackendInitialized().thenAcceptAsync(action)
}
}

private fun ensureBackendInitialized(): CompletableFuture<SonarLintRpcServer> {
Expand Down Expand Up @@ -518,7 +520,6 @@ class BackendService : Disposable {
ConfigurationScopeDto(projectId(project), null, true, project.name,
BindingConfigurationDto(binding?.connectionName, binding?.projectKey, areBindingSuggestionsDisabledFor(project)))


fun projectBound(project: Project, newBinding: ProjectBinding) {
notifyBackend {
it.configurationService.didUpdateBinding(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.sonarlint.intellij.documentation.SonarLintDocumentation
import org.sonarlint.intellij.notifications.SonarLintProjectNotifications.Companion.get
import org.sonarlint.intellij.sharing.SonarLintSharedFolderUtils.Companion.findSharedFolder
import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread
import org.sonarlint.intellij.util.runOnPooledThread
import org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetSharedConnectedModeConfigFileResponse
import org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto

Expand All @@ -50,7 +51,9 @@ class ConfigurationSharing {
if (project == null || project.isDisposed) return

if (confirm(project)) {
createFile(project, modalityState)
runOnPooledThread(project) {
createFile(project, modalityState)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.sonarlint.intellij.core.BackendService;

import static org.sonarlint.intellij.util.ProgressUtils.waitForFuture;
import static org.sonarlint.intellij.util.ThreadUtilsKt.computeOnPooledThread;

/**
* Only useful for SonarQube, since we know notifications are available in SonarCloud
Expand All @@ -48,7 +49,8 @@ public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(false);
try {
indicator.setText("Checking support of notifications");
notificationsSupported = waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).checkSmartNotificationsSupported(connection)).isSuccess();
notificationsSupported = Boolean.TRUE.equals(computeOnPooledThread("Get Notification Supported", () ->
waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).checkSmartNotificationsSupported(connection)).isSuccess()));
} catch (ProcessCanceledException e) {
if (myProject != null) {
SonarLintConsole.get(myProject).error("Failed to check notifications", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.OrganizationDto;

import static org.sonarlint.intellij.util.ProgressUtils.waitForFuture;
import static org.sonarlint.intellij.util.ThreadUtilsKt.computeOnPooledThread;

public class GetOrganizationTask extends Task.Modal {
private final ServerConnection server;
Expand All @@ -50,7 +51,8 @@ public void run(@NotNull ProgressIndicator indicator) {

try {
indicator.setText("Searching organization");
organization = waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).getOrganization(server, organizationKey)).getOrganization();
organization = computeOnPooledThread("Get User Organizations", () ->
waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).getOrganization(server, organizationKey)).getOrganization());
} catch (Exception e) {
SonarLintConsole.get(myProject).error("Failed to fetch organizations", e);
exception = e;
Expand Down
Loading

0 comments on commit 9215cee

Please sign in to comment.