fix(gui): prevent UI thread freeze on plugin install/uninstall

This commit is contained in:
Skylot
2025-06-01 19:46:38 +01:00
parent b82791706a
commit 7b54c3ae70
5 changed files with 57 additions and 12 deletions
@@ -16,9 +16,12 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
@@ -504,6 +507,27 @@ public class Utils {
}
}
public static ThreadFactory simpleThreadFactory(String name) {
return new SimpleThreadFactory(name);
}
private static final class SimpleThreadFactory implements ThreadFactory {
private static final AtomicInteger POOL = new AtomicInteger(0);
private final AtomicInteger number = new AtomicInteger(0);
private final String name;
public SimpleThreadFactory(String name) {
this.name = name;
}
@Override
public Thread newThread(@NotNull Runnable r) {
return new Thread(r, "jadx-" + name
+ '-' + POOL.incrementAndGet()
+ '-' + number.incrementAndGet());
}
}
/**
* @deprecated env vars shouldn't be used in core modules.
* Prefer to parse in `app` (use JadxCommonEnv from 'app-commons') and set in jadx args.
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class TaskExecutor implements ITaskExecutor {
@@ -99,7 +100,7 @@ public class TaskExecutor implements ITaskExecutor {
running.set(true);
progress.set(0);
terminating.set(false);
executor = Executors.newFixedThreadPool(1);
executor = Executors.newFixedThreadPool(1, Utils.simpleThreadFactory("task-s"));
executor.execute(this::runStages);
executor.shutdown();
}
@@ -142,7 +143,8 @@ public class TaskExecutor implements ITaskExecutor {
wrapTask(task);
}
} else {
ExecutorService parallelExecutor = Executors.newFixedThreadPool(threads);
ExecutorService parallelExecutor = Executors.newFixedThreadPool(
threads, Utils.simpleThreadFactory("task-p"));
for (Runnable task : stage.getTasks()) {
parallelExecutor.execute(() -> wrapTask(task));
}
@@ -22,6 +22,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
@@ -104,7 +105,7 @@ public class BackgroundExecutor {
}
private synchronized void reset() {
taskQueueExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
taskQueueExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1, Utils.simpleThreadFactory("bg"));
taskRunning.clear();
idSupplier.set(0);
}
@@ -162,6 +163,7 @@ public class BackgroundExecutor {
} finally {
taskComplete(id);
progressPane.changeVisibility(this, false);
removePropertyChangeListener(progressPane);
}
}
return status;
@@ -537,14 +537,19 @@ public class MainWindow extends JFrame {
}
public void reopen() {
synchronized (ReloadProject.EVENT) {
saveAll();
closeAll();
loadFiles(() -> {
menuBar.reloadShortcuts();
events().send(ReloadSettingsWindow.INSTANCE);
});
}
LOG.debug("starting reopen");
UiUtils.bgRun(() -> {
getBackgroundExecutor().waitForComplete();
synchronized (ReloadProject.EVENT) {
saveAll();
closeAll();
loadFiles(() -> {
menuBar.reloadShortcuts();
events().send(ReloadSettingsWindow.INSTANCE);
LOG.debug("reopen complete");
});
}
});
}
private void openProject(Path path, Runnable onFinish) {
@@ -622,7 +627,7 @@ public class MainWindow extends JFrame {
shortcutsController.reset();
UiUtils.resetClipboardOwner();
System.gc();
update();
UiUtils.uiRun(this::update);
}
private void checkLoadedStatus() {
@@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.AbstractAction;
import javax.swing.Action;
@@ -79,6 +81,8 @@ public class UiUtils {
}
};
private static final ExecutorService BACKGROUND_THREAD = Executors.newSingleThreadExecutor(Utils.simpleThreadFactory("utils-bg"));
private UiUtils() {
}
@@ -420,6 +424,14 @@ public class UiUtils {
}
}
/**
* Run task in background thread.
* Uses single thread, so all tasks are ordered.
*/
public static void bgRun(Runnable runnable) {
BACKGROUND_THREAD.submit(runnable);
}
public static void uiThreadGuard() {
if (!SwingUtilities.isEventDispatchThread()) {
LOG.warn("Expect UI thread, got: {}", Thread.currentThread(), new JadxRuntimeException());