Skip to content

Commit

Permalink
Merge pull request #48 from JetBrains-Research/make-mining-process-ca…
Browse files Browse the repository at this point in the history
…ncellable

Make the mining process cancellable
  • Loading branch information
ioanasv authored Aug 11, 2020
2 parents 04550ca + 3e5fbd8 commit bedf625
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package org.jetbrains.research.refactorinsight.services;
package org.jetbrains.research.refactorinsight;

import com.intellij.AbstractBundle;
import com.intellij.reference.SoftReference;
import java.lang.ref.Reference;
import java.util.ResourceBundle;
import org.jetbrains.annotations.PropertyKey;

public final class RefactoringsBundle {
private static final String BUNDLE = "RefactoringsBundle";
public final class RefactorInsightBundle {
private static final String BUNDLE = "RefactorInsightBundle";
private static Reference<ResourceBundle> INSTANCE;

private RefactoringsBundle() {
private RefactorInsightBundle() {
}

public static String message(@PropertyKey(resourceBundle = BUNDLE) String key, Object... params) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.research.refactorinsight.services.MiningService;
import org.jetbrains.research.refactorinsight.services.RefactoringsBundle;
import org.jetbrains.research.refactorinsight.RefactorInsightBundle;


/**
Expand All @@ -25,8 +25,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
final List<GitRepository> repositories = GitRepositoryManager
.getInstance(e.getProject()).getRepositories();
if (repositories.isEmpty()) {
Messages.showErrorDialog(RefactoringsBundle.message("no.repo"),
RefactoringsBundle.message("name"));
Messages.showErrorDialog(RefactorInsightBundle.message("no.repo"),
RefactorInsightBundle.message("name"));
return;
}
GitRepository repository = repositories.get(0);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jetbrains.research.refactorinsight.processors;

import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
Expand All @@ -10,7 +11,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
Expand All @@ -19,7 +19,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.lib.Repository;
import org.jetbrains.research.refactorinsight.data.RefactoringEntry;
import org.jetbrains.research.refactorinsight.services.RefactoringsBundle;
import org.jetbrains.research.refactorinsight.RefactorInsightBundle;
import org.jetbrains.research.refactorinsight.services.MiningService;
import org.refactoringminer.api.GitHistoryRefactoringMiner;
import org.refactoringminer.api.GitService;
import org.refactoringminer.api.Refactoring;
Expand All @@ -33,7 +34,7 @@
* Consumes a git commit, calls RefactoringMiner and detects the refactorings for a commit.
*/
public class CommitMiner implements Consumer<GitCommit> {
private static final String progress = RefactoringsBundle.message("progress");
private static final String progress = RefactorInsightBundle.message("progress");
private final ExecutorService pool;
private final Map<String, RefactoringEntry> map;
private final Project myProject;
Expand All @@ -52,40 +53,29 @@ public class CommitMiner implements Consumer<GitCommit> {
public CommitMiner(ExecutorService pool, Map<String, RefactoringEntry> map,
GitRepository repository,
AtomicInteger commitsDone, ProgressIndicator progressIndicator, int limit) {

this.pool = pool;
this.map = map;
myProject = repository.getProject();
//NB: nullable, check if initialized correctly
myRepository = openRepository(myProject.getBasePath());
myRepository = ServiceManager.getService(myProject, MiningService.class).getRepository();
this.commitsDone = commitsDone;
this.progressIndicator = progressIndicator;
this.limit = limit;
}

private static Repository openRepository(final String path) {
try {
return new GitServiceImpl().openRepository(path);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* Method that mines only one commit.
*
* @param commit commit metadata
* @param map the inner map that should be updated
* @param project the current project
* @param repository Git Repository
*/
public static void mineAtCommit(VcsCommitMetadata commit, Map<String, RefactoringEntry> map,
Project project) {
GitService gitService = new GitServiceImpl();
Project project, Repository repository) {
GitHistoryRefactoringMiner miner = new GitHistoryRefactoringMinerImpl();
try {
miner.detectAtCommit(gitService.openRepository(project.getBasePath()),
commit.getId().asString(),
miner.detectAtCommit(repository, commit.getId().asString(),
new RefactoringHandler() {
@Override
public void handle(String commitId, List<Refactoring> refactorings) {
Expand All @@ -98,55 +88,6 @@ public void handle(String commitId, List<Refactoring> refactorings) {
}
}

/**
* Mines a single commit if the mining process does not take longer than
* 60 seconds.
*
* @param commit to be mined
* @param map refactorings map
* @param project the current project
*/
public static void mineAtCommitTimeout(VcsCommitMetadata commit,
Map<String, RefactoringEntry> map,
Project project) {
GitService gitService = new GitServiceImpl();
GitHistoryRefactoringMiner miner = new GitHistoryRefactoringMinerImpl();
ExecutorService service = Executors.newSingleThreadExecutor();
Future<?> f = null;
final Repository repository;
try {
repository = gitService.openRepository(project.getBasePath());
} catch (Exception e) {
return;
}
try {
Runnable r = () -> {
miner.detectAtCommit(repository,
commit.getId().asString(), new RefactoringHandler() {
@Override
public void handle(String commitId, List<Refactoring> refactorings) {
map.put(commitId,
RefactoringEntry
.convert(refactorings, commit, project));
}
});
};
f = service.submit(r);
f.get(60, TimeUnit.SECONDS);
} catch (TimeoutException e) {
if (f.cancel(true)) {
RefactoringEntry refactoringEntry = RefactoringEntry
.convert(new ArrayList<>(), commit, project);
refactoringEntry.setTimeout(true);
map.put(commit.getId().asString(), refactoringEntry);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
}

/**
* Mines a gitCommit.
* Method that calls RefactoringMiner and updates the refactoring map.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package org.jetbrains.research.refactorinsight.processors;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.util.ExceptionUtil;
import com.intellij.vcs.log.VcsCommitMetadata;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jgit.lib.Repository;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.research.refactorinsight.RefactorInsightBundle;
import org.jetbrains.research.refactorinsight.data.RefactoringEntry;
import org.jetbrains.research.refactorinsight.services.MiningService;
import org.jetbrains.research.refactorinsight.ui.windows.GitWindow;

public class SingleCommitRefactoringTask extends Task.Backgroundable {

private final Project project;
private final VcsCommitMetadata commit;
private final GitWindow window;
private final MiningService service;
private final Repository myRepository;
private boolean canceled = false;
private final Logger logger = Logger.getInstance(SingleCommitRefactoringTask.class);

/**
* Cancelable mining task for mining a single commit.
*
* @param project Current IDEA project
* @param commit Commit meta data
* @param window Git Window for callback
*/
public SingleCommitRefactoringTask(
@Nullable Project project,
VcsCommitMetadata commit,
GitWindow window) {
super(project,
String.format(RefactorInsightBundle.message("mining.at"), commit.getId().toShortString()));
this.project = project;
this.commit = commit;
this.window = window;
this.service = ServiceManager.getService(project, MiningService.class);
this.myRepository = service.getRepository();
}

@Override
public void onCancel() {
super.onCancel();
}

@Override
public void onFinished() {
super.onFinished();
if (service.containsCommit(commit.getId().asString())) {
System.out.println(RefactorInsightBundle.message("finished"));
ApplicationManager.getApplication()
.invokeLater(() -> window.refresh(commit.getId().asString()));
}
}

@Override
public void run(@NotNull ProgressIndicator progressIndicator) {
try {
runWithCheckCanceled(
() -> CommitMiner.mineAtCommit(commit, service.getState().refactoringsMap.map, project, myRepository),
progressIndicator, commit, project
);
} catch (Exception e) {
logger.info(String.format("The mining of refactorings at the commit %s was canceled", commit.getId()));
}
}

public void cancel() {
canceled = true;
}

/**
* Allows to interrupt a process which does not performs checkCancelled() calls by itself.
*/
public void runWithCheckCanceled(@NotNull final Runnable runnable,
@NotNull final ProgressIndicator indicator,
VcsCommitMetadata commit, Project project) throws Exception {
final Ref<Throwable> error = Ref.create();
Future<?> future = ApplicationManager.getApplication().executeOnPooledThread(
() -> ProgressManager.getInstance().executeProcessUnderProgress(() -> {
try {
runnable.run();
} catch (Throwable t) {
error.set(t);
}
}, indicator)
);
try {
runWithCheckCanceled(future, indicator, commit, project);
ExceptionUtil.rethrowAll(error.get());
} catch (ProcessCanceledException e) {
future.cancel(true);
throw e;
}
}

/**
* Waits for {@code future} to be complete or reach the maximum allowed mining time of 60 sec,
* or the current thread's indicator to be canceled.
*/
private <T> void runWithCheckCanceled(@NotNull Future<T> future,
@NotNull final ProgressIndicator indicator,
VcsCommitMetadata commit, Project project) throws
ExecutionException {
int timeout = 6000;
while (timeout > 0) {
if (canceled) {
indicator.cancel();
}
indicator.checkCanceled();
try {
future.get(10, TimeUnit.MILLISECONDS);
return;
} catch (InterruptedException e) {
throw new ProcessCanceledException(e);
} catch (TimeoutException ignored) {
logger.info("The timeout has been exceeded while checking task cancellation");
}
timeout -= 1;
}
if (timeout == 0) {
RefactoringEntry refactoringEntry = RefactoringEntry
.convert(new ArrayList<>(), commit, project);
refactoringEntry.setTimeout(true);
MiningService.getInstance(project).getState().refactoringsMap.map.put(commit.getId().asString(),
refactoringEntry);
}
}
}
Loading

0 comments on commit bedf625

Please sign in to comment.