Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions se-commons-gradle/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@ dependencies {
implementation "com.google.guava:guava:33.1.0-jre"
}

publishing {
publications {
maven(MavenPublication) {
from(components.java)
}
}
}

// Test our plugin infrastructure with a test-plugin
sourceSets {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/**
* We use a build service to pass a ProgressLogger instance to the workers
* See https://github.com/gradle/gradle/issues/2678
* Note: Build services cannot be serialized when isolation a workqueue (when isolating the queue)
*/
public abstract class ProgressLoggerService implements BuildService<BuildServiceParameters.None> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,10 @@ protected synchronized void cleanupOld(long pCloseThreshold) {
// Close closeable classloaders
try {
((Closeable) data.getClassLoader()).close();
} catch (IOException ignored) { }
} catch (Throwable ignored) {
// In case java is eagerly unloading the classloader already,
// used classes from libraries might result in NoClassDefErrors
}
}
data.cleanUp();
isolated.remove();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ public void close() throws IOException {
}
}
// And finally, make *really* sure we unset their context classloader
for (Thread thread : Iterables.concat(Thread.getAllStackTraces().keySet(), shutdownHooks)) {
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread.getContextClassLoader() != IsolatedURLClassLoader.this)
continue; // but only threads within this context
thread.setContextClassLoader(null);
}
// duplicate code to avoid library usage here during cleanup
// (in case Iterables were to be used the first time)
for (Thread thread : shutdownHooks) {
if (thread.getContextClassLoader() != IsolatedURLClassLoader.this)
continue; // but only threads within this context
thread.setContextClassLoader(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@
import com.google.gson.Gson;

import javax.annotation.Nullable;
import java.io.File;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;

/**
* Statistics collector for the cached isolated worker.
*
* Can be exported via the reportCachedQueueService gradle task
* or -Dde.se_rwth.workqueue.report.continuous=true
*/
public class CachedIsolationStats {

// Unique ID for tracing reasons
final UUID instanceUUID = UUID.randomUUID();

protected List<Event> events = Collections.synchronizedList(new ArrayList<>());
protected boolean continuousTrack = shouldContinuousTrack();

protected boolean shouldContinuousTrack() {
return "true".equals(System.getProperty("de.se_rwth.workqueue.report.continuous", "false"));
}

void track(EventKind kind, @Nullable UUID uuid, int semaphoreMax, List<CachedQueueService.IIsolationData> runners) {
track(kind, uuid, semaphoreMax, runners, null);
Expand All @@ -29,6 +39,22 @@ void track(EventKind kind, @Nullable UUID uuid, int semaphoreMax, List<CachedQue
event.existingRunnerList = createRunnerList(runners);
event.reason = reason;
events.add(event);

if (continuousTrack) {
report();
}
}

/**
* Continuously report the current workqueue state
*/
protected synchronized void report() {
File f = new File("report-workqueue-" + instanceUUID + ".json");
try{
Files.writeString(f.getAbsoluteFile().toPath(), asJson(new Gson()));
}catch (Exception ignored) {

}
}

public String asJson(Gson gson) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.gradle.internal.isolation.IsolatableFactory;
import org.gradle.internal.operations.*;
import org.gradle.internal.reflect.Instantiator;
import org.gradle.internal.serialize.Decoder;
import org.gradle.internal.serialize.InputStreamBackedDecoder;
import org.gradle.internal.serialize.OutputStreamBackedEncoder;
import org.gradle.internal.serialize.Serializer;
Expand All @@ -34,7 +35,6 @@
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import org.gradle.workers.internal.ActionExecutionSpecFactory;
import org.gradle.workers.internal.IsolatableSerializerRegistry;

import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
Expand All @@ -55,7 +55,7 @@
* Service managing the cached isolators and their backing queue
*/
public abstract class CachedQueueService
implements BuildService<BuildServiceParameters.None>, AutoCloseable, BuildOperationListener {
implements ICachedQueueService, BuildService<BuildServiceParameters.None>, AutoCloseable, BuildOperationListener {

static CachedQueueService INSTANCE;

Expand Down Expand Up @@ -95,7 +95,7 @@ protected void init(ServiceRegistry serviceRegistry) {
* Time (in ms) after the last use of an isolated classloader before its
* allocated resources are freed
*/
protected long closeThreshold = 6 * 1000; // 6 seconds
protected long closeThreshold = 20 * 1000; // 20 seconds

/**
* We periodically clean up the open classloaders
Expand Down Expand Up @@ -267,6 +267,7 @@ protected Set<String> getPassThroughPackages() {
}

public void doExecuteWorkAction(UUID actionUUID) {
long timeWaited = System.currentTimeMillis();
try {
// In case we run into the limit of maximum concurrent MontiCore Generation actions,
// we wait until another generation has concluded
Expand All @@ -275,14 +276,15 @@ public void doExecuteWorkAction(UUID actionUUID) {
// Unable to acquire slot to run -> abort
throw new RuntimeException(e);
}
timeWaited = System.currentTimeMillis() - timeWaited;
try {
doExecuteWorkAction(taskInfoMap.get(actionUUID));
doExecuteWorkAction(taskInfoMap.get(actionUUID), timeWaited);
} finally {
semaphore.release();
}
}

void doExecuteWorkAction(ActualTaskInfo<?> info) {
void doExecuteWorkAction(ActualTaskInfo<?> info, long timeWaitedForSemaphore) {

Class<? extends WorkParameters> parameterTypeNotIsolated =
isolationScheme.parameterTypeFor(info.workActionClass);
Expand All @@ -299,21 +301,23 @@ void doExecuteWorkAction(ActualTaskInfo<?> info) {

Isolatable<WorkParameters> paramIsol = serviceRegistry.get(IsolatableFactory.class).isolate(parametersUnsafe);

IsolatableSerializerRegistry isolatableSerializerRegistry = this.serviceRegistry.get(IsolatableSerializerRegistry.class);
IsolatableSerializerRegistryWrapper isolatableSerializerRegistry = getIsolatableSerializerRegistryWrapper();

ByteArrayOutputStream bos = new ByteArrayOutputStream();
Serializer serializer = isolatableSerializerRegistry.build(paramIsol.getClass());
try {
serializer.write(new OutputStreamBackedEncoder(bos), paramIsol);
} catch (Exception e) {
passThrowableAlong(e);
throw new RuntimeException("Failed to serialize " + info.workActionClass.getName() + " " + parameterTypeNotIsolated.getName() + " with " + parametersUnsafe.getClass(), e);
}

String prefix = parametersUnsafe instanceof CachedIsolatedWorkQueue.WorkQueueParameters ? ((CachedIsolatedWorkQueue.WorkQueueParameters) parametersUnsafe)
.getPrefix().getOrElse("[WA]") : "[WA]";

String uniqueId = parametersUnsafe instanceof CachedIsolatedWorkQueue.WorkQueueParameters ? ((CachedIsolatedWorkQueue.WorkQueueParameters) parametersUnsafe).getStatsUniqueName().getOrElse("?") : "?";


uniqueId += "," + timeWaitedForSemaphore + "ms";

executeInClassloader(() -> {

try {
Expand Down Expand Up @@ -635,17 +639,71 @@ public void markForErasure() {


/**
* Construc
* Construct a new WorkQueue
*
* @param workerExecutor the worker executor to use
* @param extraClasspathElement the classpath elements to use
* @return a new {@link WorkQueue}
*/
public WorkQueue newWorkQueue(WorkerExecutor workerExecutor, FileCollection extraClasspathElement) {
Objects.requireNonNull(workerExecutor, "worker executor must not be null");
Objects.requireNonNull(serviceRegistry, "serviceRegistry must not be null");
return new CachedIsolatedWorkQueue(workerExecutor.noIsolation(),
serviceRegistry.get(InstantiatorFactory.class),
Objects.requireNonNull(serviceRegistry, "serviceRegistry"),
this.providerSelf,
extraClasspathElement);
}

// Gradle Version compat
protected IsolatableSerializerRegistryWrapper getIsolatableSerializerRegistryWrapper() {
return new IsolatableSerializerRegistryWrapper(this.serviceRegistry.get(getIsolatableSerializerRegistryClass()));
}

protected Class<?> getIsolatableSerializerRegistryClass() {
try {
return Class.forName("org.gradle.workers.internal.IsolatableSerializerRegistry");
}
catch (ClassNotFoundException e) {
try {
return Class.forName("org.gradle.internal.snapshot.impl.IsolatableSerializerRegistry");
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
}

/**
* Wrapper around gradle internals.
* Must not refer to gradle internal classes to avoid errors during plugin-ASM-phase
*/
protected static class IsolatableSerializerRegistryWrapper {

final Object instance;

IsolatableSerializerRegistryWrapper(Object instance) {
this.instance = instance;
}

<T> Serializer<T> build(Class<T> baseType) {
try {
return (Serializer<T>) instance.getClass().getMethod("build", Class.class)
.invoke(instance, baseType);
}
catch (ReflectiveOperationException e) {
throw new IllegalStateException("Failed to wrap IsolatableSerializerRegistry", e);
}
}

Isolatable<?> readIsolatable(Decoder decoder) {
try {
return (Isolatable<?>) instance.getClass().getMethod("readIsolatable", Decoder.class)
.invoke(instance, decoder);
}
catch (ReflectiveOperationException e) {
throw new IllegalStateException("Failed to wrap IsolatableSerializerRegistry", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.checkerframework.checker.nullness.qual.NonNull;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskProvider;

/**
Expand All @@ -13,29 +12,17 @@
public class CachedQueueServicePlugin implements Plugin<Project> {
@Override
public void apply(@NonNull Project project) {
// Register the service, if needed
Provider<CachedQueueService> sp = project.getGradle().getSharedServices().registerIfAbsent(CachedQueueService.NAME, CachedQueueService.class, spec -> {
});
// Register the service in the settings plugin
project.getGradle().getPluginManager().apply(InternalCachedQueueSetupSettingsPlugin.class);
if (project == project.getRootProject()) {
// initialize the service but only if it is the root project
sp.get().init(project.getGradle());

// Register an optional reporting task
TaskProvider<ReportCachedQueueServiceTask> reportTask = project.getTasks().register("reportCachedQueueService", ReportCachedQueueServiceTask.class, spec -> {
spec.mustRunAfter(project.getTasks().withType(ICachedQueueTask.class));
spec.getSharedQueueService().set(sp);
});
project.getTasks().withType(ICachedQueueTask.class).configureEach(task -> {
task.finalizedBy(reportTask);
});
}


// And configure all tasks with the ICachedQueueTask interface to access this service

project.getTasks().withType(ICachedQueueTask.class).configureEach(task -> {
task.getSharedQueueService().set(sp);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package de.monticore.gradle.queue;

import org.gradle.api.file.FileCollection;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;

import java.lang.reflect.Proxy;

/**
* @since 7.9.0
*/
public interface ICachedQueueService {

/**
* Construct a new WorkQueue
*
* @param workerExecutor the worker executor to use
* @param extraClasspathElement the classpath elements to use
* @return a new {@link WorkQueue}
*/
WorkQueue newWorkQueue(WorkerExecutor workerExecutor, FileCollection extraClasspathElement);

/**
* Returns the tracked stats as a serialized JSON string.
*
* @return a serialized JSON string of the stats
* @see CachedIsolationStats
*/
String getStats();

void setMaxConcurrentMC(int maxParallelMC);

void setCloseThreshold(long closeThreshold);

static ICachedQueueService asICachedQueueService(Object sharedQueueService) {
if (sharedQueueService instanceof ICachedQueueService) {
return (ICachedQueueService) sharedQueueService;
}
// Gradle might load the plugin into multiple classloaders (once per subproject where it is applied)
// If that is the case, ICachedQueueService (from CL 1) is not castable to ICachedQueueService (from CL 2)
// As we only expose the ICachedQueueService, we can create a proxy between both classloaders
return (ICachedQueueService) Proxy.newProxyInstance(CachedQueueService.class.getClassLoader(),
new Class[] { ICachedQueueService.class },
(proxy, method, args) -> sharedQueueService.getClass()
.getMethod(method.getName(), method.getParameterTypes())
.invoke(sharedQueueService, args));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,25 @@
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Internal;

import java.lang.reflect.Proxy;

/**
* A task with access to a cached queue service.
* Automatically managed via the {@link CachedQueueServicePlugin}
*/
public interface ICachedQueueTask extends Task {

// Type must be object due to https://github.com/gradle/gradle/issues/17559
@Internal
Property<Object> getSharedQueueServiceProperty();

@Internal
Property<CachedQueueService> getSharedQueueService();
@Deprecated(forRemoval = true)
default Property<ICachedQueueService> getSharedQueueService() {
return (Property<ICachedQueueService>) ((Property) getSharedQueueServiceProperty());
}

default ICachedQueueService doGetSharedQueueService() {
return ICachedQueueService.asICachedQueueService(getSharedQueueServiceProperty().get());
}
}
Loading
Loading