refactor: add new task executor, remove task barrier (#1879)

This commit is contained in:
Skylot
2024-01-23 20:45:19 +00:00
parent e73612b4d2
commit 75d2e540aa
15 changed files with 398 additions and 192 deletions
@@ -102,6 +102,7 @@ public class JadxCLI {
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
jadx.save();
} else {
LOG.info("processing ...");
jadx.save(500, (done, total) -> {
int progress = (int) (done * 100.0 / total);
System.out.printf("INFO - progress: %d of %d (%d%%)\r", done, total, progress);
@@ -12,9 +12,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -36,6 +33,7 @@ import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.pass.JadxPass;
import jadx.api.plugins.pass.types.JadxAfterLoadPass;
import jadx.api.plugins.pass.types.JadxPassType;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
@@ -52,6 +50,7 @@ import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.tasks.TaskExecutor;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ProtoXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
@@ -248,13 +247,12 @@ public final class JadxDecompiler implements Closeable {
@SuppressWarnings("BusyWait")
public void save(int intervalInMillis, ProgressListener listener) {
ThreadPoolExecutor ex = (ThreadPoolExecutor) getSaveExecutor();
ex.shutdown();
try {
long total = ex.getTaskCount();
while (ex.isTerminating()) {
long done = ex.getCompletedTaskCount();
listener.progress(done, total);
ITaskExecutor tasks = getSaveTaskExecutor();
tasks.execute();
long total = tasks.getTasksCount();
while (tasks.isRunning()) {
listener.progress(tasks.getProgress(), total);
Thread.sleep(intervalInMillis);
}
} catch (InterruptedException e) {
@@ -271,72 +269,64 @@ public final class JadxDecompiler implements Closeable {
save(false, true);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void save(boolean saveSources, boolean saveResources) {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
try {
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
Thread.currentThread().interrupt();
}
ITaskExecutor executor = getSaveTasks(saveSources, saveResources);
executor.execute();
executor.awaitTermination();
}
public ExecutorService getSaveExecutor() {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
}
public List<Runnable> getSaveTasks() {
public ITaskExecutor getSaveTaskExecutor() {
return getSaveTasks(!args.isSkipSources(), !args.isSkipResources());
}
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
List<Runnable> tasks = getSaveTasks(saveSources, saveResources);
tasks.forEach(executor::execute);
return executor;
@Deprecated(forRemoval = true)
public ExecutorService getSaveExecutor() {
ITaskExecutor executor = getSaveTaskExecutor();
executor.execute();
return executor.getInternalExecutor();
}
private List<Runnable> getSaveTasks(boolean saveSources, boolean saveResources) {
@Deprecated(forRemoval = true)
public List<Runnable> getSaveTasks() {
return Collections.singletonList(this::save);
}
private TaskExecutor getSaveTasks(boolean saveSources, boolean saveResources) {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
File sourcesOutDir;
File resOutDir;
List<Runnable> tasks = new ArrayList<>();
TaskBarrier barrier = new TaskBarrier();
ExportGradleTask gradleExportTask;
if (args.isExportAsGradleProject()) {
ExportGradleTask gradleExportTask = new ExportGradleTask(resources, root, args.getOutDir(), barrier);
gradleExportTask = new ExportGradleTask(resources, root, args.getOutDir());
gradleExportTask.init();
sourcesOutDir = gradleExportTask.getSrcOutDir();
resOutDir = gradleExportTask.getResOutDir();
tasks.add(gradleExportTask);
} else {
sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes();
gradleExportTask = null;
}
int taskCount = 0;
// save resources first because decompilation can hang or fail
TaskExecutor executor = new TaskExecutor();
executor.setThreadsCount(args.getThreadsCount());
if (saveResources) {
taskCount = appendResourcesSaveTasks(tasks, resOutDir, barrier);
// save resources first because decompilation can stop or fail
appendResourcesSaveTasks(executor, resOutDir);
}
if (saveSources) {
taskCount += appendSourcesSave(tasks, sourcesOutDir, barrier);
appendSourcesSave(executor, sourcesOutDir);
}
barrier.setUpBarrier(taskCount);
return tasks;
if (gradleExportTask != null) {
executor.addSequentialTask(gradleExportTask);
}
return executor;
}
private int appendResourcesSaveTasks(List<Runnable> tasks, File outDir, TaskBarrier barrier) {
int numResourceTasks = 0;
private void appendResourcesSaveTasks(ITaskExecutor executor, File outDir) {
if (args.isSkipFilesSave()) {
return 0;
return;
}
// process AndroidManifest.xml first to load complete resource ids table
for (ResourceFile resourceFile : getResources()) {
@@ -345,8 +335,10 @@ public final class JadxDecompiler implements Closeable {
break;
}
}
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
Set<String> inputFileNames = args.getInputFiles().stream()
.map(File::getAbsolutePath)
.collect(Collectors.toSet());
List<Runnable> tasks = new ArrayList<>();
for (ResourceFile resourceFile : getResources()) {
ResourceType resType = resourceFile.getType();
if (resType == ResourceType.MANIFEST) {
@@ -358,17 +350,41 @@ public final class JadxDecompiler implements Closeable {
// ignore resource made from input file
continue;
}
tasks.add(new ResourcesSaver(outDir, resourceFile, barrier));
numResourceTasks++;
tasks.add(new ResourcesSaver(outDir, resourceFile));
}
return numResourceTasks;
executor.addParallelTasks(tasks);
}
private int appendSourcesSave(List<Runnable> tasks, File outDir, TaskBarrier barrier) {
int numSourceTasks = 0;
Predicate<String> classFilter = args.getClassFilter();
private void appendSourcesSave(ITaskExecutor executor, File outDir) {
List<JavaClass> classes = getClasses();
List<JavaClass> processQueue = new ArrayList<>(classes.size());
List<JavaClass> processQueue = filterClasses(classes);
List<List<JavaClass>> batches;
try {
batches = decompileScheduler.buildBatches(processQueue);
} catch (Exception e) {
throw new JadxRuntimeException("Decompilation batches build failed", e);
}
List<Runnable> decompileTasks = new ArrayList<>(batches.size());
for (List<JavaClass> decompileBatch : batches) {
decompileTasks.add(() -> {
for (JavaClass cls : decompileBatch) {
try {
ClassNode clsNode = cls.getClassNode();
ICodeInfo code = clsNode.getCode();
SaveCode.save(outDir, clsNode, code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
}
});
}
executor.addParallelTasks(decompileTasks);
}
private List<JavaClass> filterClasses(List<JavaClass> classes) {
Predicate<String> classFilter = args.getClassFilter();
List<JavaClass> list = new ArrayList<>(classes.size());
for (JavaClass cls : classes) {
ClassNode clsNode = cls.getClassNode();
if (clsNode.contains(AFlag.DONT_GENERATE)) {
@@ -380,35 +396,9 @@ public final class JadxDecompiler implements Closeable {
}
continue;
}
processQueue.add(cls);
list.add(cls);
}
List<List<JavaClass>> batches;
try {
batches = decompileScheduler.buildBatches(processQueue);
} catch (Exception e) {
throw new JadxRuntimeException("Decompilation batches build failed", e);
}
for (List<JavaClass> decompileBatch : batches) {
tasks.add(() -> {
try {
for (JavaClass cls : decompileBatch) {
try {
ClassNode clsNode = cls.getClassNode();
ICodeInfo code = clsNode.getCode();
SaveCode.save(outDir, clsNode, code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
}
} finally {
if (barrier != null) {
barrier.finishTask();
}
}
});
numSourceTasks++;
}
return numSourceTasks;
return list;
}
public List<JavaClass> getClasses() {
@@ -1,22 +0,0 @@
package jadx.api;
import java.util.concurrent.CountDownLatch;
public class TaskBarrier {
private CountDownLatch taskCountDown = null;
public void setUpBarrier(final int numTasks) {
taskCountDown = new CountDownLatch(numTasks);
}
public CountDownLatch getTaskCountDown() {
return taskCountDown;
}
public void finishTask() {
if (taskCountDown != null) {
taskCountDown.countDown();
}
}
}
@@ -0,0 +1,69 @@
package jadx.api.utils.tasks;
import java.util.List;
import java.util.concurrent.ExecutorService;
import org.jetbrains.annotations.Nullable;
/**
* Schedule and execute tasks combined into stages
* with parallel or sequential execution (similar to the fork-join pattern).
*/
public interface ITaskExecutor {
/**
* Add parallel stage with provided tasks
*/
void addParallelTasks(List<? extends Runnable> parallelTasks);
/**
* Add sequential stage with provided tasks
*/
void addSequentialTasks(List<? extends Runnable> seqTasks);
/**
* Add sequential stage with a single task
*/
void addSequentialTask(Runnable task);
/**
* Scheduled tasks count
*/
int getTasksCount();
/**
* Set threads count for parallel stage.
* Can be changed during execution.
* Defaults to half of processors count.
*/
void setThreadsCount(int threadsCount);
int getThreadsCount();
/**
* Start tasks execution.
*/
void execute();
int getProgress();
/**
* Not started tasks will be not executed after this method invocation.
*/
void terminate();
boolean isTerminating();
boolean isRunning();
/**
* Block until execution is finished
*/
void awaitTermination();
/**
* Return internal executor service.
*/
@Nullable
ExecutorService getInternalExecutor();
}
@@ -5,10 +5,8 @@ import java.util.List;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.TaskBarrier;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.android.AndroidManifestParser;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResContainer;
@@ -18,20 +16,21 @@ public class ExportGradleTask implements Runnable {
private final RootNode root;
private final File projectDir;
private final File appDir;
private final File srcOutDir;
private final File resOutDir;
private final TaskBarrier barrier;
public ExportGradleTask(List<ResourceFile> resources, RootNode root, File projectDir, TaskBarrier barrier) {
public ExportGradleTask(List<ResourceFile> resources, RootNode root, File projectDir) {
this.resources = resources;
this.projectDir = projectDir;
this.root = root;
this.appDir = new File(projectDir, "app");
File appDir = new File(projectDir, "app");
this.srcOutDir = new File(appDir, "src/main/java");
this.resOutDir = new File(appDir, "src/main");
this.barrier = barrier;
}
public void init() {
FileUtils.makeDirs(srcOutDir);
FileUtils.makeDirs(resOutDir);
}
@Override
@@ -58,23 +57,9 @@ public class ExportGradleTask implements Runnable {
.orElse(null));
ExportGradleProject export = new ExportGradleProject(root, projectDir, androidManifest, strings);
// wait until all sources and resources are exported and all necessary info for gradle export are
// collected
try {
barrier.getTaskCountDown().await();
} catch (InterruptedException e) {
throw new JadxRuntimeException("Gradle export failed", e);
}
export.generateGradleFiles();
}
public void init() {
FileUtils.makeDirs(srcOutDir);
FileUtils.makeDirs(resOutDir);
}
public File getSrcOutDir() {
return srcOutDir;
}
@@ -0,0 +1,180 @@
package jadx.core.utils.tasks;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class TaskExecutor implements ITaskExecutor {
private enum ExecType {
PARALLEL,
SEQUENTIAL,
}
private static final class ExecStage {
private final ExecType type;
private final List<? extends Runnable> tasks;
private ExecStage(ExecType type, List<? extends Runnable> tasks) {
this.type = type;
this.tasks = tasks;
}
public ExecType getType() {
return type;
}
public List<? extends Runnable> getTasks() {
return tasks;
}
}
private final List<ExecStage> stages = new ArrayList<>();
private final AtomicInteger threadsCount = new AtomicInteger(JadxArgs.DEFAULT_THREADS_COUNT);
private final AtomicInteger progress = new AtomicInteger(0);
private final AtomicBoolean running = new AtomicBoolean(false);
private final AtomicBoolean terminating = new AtomicBoolean(false);
private int tasksCount = 0;
private @Nullable ExecutorService executor;
@Override
public void addParallelTasks(List<? extends Runnable> parallelTasks) {
if (parallelTasks.isEmpty()) {
return;
}
tasksCount += parallelTasks.size();
stages.add(new ExecStage(ExecType.PARALLEL, parallelTasks));
}
@Override
public void addSequentialTasks(List<? extends Runnable> seqTasks) {
if (seqTasks.isEmpty()) {
return;
}
tasksCount += seqTasks.size();
stages.add(new ExecStage(ExecType.SEQUENTIAL, seqTasks));
}
@Override
public void addSequentialTask(Runnable seqTask) {
addSequentialTasks(Collections.singletonList(seqTask));
}
@Override
public int getThreadsCount() {
return threadsCount.get();
}
@Override
public void setThreadsCount(int count) {
threadsCount.set(count);
}
@Override
public int getTasksCount() {
return tasksCount;
}
@Override
public int getProgress() {
return progress.get();
}
@Override
public void execute() {
if (running.get() || executor != null) {
throw new IllegalStateException("Already executing");
}
running.set(true);
progress.set(0);
terminating.set(false);
executor = Executors.newFixedThreadPool(1);
executor.execute(this::runStages);
executor.shutdown();
}
@Override
public void terminate() {
terminating.set(true);
}
@Override
public boolean isTerminating() {
return terminating.get();
}
@Override
public boolean isRunning() {
return running.get();
}
@Override
public @Nullable ExecutorService getInternalExecutor() {
return executor;
}
@Override
public void awaitTermination() {
if (executor == null || !running.get()) {
// already terminated
return;
}
awaitExecutorTermination(executor);
}
private void runStages() {
try {
for (ExecStage stage : stages) {
int threads = Math.min(stage.getTasks().size(), threadsCount.get());
if (stage.getType() == ExecType.SEQUENTIAL || threads == 1) {
for (Runnable task : stage.getTasks()) {
wrapTask(task);
}
} else {
ExecutorService parallelExecutor = Executors.newFixedThreadPool(threads);
for (Runnable task : stage.getTasks()) {
parallelExecutor.execute(() -> wrapTask(task));
}
parallelExecutor.shutdown();
awaitExecutorTermination(parallelExecutor);
}
if (terminating.get()) {
break;
}
}
} finally {
running.set(false);
executor = null;
}
}
private void wrapTask(Runnable task) {
if (terminating.get()) {
return;
}
task.run();
progress.incrementAndGet();
}
public static void awaitExecutorTermination(ExecutorService executor) {
try {
boolean complete = executor.awaitTermination(10, TimeUnit.DAYS);
if (!complete) {
throw new JadxRuntimeException("Executor timeout");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@@ -10,7 +10,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile;
import jadx.api.ResourcesLoader;
import jadx.api.TaskBarrier;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.exceptions.JadxException;
@@ -23,29 +22,17 @@ public class ResourcesSaver implements Runnable {
private final ResourceFile resourceFile;
private final File outDir;
private TaskBarrier barrier = null;
public ResourcesSaver(File outDir, ResourceFile resourceFile) {
this.resourceFile = resourceFile;
this.outDir = outDir;
}
public ResourcesSaver(File outDir, ResourceFile resourceFile, TaskBarrier barrier) {
this.resourceFile = resourceFile;
this.outDir = outDir;
this.barrier = barrier;
}
@Override
public void run() {
try {
saveResources(resourceFile.loadContent());
} catch (Throwable e) {
LOG.warn("Failed to save resource: {}", resourceFile.getOriginalName(), e);
} finally {
if (barrier != null) {
barrier.finishTask();
}
}
}
@@ -13,7 +13,6 @@ import jadx.api.ICodeInfo;
import jadx.api.JadxDecompiler;
import jadx.api.JadxDecompilerTestUtils;
import jadx.api.ResourceFile;
import jadx.api.TaskBarrier;
import jadx.core.dex.nodes.RootNode;
import jadx.core.export.ExportGradleProject;
import jadx.core.export.ExportGradleTask;
@@ -66,12 +65,11 @@ public abstract class ExportGradleTest {
final ResContainer androidManifestContainer = createResourceContainer(manifestFilename);
when(androidManifest.loadContent()).thenReturn(androidManifestContainer);
final ResContainer strings = createResourceContainer(stringsFileName);
TaskBarrier taskBarrier = new TaskBarrier();
final ExportGradleTask exportGradleTask = new ExportGradleTask(List.of(androidManifest), root, exportDir, taskBarrier);
final ExportGradleTask exportGradleTask = new ExportGradleTask(List.of(androidManifest), root, exportDir);
exportGradleTask.init();
assertThat(exportGradleTask.getSrcOutDir().exists());
assertThat(exportGradleTask.getResOutDir().exists());
assertThat(exportGradleTask.getSrcOutDir()).exists();
assertThat(exportGradleTask.getResOutDir()).exists();
final ExportGradleProject export =
new ExportGradleProject(root, exportDir, androidManifest, strings);
@@ -80,13 +78,13 @@ public abstract class ExportGradleTest {
protected String getAppGradleBuild() {
File appBuildGradle = new File(exportDir, "app/build.gradle");
assertThat(appBuildGradle.exists());
assertThat(appBuildGradle).exists();
return loadFileContent(appBuildGradle);
}
protected String getSettingsGradle() {
File settingsGradle = new File(exportDir, "settings.gradle");
assertThat(settingsGradle.exists());
assertThat(settingsGradle).exists();
return loadFileContent(settingsGradle);
}
}
@@ -266,10 +266,6 @@ public class JadxWrapper {
return getDecompiler().getEnclosingNode(codeInfo, pos);
}
public List<Runnable> getSaveTasks() {
return getDecompiler().getSaveTasks();
}
public List<JavaPackage> getPackages() {
return getDecompiler().getPackages();
}
@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
@@ -20,6 +21,7 @@ import javax.swing.SwingWorker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.panel.ProgressPanel;
@@ -104,7 +106,7 @@ public class BackgroundExecutor {
private final class TaskWorker extends SwingWorker<TaskStatus, Void> implements ITaskInfo {
private final long id;
private final IBackgroundTask task;
private ThreadPoolExecutor executor;
private ITaskExecutor taskExecutor;
private TaskStatus status = TaskStatus.WAIT;
private long jobsCount;
private long jobsComplete;
@@ -148,47 +150,43 @@ public class BackgroundExecutor {
}
private void runJobs() throws InterruptedException {
List<? extends Runnable> jobs = task.scheduleJobs();
jobsCount = jobs.size();
taskExecutor = task.scheduleTasks();
jobsCount = taskExecutor.getTasksCount();
LOG.debug("Starting background task '{}', jobs count: {}, time limit: {} ms, memory check: {}",
task.getTitle(), jobsCount, task.timeLimit(), task.checkMemoryUsage());
if (jobsCount != 1) {
progressPane.changeVisibility(this, true);
}
status = TaskStatus.STARTED;
int threadsCount = settings.getThreadsCount();
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (Runnable job : jobs) {
executor.execute(job);
}
executor.shutdown();
taskExecutor.setThreadsCount(settings.getThreadsCount());
taskExecutor.execute();
long startTime = System.currentTimeMillis();
status = waitTermination(executor, buildCancelCheck(startTime));
status = waitTermination(buildCancelCheck(startTime));
time = System.currentTimeMillis() - startTime;
jobsComplete = executor.getCompletedTaskCount();
jobsComplete = taskExecutor.getProgress();
}
@SuppressWarnings("BusyWait")
private TaskStatus waitTermination(ThreadPoolExecutor executor, Supplier<TaskStatus> cancelCheck) throws InterruptedException {
private TaskStatus waitTermination(Supplier<TaskStatus> cancelCheck) throws InterruptedException {
try {
int k = 0;
while (true) {
if (executor.isTerminated()) {
if (!taskExecutor.isRunning()) {
return TaskStatus.COMPLETE;
}
TaskStatus cancelStatus = cancelCheck.get();
if (cancelStatus != null) {
performCancel(executor);
performCancel();
return cancelStatus;
}
if (k < 10) {
// faster update for short tasks
Thread.sleep(200);
if (k == 5) {
updateProgress(executor);
updateProgress();
}
} else {
updateProgress(executor);
updateProgress();
Thread.sleep(1000);
}
if (jobsCount == 1 && k == 5) {
@@ -199,22 +197,22 @@ public class BackgroundExecutor {
}
} catch (InterruptedException e) {
LOG.debug("Task wait interrupted");
performCancel(executor);
performCancel();
return TaskStatus.CANCEL_BY_USER;
} catch (Exception e) {
LOG.error("Task wait aborted by exception", e);
performCancel(executor);
performCancel();
return TaskStatus.ERROR;
}
}
private void updateProgress(ThreadPoolExecutor executor) {
private void updateProgress() {
Consumer<ITaskProgress> onProgressListener = task.getProgressListener();
ITaskProgress taskProgress = task.getTaskProgress();
if (taskProgress == null) {
setProgress(calcProgress(executor.getCompletedTaskCount(), jobsCount));
setProgress(calcProgress(taskExecutor.getProgress(), jobsCount));
if (onProgressListener != null) {
onProgressListener.accept(new TaskProgress(executor.getCompletedTaskCount(), jobsCount));
onProgressListener.accept(new TaskProgress(taskExecutor.getProgress(), jobsCount));
}
} else {
setProgress(calcProgress(taskProgress));
@@ -224,12 +222,16 @@ public class BackgroundExecutor {
}
}
private void performCancel(ThreadPoolExecutor executor) throws InterruptedException {
private void performCancel() throws InterruptedException {
progressPane.changeLabel(this, task.getTitle() + " (" + NLS.str("progress.canceling") + ")… ");
progressPane.changeIndeterminate(this, true);
// force termination
ExecutorService executor = taskExecutor.getInternalExecutor();
if (executor == null) {
return;
}
taskExecutor.terminate();
task.cancel();
executor.shutdown();
int cancelTimeout = task.getCancelTimeoutMS();
if (cancelTimeout != 0) {
if (executor.awaitTermination(cancelTimeout, TimeUnit.MILLISECONDS)) {
@@ -240,8 +242,9 @@ public class BackgroundExecutor {
LOG.debug("Forcing tasks cancel");
executor.shutdownNow();
boolean complete = executor.awaitTermination(task.getShutdownTimeoutMS(), TimeUnit.MILLISECONDS);
LOG.debug("Forced task cancel status: {}",
complete ? "success" : "fail, still active: " + executor.getActiveCount());
LOG.debug("Forced task cancel status: {}", complete
? "success"
: "fail, still active: " + (taskExecutor.getTasksCount() - taskExecutor.getProgress()));
}
private Supplier<TaskStatus> buildCancelCheck(long startTime) {
@@ -261,13 +264,13 @@ public class BackgroundExecutor {
}
if (checkMemoryUsage && !UiUtils.isFreeMemoryAvailable()) {
LOG.info("Memory usage: {}", UiUtils.memoryInfo());
if (executor.getCorePoolSize() == 1) {
if (taskExecutor.getThreadsCount() == 1) {
LOG.error("Task '{}' memory limit reached, force cancel", task.getTitle());
return TaskStatus.CANCEL_BY_MEMORY;
}
LOG.warn("Low memory, reduce processing threads count to 1");
// reduce thread count and continue
executor.setCorePoolSize(1);
// reduce threads count and continue
taskExecutor.setThreadsCount(1);
System.gc();
UiUtils.sleep(1000); // wait GC
if (!UiUtils.isFreeMemoryAvailable()) {
@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeCache;
import jadx.api.JavaClass;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.core.utils.tasks.TaskExecutor;
import jadx.gui.JadxWrapper;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.NLS;
@@ -44,6 +46,12 @@ public class DecompileTask extends CancelableBackgroundTask {
}
@Override
public ITaskExecutor scheduleTasks() {
TaskExecutor executor = new TaskExecutor();
executor.addParallelTasks(scheduleJobs());
return executor;
}
public List<Runnable> scheduleJobs() {
if (mainWindow.getCacheObject().isFullDecompilationFinished()) {
return Collections.emptyList();
@@ -60,6 +68,10 @@ public class DecompileTask extends CancelableBackgroundTask {
LOG.error("Decompile batches build error", e);
return Collections.emptyList();
}
return getJobs(batches);
}
private List<Runnable> getJobs(List<List<JavaClass>> batches) {
ICodeCache codeCache = wrapper.getArgs().getCodeCache();
List<Runnable> jobs = new ArrayList<>(batches.size());
for (List<JavaClass> batch : batches) {
@@ -1,11 +1,11 @@
package jadx.gui.jobs;
import java.io.File;
import java.util.List;
import javax.swing.JOptionPane;
import jadx.api.ICodeCache;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.gui.JadxWrapper;
import jadx.gui.cache.code.FixedCodeCache;
import jadx.gui.ui.MainWindow;
@@ -32,11 +32,11 @@ public class ExportTask extends CancelableBackgroundTask {
}
@Override
public List<Runnable> scheduleJobs() {
public ITaskExecutor scheduleTasks() {
wrapCodeCache();
wrapper.getArgs().setRootDir(saveDir);
List<Runnable> saveTasks = wrapper.getSaveTasks();
this.timeLimit = DecompileTask.calcDecompileTimeLimit(saveTasks.size());
ITaskExecutor saveTasks = wrapper.getDecompiler().getSaveTaskExecutor();
this.timeLimit = DecompileTask.calcDecompileTimeLimit(saveTasks.getTasksCount());
return saveTasks;
}
@@ -1,18 +1,16 @@
package jadx.gui.jobs;
import java.util.List;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import jadx.api.utils.tasks.ITaskExecutor;
public interface IBackgroundTask extends Cancelable {
String getTitle();
/**
* Jobs to run in parallel
*/
List<? extends Runnable> scheduleJobs();
ITaskExecutor scheduleTasks();
/**
* Called on executor thread after the all jobs finished.
@@ -5,6 +5,9 @@ import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.core.utils.tasks.TaskExecutor;
/**
* Simple not cancelable task with memory check
*/
@@ -29,8 +32,10 @@ public class SimpleTask implements IBackgroundTask {
}
@Override
public List<Runnable> scheduleJobs() {
return jobs;
public ITaskExecutor scheduleTasks() {
TaskExecutor executor = new TaskExecutor();
executor.addParallelTasks(jobs);
return executor;
}
@Override
@@ -14,6 +14,8 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.utils.tasks.ITaskExecutor;
import jadx.core.utils.tasks.TaskExecutor;
import jadx.gui.jobs.BackgroundExecutor;
import jadx.gui.jobs.CancelableBackgroundTask;
import jadx.gui.jobs.ITaskInfo;
@@ -98,8 +100,10 @@ public class SearchTask extends CancelableBackgroundTask {
}
@Override
public List<? extends Runnable> scheduleJobs() {
return jobs;
public ITaskExecutor scheduleTasks() {
TaskExecutor executor = new TaskExecutor();
executor.addParallelTasks(jobs);
return executor;
}
@Override