IDEA-321510 [Gradle|Sync] fix: convert and transfer the Gradle build environment once per model fetch action

Extracted GradleExecutorServiceUtil

GitOrigin-RevId: 1654459bbba7d4edcb4401c6e572f3a35ede2f39
This commit is contained in:
Sergei Vorobyov
2024-03-20 23:27:38 +00:00
committed by intellij-monorepo-bot
parent c8af8fea88
commit 97b95edbec
3 changed files with 106 additions and 64 deletions
@@ -2,7 +2,7 @@
package com.intellij.gradle.toolingExtension.impl.modelAction;
import com.intellij.gradle.toolingExtension.impl.modelSerialization.ModelConverter;
import io.opentelemetry.context.Context;
import com.intellij.gradle.toolingExtension.impl.util.GradleExecutorServiceUtil;
import org.gradle.tooling.BuildController;
import org.gradle.tooling.internal.gradle.DefaultBuildIdentifier;
import org.gradle.tooling.model.BuildIdentifier;
@@ -16,6 +16,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.model.DefaultBuild;
import org.jetbrains.plugins.gradle.model.DefaultBuildController;
import org.jetbrains.plugins.gradle.model.ProjectImportModelProvider.GradleModelConsumer;
import org.jetbrains.plugins.gradle.tooling.serialization.internal.adapter.InternalBuildEnvironment;
import java.io.File;
import java.util.*;
@@ -56,6 +57,7 @@ public class GradleDaemonModelHolder {
private final @NotNull BlockingQueue<Future<DefaultBuild>> myConvertedRootBuild = new LinkedBlockingQueue<>();
private final @NotNull BlockingQueue<Future<Collection<DefaultBuild>>> myConvertedNestedBuilds = new LinkedBlockingQueue<>();
private final @NotNull BlockingQueue<Future<ConvertedModel>> myConvertedModelQueue = new LinkedBlockingQueue<>();
private final @NotNull BlockingQueue<Future<BuildEnvironment>> myConvertedBuildEnvironment = new LinkedBlockingQueue<>();
public GradleDaemonModelHolder(
@NotNull ExecutorService converterExecutor,
@@ -68,8 +70,15 @@ public class GradleDaemonModelHolder {
myRootGradleBuild = rootGradleBuild;
myNestedGradleBuilds = nestedGradleBuilds;
myBuildEnvironment = buildEnvironment;
submitTask(converterExecutor, myConvertedRootBuild, () -> DefaultBuild.convertGradleBuild(rootGradleBuild));
submitTask(converterExecutor, myConvertedNestedBuilds, () -> convertNestedGradleBuilds(nestedGradleBuilds));
GradleExecutorServiceUtil.submitTask(converterExecutor, myConvertedRootBuild, () -> {
return DefaultBuild.convertGradleBuild(rootGradleBuild);
});
GradleExecutorServiceUtil.submitTask(converterExecutor, myConvertedNestedBuilds, () -> {
return convertNestedGradleBuilds(nestedGradleBuilds);
});
GradleExecutorServiceUtil.submitTask(converterExecutor, myConvertedBuildEnvironment, () -> {
return InternalBuildEnvironment.convertBuildEnvironment(myBuildEnvironment);
});
}
public @NotNull Collection<? extends GradleBuild> getGradleBuilds() {
@@ -96,7 +105,7 @@ public class GradleDaemonModelHolder {
}
private void consumeModel(@NotNull Object model, @NotNull GradleModelId modelId) {
submitTask(converterExecutor, myConvertedModelQueue, () -> {
GradleExecutorServiceUtil.submitTask(converterExecutor, myConvertedModelQueue, () -> {
Object convertedModel = myModelConverter.convert(model);
return new ConvertedModel(modelId, convertedModel);
});
@@ -104,54 +113,34 @@ public class GradleDaemonModelHolder {
};
}
private static <T> void submitTask(
@NotNull ExecutorService executor,
@NotNull BlockingQueue<Future<T>> queue,
@NotNull Callable<T> task
) {
Future<T> convertedModelFuture = Context.current()
.wrap(executor)
.submit(task);
queue.add(convertedModelFuture);
}
public @NotNull GradleModelHolderState pollPendingState() {
DefaultBuild rootBuild = pollPendingConvertedRootBuild();
Collection<DefaultBuild> nestedBuilds = pollPendingConvertedNestedBuilds();
Map<GradleModelId, Object> models = pollAllPendingConvertedModels();
return new GradleModelHolderState(rootBuild, nestedBuilds, myBuildEnvironment, models);
BuildEnvironment buildEnvironment = pollPendingBuildEnvironment();
return new GradleModelHolderState(rootBuild, nestedBuilds, buildEnvironment, models);
}
private @Nullable DefaultBuild pollPendingConvertedRootBuild() {
return poolPendingModel(myConvertedRootBuild);
return GradleExecutorServiceUtil.poolPendingResult(myConvertedRootBuild);
}
private @NotNull Collection<DefaultBuild> pollPendingConvertedNestedBuilds() {
Collection<DefaultBuild> builds = poolPendingModel(myConvertedNestedBuilds);
Collection<DefaultBuild> builds = GradleExecutorServiceUtil.poolPendingResult(myConvertedNestedBuilds);
return builds == null ? Collections.emptyList() : builds;
}
private @NotNull Map<GradleModelId, Object> pollAllPendingConvertedModels() {
Map<GradleModelId, Object> models = new LinkedHashMap<>();
ConvertedModel model = poolPendingModel(myConvertedModelQueue);
while (model != null) {
models.put(model.myId, model.myModel);
model = poolPendingModel(myConvertedModelQueue);
List<ConvertedModel> models = GradleExecutorServiceUtil.pollAllPendingResults(myConvertedModelQueue);
Map<GradleModelId, Object> modelMap = new LinkedHashMap<>();
for (ConvertedModel convertedModel : models) {
modelMap.put(convertedModel.myId, convertedModel.myModel);
}
return models;
return modelMap;
}
private static <T> @Nullable T poolPendingModel(@NotNull BlockingQueue<Future<T>> modelQueue) {
try {
Future<T> future = modelQueue.poll();
if (future == null) {
return null;
}
return future.get();
}
catch (InterruptedException | ExecutionException ignored) {
return null;
}
private @Nullable BuildEnvironment pollPendingBuildEnvironment() {
return GradleExecutorServiceUtil.poolPendingResult(myConvertedBuildEnvironment);
}
private static @NotNull Collection<DefaultBuild> convertNestedGradleBuilds(
@@ -3,6 +3,7 @@ package com.intellij.gradle.toolingExtension.impl.modelAction;
import com.intellij.gradle.toolingExtension.impl.telemetry.GradleOpenTelemetry;
import com.intellij.gradle.toolingExtension.impl.telemetry.GradleTracingContext;
import com.intellij.gradle.toolingExtension.impl.util.GradleExecutorServiceUtil;
import com.intellij.gradle.toolingExtension.modelAction.GradleModelFetchPhase;
import com.intellij.gradle.toolingExtension.util.GradleVersionUtil;
import com.intellij.openapi.externalSystem.model.ExternalSystemException;
@@ -27,9 +28,6 @@ import org.jetbrains.plugins.gradle.model.ProjectImportModelProvider.GradleModel
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -83,7 +81,7 @@ public class GradleModelFetchAction implements BuildAction<GradleModelHolderStat
@Override
public @NotNull GradleModelHolderState execute(@NotNull BuildController controller) {
configureAdditionalTypes(controller);
return withConverterExecutor(converterExecutor -> {
return GradleExecutorServiceUtil.withSingleThreadExecutor("idea-tooling-model-converter", converterExecutor -> {
return withOpenTelemetry(telemetry -> {
return telemetry.callWithSpan("ProjectImportAction", __ -> {
return doExecute(controller, converterExecutor, telemetry);
@@ -92,22 +90,6 @@ public class GradleModelFetchAction implements BuildAction<GradleModelHolderStat
});
}
private static <T> T withConverterExecutor(@NotNull Function<ExecutorService, T> action) {
ExecutorService converterExecutor = Executors.newSingleThreadExecutor(new SimpleThreadFactory());
try {
return action.apply(converterExecutor);
}
finally {
converterExecutor.shutdown();
try {
converterExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private GradleModelHolderState withOpenTelemetry(@NotNull Function<GradleOpenTelemetry, GradleModelHolderState> action) {
GradleTracingContext tracingContext = myTracingContext;
if (tracingContext == null) {
@@ -303,13 +285,4 @@ public class GradleModelFetchAction implements BuildAction<GradleModelHolderStat
}
return modelProviders.tailMap(GradleModelFetchPhase.PROJECT_LOADED_PHASE, false);
}
// Use this static class as a simple ThreadFactory to prevent a memory leak when passing an anonymous ThreadFactory object to
// Executors.newSingleThreadExecutor. Memory leak will occur on the Gradle Daemon otherwise.
private static final class SimpleThreadFactory implements ThreadFactory {
@Override
public Thread newThread(@NotNull Runnable runnable) {
return new Thread(runnable, "idea-tooling-model-converter");
}
}
}
@@ -0,0 +1,80 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.gradle.toolingExtension.impl.util;
import io.opentelemetry.context.Context;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Function;
public final class GradleExecutorServiceUtil {
public static <T> T withSingleThreadExecutor(@NotNull String name, @NotNull Function<ExecutorService, T> action) {
ExecutorService executorService = Executors.newSingleThreadExecutor(new SimpleThreadFactory(name));
try {
return action.apply(executorService);
}
finally {
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static <T> void submitTask(
@NotNull ExecutorService executor,
@NotNull BlockingQueue<Future<T>> queue,
@NotNull Callable<T> task
) {
Future<T> taskResult = Context.current()
.wrap(executor)
.submit(task);
queue.add(taskResult);
}
public static <T> @NotNull List<T> pollAllPendingResults(@NotNull BlockingQueue<Future<T>> queue) {
List<T> results = new ArrayList<>();
T result = poolPendingResult(queue);
while (result != null) {
results.add(result);
result = poolPendingResult(queue);
}
return results;
}
public static <T> @Nullable T poolPendingResult(@NotNull BlockingQueue<Future<T>> queue) {
try {
Future<T> future = queue.poll();
if (future == null) {
return null;
}
return future.get();
}
catch (InterruptedException | ExecutionException ignored) {
return null;
}
}
// Use this static class as a simple ThreadFactory to prevent a memory leak when passing an anonymous ThreadFactory object to
// Executors.newSingleThreadExecutor. Memory leak will occur on the Gradle Daemon otherwise.
private static final class SimpleThreadFactory implements ThreadFactory {
private final String myName;
private SimpleThreadFactory(@NotNull String name) {
myName = name;
}
@Override
public Thread newThread(@NotNull Runnable runnable) {
return new Thread(runnable, myName);
}
}
}