| // 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.junit.Assume.assumeTrue; |
| |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.google.common.collect.ImmutableList; |
| import java.lang.Thread.State; |
| import java.util.List; |
| 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) |
| .addHorizontallyMergedClassesInspector( |
| inspector -> inspector.assertClassesNotMerged(B.class, C.class)) |
| .addOptionsModification( |
| options -> |
| options.horizontalClassMergerOptions().setEnableClassInitializerDeadlockDetection()) |
| .setMinApi(parameters.getApiLevel()) |
| .compile() |
| .run(parameters.getRuntime(), Main.class) |
| .assertSuccessWithOutputLines(getExpectedOutput()); |
| } |
| |
| @Test |
| public void testJvm() throws Exception { |
| assumeTrue(parameters.isCfRuntime()); |
| testForJvm() |
| .addTestClasspath() |
| .run(parameters.getRuntime(), Main.class) |
| .assertSuccessWithOutputLines(getExpectedOutput()); |
| } |
| |
| private static List<String> getExpectedOutput() { |
| return ImmutableList.of( |
| "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"; |
| } |
| } |
| } |