Skip to content

Commit

Permalink
SLI-1239 Avoid busy-waiting for automatic analysis trigger
Browse files Browse the repository at this point in the history
  • Loading branch information
nquinquenel committed Oct 15, 2024
1 parent c7a4eed commit 598d34c
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,7 @@
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.concurrent.ThreadSafe;
import org.sonarlint.intellij.messages.AnalysisListener;

import static org.sonarlint.intellij.config.Settings.getGlobalSettings;
import static org.sonarlint.intellij.util.SonarLintAppUtils.guessProjectForFile;
Expand All @@ -41,26 +35,15 @@
@Service(Service.Level.PROJECT)
public final class EditorChangeTrigger implements DocumentListener, Disposable {

// entries in this map mean that the file is "dirty"
private final ConcurrentHashMap<VirtualFile, Long> eventMap = new ConcurrentHashMap<>();
private final EventWatcher watcher;
private final EventScheduler scheduler;
private final Project myProject;

public EditorChangeTrigger(Project project) {
myProject = project;
watcher = new EventWatcher(myProject, "change", eventMap, TriggerType.EDITOR_CHANGE, 2000);
scheduler = new EventScheduler(myProject, "change", TriggerType.EDITOR_CHANGE, 2000, false);
}

public void onProjectOpened() {
myProject.getMessageBus()
.connect()
.subscribe(AnalysisListener.TOPIC, new AnalysisListener.Adapter() {
@Override
public void started(Collection<VirtualFile> files, TriggerType trigger) {
removeFiles(files);
}
});
watcher.start();
EditorFactory.getInstance().getEventMulticaster().addDocumentListener(this, this);
}

Expand All @@ -84,24 +67,12 @@ public void documentChanged(DocumentEvent event) {
return;
}

eventMap.put(file, System.currentTimeMillis());
}

/**
* Marks a file as launched, resetting its state to unchanged
*/
private void removeFiles(Collection<VirtualFile> files) {
files.forEach(eventMap::remove);
}

Map<VirtualFile, Long> getEvents() {
return Collections.unmodifiableMap(eventMap);
scheduler.notify(file);
}

@Override
public void dispose() {
eventMap.clear();
watcher.stopWatcher();
scheduler.stopScheduler();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,12 @@
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.concurrent.ThreadSafe;
import org.jetbrains.annotations.NotNull;
import org.sonarlint.intellij.core.BackendService;
import org.sonarlint.intellij.fs.VirtualFileEvent;
import org.sonarlint.intellij.messages.AnalysisListener;
import org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileEvent;

import static org.sonarlint.intellij.common.util.SonarLintUtils.getService;
Expand All @@ -44,35 +41,20 @@
@Service(Service.Level.PROJECT)
public final class EditorOpenTrigger implements FileEditorManagerListener, Disposable {

// entries in this map mean that the file is "dirty"
private final ConcurrentHashMap<VirtualFile, Long> eventMap = new ConcurrentHashMap<>();
private final EventWatcher watcher;
private final EventScheduler scheduler;
private final Project myProject;

public EditorOpenTrigger(Project project) {
myProject = project;
watcher = new EventWatcher(myProject, "open", eventMap, TriggerType.EDITOR_OPEN, 1000);
scheduler = new EventScheduler(myProject, "open", TriggerType.EDITOR_OPEN, 1000, true);
}

public void onProjectOpened() {
myProject.getMessageBus()
.connect()
.subscribe(AnalysisListener.TOPIC, new AnalysisListener.Adapter() {
@Override
public void started(Collection<VirtualFile> files, TriggerType trigger) {
removeFiles(files);
}
});
watcher.start();
myProject.getMessageBus()
.connect()
.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this);
}

private void removeFiles(Collection<VirtualFile> files) {
files.forEach(eventMap::remove);
}

@Override
public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
runOnPooledThread(source.getProject(), () -> {
Expand All @@ -81,15 +63,14 @@ public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile f
getService(BackendService.class).updateFileSystem(Map.of(module, List.of(new VirtualFileEvent(ModuleFileEvent.Type.CREATED, file))));
}
if (source.getProject().equals(myProject)) {
eventMap.put(file, System.currentTimeMillis());
scheduler.notify(file);
}
});
}

@Override
public void dispose() {
eventMap.clear();
watcher.stopWatcher();
scheduler.stopScheduler();
}

}
82 changes: 82 additions & 0 deletions src/main/java/org/sonarlint/intellij/trigger/EventScheduler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* SonarLint for IntelliJ IDEA
* Copyright (C) 2015-2024 SonarSource
* [email protected]
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonarlint.intellij.trigger

import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import org.sonarlint.intellij.analysis.AnalysisSubmitter
import org.sonarlint.intellij.common.util.SonarLintUtils.getService
import org.sonarlint.intellij.config.Settings
import org.sonarlint.intellij.util.SonarLintAppUtils.retainOpenFiles

class EventScheduler(
private val project: Project,
private val schedulerName: String,
private val triggerType: TriggerType,
private val timer: Long,
// True -> Schedule tasks at specific intervals
// False -> Cancel the scheduled task and reschedule a new one
private val atInterval: Boolean
) {

private val filesToAnalyze = mutableSetOf<VirtualFile>()
private val scheduler = Executors.newScheduledThreadPool(1) { r -> Thread(r, "sonarlint-auto-trigger-$schedulerName-${project.name}") }
private var scheduledTask: ScheduledFuture<*>? = null

fun stopScheduler() {
scheduledTask?.cancel(true)
filesToAnalyze.clear()
scheduler.shutdownNow()
}

private fun trigger() {
if (Settings.getGlobalSettings().isAutoTrigger) {
val openFileToAnalyze = retainOpenFiles(project, filesToAnalyze.toList())
if (openFileToAnalyze.isNotEmpty()) {
getService(project, AnalysisSubmitter::class.java).autoAnalyzeFiles(openFileToAnalyze, triggerType)
filesToAnalyze.clear()
}
}
}

fun notify(file: VirtualFile) {
// Remove the previously finished task
if (scheduledTask?.isDone == true) {
scheduledTask = null
}

if (atInterval) {
scheduledTask ?: let {
// Schedule new task only if no task currently scheduled
scheduledTask = scheduler.schedule({ trigger() }, timer, TimeUnit.MILLISECONDS)
}
} else {
// Cancelling the scheduled task and postponing it later
scheduledTask?.cancel(false)
scheduledTask = scheduler.schedule({ trigger() }, timer, TimeUnit.MILLISECONDS)
}

filesToAnalyze.add(file)
}

}
100 changes: 0 additions & 100 deletions src/main/java/org/sonarlint/intellij/trigger/EventWatcher.kt

This file was deleted.

Loading

0 comments on commit 598d34c

Please sign in to comment.