Skip to content

Commit

Permalink
Add cancellation support for compilation tasks.
Browse files Browse the repository at this point in the history
This doesn't wire it in with Bazel workers yet but builds the general cancellation logic and add tests for it.

I will follow up with worker wiring later on.

PiperOrigin-RevId: 732019942
  • Loading branch information
gkdn authored and copybara-github committed Feb 28, 2025
1 parent c08569a commit 5a46d72
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 20 deletions.
8 changes: 7 additions & 1 deletion transpiler/java/com/google/j2cl/common/Problems.java
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,14 @@ public void abortIfCancelled() {
}
}

private volatile boolean cancelled = false;

public boolean isCancelled() {
return false;
return cancelled;
}

public void requestCancellation() {
cancelled = true;
}

private void abort() {
Expand Down
2 changes: 1 addition & 1 deletion transpiler/javatests/com/google/j2cl/transpiler/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ java_test(
"-XX:TieredStopAtLevel=2",
"-XX:MaxGCPauseMillis=100",
],
shard_count = 2,
shard_count = 4,
deps = [
":tester_lib",
"//third_party:junit",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import static com.google.j2cl.transpiler.TranspilerTester.newTesterWithKotlinDefaults;

import com.google.j2cl.transpiler.TranspilerTester.TranspileResult;
import java.util.Random;
import junit.framework.TestCase;

/** Test to run the transpiler twice and possibly uncover static state within the transpiler. */
Expand All @@ -26,28 +27,62 @@ public void testCompileJreTwice() throws Exception {
compileJre().assertOutputFilesAreSame(compileJre());
}

private static final int JAVA_CONSERVATIVE_COMPILATION_TIME_MS = 1000;

private static int getJavaCancelDelay() {
// Random to introduce more variance in the timing instead of always targeting beginning.
return new Random().nextInt(JAVA_CONSERVATIVE_COMPILATION_TIME_MS);
}

public void testCompileJreWithCancelation() throws Exception {
createJreCompile().assertTranspileWithCancellation(getJavaCancelDelay());
// Ensure compilation succeed after cancellation.
compileJre().assertOutputFilesAreSame(compileJre());
// Ensure compilation can be cancelled again.
createJreCompile().assertTranspileWithCancellation(getJavaCancelDelay());
}

private static TranspileResult compileJre() throws Exception {
return createJreCompile().assertTranspileSucceeds().assertNoWarnings();
}

private static TranspilerTester createJreCompile() {
return newTesterWithDefaults()
.setNativeSourcePathArg("transpiler/javatests/com/google/j2cl/transpiler/libjre_native.jar")
.addSourcePathArg(
"transpiler/javatests/com/google/j2cl/transpiler/jre_bundle_deploy-src.jar")
.assertTranspileSucceeds()
.assertNoWarnings();
"transpiler/javatests/com/google/j2cl/transpiler/jre_bundle_deploy-src.jar");
}

public void testCompileKotlinBox2DTwice() throws Exception {
compileKotlinBox2D().assertOutputFilesAreSame(compileKotlinBox2D());
}

private static final int KOTLIN_CONSERVATIVE_COMPILATION_TIME_MS = 1000;

private static int getKotlinCancelDelay() {
// Random to introduce more variance in the timing instead of always targeting beginning.
return new Random().nextInt(KOTLIN_CONSERVATIVE_COMPILATION_TIME_MS);
}

public void testCompileKotlinBox2DWithCancelation() throws Exception {
createKotlinBox2DCompile().assertTranspileWithCancellation(getKotlinCancelDelay());
// Ensure compilation succeed after cancellation.
compileKotlinBox2D().assertOutputFilesAreSame(compileKotlinBox2D());
// Ensure compilation can be cancelled again.
createKotlinBox2DCompile().assertTranspileWithCancellation(getKotlinCancelDelay());
}

private static TranspileResult compileKotlinBox2D() throws Exception {
return createKotlinBox2DCompile().assertTranspileSucceeds().assertNoWarnings();
}

private static TranspilerTester createKotlinBox2DCompile() {
// Unlike for the java frontend test that use the JRE, we are not using Kotlin stdlib for
// replicating the test for the Koltin frontend. Compiling stdlib is challenging due to the
// complexity of stdlib compilation(special flags, builtins, etc.) and could require temporary
// additional flag when we migrate to new Kotlinc version.
// We decided to use box2d instead that is complex enough for the purpose of this test.
compileKotlinBox2D().assertOutputFilesAreSame(compileKotlinBox2D());
}

private static TranspileResult compileKotlinBox2D() throws Exception {
return newTesterWithKotlinDefaults()
.addSourcePathArg("samples/box2d/src/main/kotlin/idiomatic/libbox2d_library-j2cl-src.jar")
.assertTranspileSucceeds()
.assertNoWarnings();
.addSourcePathArg("samples/box2d/src/main/kotlin/idiomatic/libbox2d_library-j2cl-src.jar");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
Expand All @@ -24,6 +26,7 @@
import com.google.common.collect.Lists;
import com.google.common.io.MoreFiles;
import com.google.common.truth.Correspondence;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.j2cl.common.Problems;
import java.io.FileOutputStream;
Expand All @@ -39,6 +42,8 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
Expand Down Expand Up @@ -88,7 +93,7 @@ public static TranspilerTester newTesterWithJ2ktDefaults() {
public static TranspilerTester newTesterWithWasmDefaults() {
return newTester()
// TODO(b/395921769): Remove this after the test are ported to modular WASM.
.disableCheckCancelationDelay()
.noAssertDelayedCancelChecks()
.addArgs("-backend", "WASM")
.setClassPathArg(
"transpiler/javatests/com/google/j2cl/transpiler/jre_bundle-j2wasm_deploy.jar")
Expand Down Expand Up @@ -201,7 +206,7 @@ public void createFileIn(Path basePath) {
private List<String> args = new ArrayList<>();
private String temporaryDirectoryPrefix = "transpile_tester";
private Path outputPath;
private boolean checkCancelationDelay = true;
private boolean assertDelayedCancelChecks = true;

public TranspilerTester addCompilationUnit(String qualifiedCompilationUnitName, String... code) {
List<String> content = Lists.newArrayList(code);
Expand Down Expand Up @@ -332,8 +337,8 @@ public TranspilerTester setOutputPath(Path outputPath) {
}

@CanIgnoreReturnValue
public TranspilerTester disableCheckCancelationDelay() {
this.checkCancelationDelay = false;
public TranspilerTester noAssertDelayedCancelChecks() {
this.assertDelayedCancelChecks = false;
return this;
}

Expand All @@ -355,6 +360,13 @@ public TranspileResult assertTranspileFails() {
return transpile().assertHasErrors();
}

public void assertTranspileWithCancellation(int cancelDelayMs) throws IOException {
noAssertDelayedCancelChecks(); // Not compatible.
var result = transpile(cancelDelayMs);
result.assertNoErrors();
assertThat(MoreFiles.listFiles(result.getOutputPath())).isEmpty();
}

/** A bundle of data recording the results of a transpile operation. */
public static class TranspileResult {
private final Problems problems;
Expand Down Expand Up @@ -513,17 +525,22 @@ private static boolean compare(String actual, String expected) {
}
}

/** Do not automatically cancel the transpiler. */
private static final int NO_CANCEL = -1;

/**
* The maximum delay (in ms) that we allow for calls to #isCancelled(). Note that we have a much
* lower delay but this number should be high enough to cover random GC events.
*/
private static final int MAX_DELAY = 250;

private static Problems transpile(ImmutableList<String> args, boolean checkDelayedCalls) {
@SuppressWarnings("FutureReturnValueIgnored")
private static Problems transpile(
ImmutableList<String> args, boolean assertDelayedCancelChecks, int cancelDelayMs) {
List<String> delayedCalls = new ArrayList<>();
List<String> slightlyDelayedCalls = new ArrayList<>();
Problems problems =
checkDelayedCalls
assertDelayedCancelChecks
? new Problems() {
long lastCall = System.nanoTime();

Expand All @@ -546,7 +563,15 @@ public boolean isCancelled() {
: new Problems();

J2clCommandLineRunner runner = new J2clCommandLineRunner(problems);
runner.executeForTesting(args);
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
try {
executorService.execute(() -> runner.executeForTesting(args));
if (cancelDelayMs != NO_CANCEL) {
executorService.schedule(problems::requestCancellation, cancelDelayMs, MILLISECONDS);
}
} finally {
MoreExecutors.shutdownAndAwaitTermination(executorService, 30, SECONDS);
}

final String[] knownDelayedCalls = {
"com.google.j2cl.transpiler.frontend.javac.JavacParser.parseFiles",
Expand All @@ -573,6 +598,10 @@ public boolean isCancelled() {
}

private TranspileResult transpile() {
return transpile(NO_CANCEL);
}

private TranspileResult transpile(int cancelDelayMs) {
try {
Path tempDir = Files.createTempDirectory(temporaryDirectoryPrefix);

Expand Down Expand Up @@ -619,7 +648,8 @@ private TranspileResult transpile() {
commandLineArgsBuilder.addAll(args);

return new TranspileResult(
transpile(commandLineArgsBuilder.build(), checkCancelationDelay), outputPath);
transpile(commandLineArgsBuilder.build(), assertDelayedCancelChecks, cancelDelayMs),
outputPath);
} catch (IOException e) {
throw new AssertionError(e);
}
Expand Down

0 comments on commit 5a46d72

Please sign in to comment.