blob: aa0d663776f237f07908b1dff5f0ea00eaa0c8d5 [file] [log] [blame]
// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.classmerging.horizontal;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
import java.lang.Thread.State;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class ImplicitClassInitializationSynchronizationTest extends TestBase {
@Parameter(0)
public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection parameters() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
// TODO(b/205611444): Should not be merged.
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(B.class, C.class))
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
// TODO(b/205611444): Should succeed.
.assertFailure()
.assertStdoutMatches(
equalTo(
StringUtils.lines(
"Main: fork",
"Main: wait",
"Worker: notify",
"Worker: wait",
"Main: notified",
"Main: lock C",
"Worker: notified",
"Worker: lock B")));
}
@Test
public void testJvm() throws Exception {
assumeTrue(parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(
"Main: fork",
"Main: wait",
"Worker: notify",
"Worker: wait",
"Main: notified",
"Main: lock C",
"Worker: notified",
"Worker: lock B",
"B",
"Worker: unlock B",
"C",
"Main: unlock C");
}
static class Main {
static Object lock = new Object();
static Thread mainThread = Thread.currentThread();
public static void main(String[] args) throws Exception {
System.out.println("Main: fork");
Thread workerThread = new Thread(A::new);
workerThread.start();
// Wait for the worker thread to take the lock for A.
System.out.println("Main: wait");
synchronized (lock) {
lock.wait();
}
// Wait for the worker thread to be waiting on the main thread.
while (workerThread.getState() != State.WAITING) {
Thread.sleep(100);
}
System.out.println("Main: notified");
// In one second, let the worker thread continue.
doAfter(
1000,
() -> {
synchronized (lock) {
lock.notify();
}
});
// In five seconds, report a dead lock.
doAfter(5000, () -> System.exit(1));
System.out.println("Main: lock C");
System.out.println(new C());
System.out.println("Main: unlock C");
// No deadlock, success.
System.exit(0);
}
private static void doAfter(int ms, Runnable runnable) {
new Thread(
() -> {
try {
Thread.sleep(ms);
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
})
.start();
}
}
static class A {
static {
try {
// Wait for the main thread to be waiting on the worker thread.
while (Main.mainThread.getState() != State.WAITING) {
Thread.sleep(100);
}
System.out.println("Worker: notify");
synchronized (Main.lock) {
Main.lock.notify();
}
// Wait for the main thread to take the lock for B.
System.out.println("Worker: wait");
synchronized (Main.lock) {
Main.lock.wait();
}
System.out.println("Worker: notified");
// Try to take the lock for B.
System.out.println("Worker: lock B");
System.out.println(new B());
System.out.println("Worker: unlock B");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
static class B extends A {
@Override
public String toString() {
return "B";
}
}
static class C extends A {
@Override
public String toString() {
return "C";
}
}
}