/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package art; import java.util.concurrent.CountDownLatch; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Test923 { public static void run() throws Exception { doTest(); } private static void doTest() throws Exception { // Start a watchdog, to make sure on deadlocks etc the test dies. startWatchdog(); sharedId = createRawMonitor(); output = new ArrayList(100); simpleTests(sharedId); for (String s : output) { System.out.println(s); } output.clear(); threadTests(sharedId); destroyRawMonitor(sharedId); } private static void simpleTests(long id) { unlock(id); // Should fail. lock(id); unlock(id); unlock(id); // Should fail. lock(id); lock(id); unlock(id); unlock(id); unlock(id); // Should fail. rawWait(id, 0); // Should fail. rawWait(id, -1); // Should fail. rawWait(id, 1); // Should fail. lock(id); rawWait(id, 50); unlock(id); unlock(id); // Should fail. rawNotify(id); // Should fail. lock(id); rawNotify(id); unlock(id); unlock(id); // Should fail. rawNotifyAll(id); // Should fail. lock(id); rawNotifyAll(id); unlock(id); unlock(id); // Should fail. } private static void threadTests(final long id) throws Exception { final int N = 10; final CountDownLatch waitLatch = new CountDownLatch(N); final CountDownLatch wait2Latch = new CountDownLatch(1); Runnable r = new Runnable() { @Override public void run() { lock(id); waitLatch.countDown(); rawWait(id, 0); firstAwakened = Thread.currentThread(); appendToLog("Awakened"); unlock(id); wait2Latch.countDown(); } }; List threads = new ArrayList(); for (int i = 0; i < N; i++) { Thread t = new Thread(r); threads.add(t); t.start(); } // Wait till all threads have been started. waitLatch.await(); // Hopefully enough time for all the threads to progress into wait. Thread.yield(); Thread.sleep(500); // Wake up one. lock(id); rawNotify(id); unlock(id); wait2Latch.await(); // Wait a little bit more to see stragglers. This is flaky - spurious wakeups could // make the test fail. Thread.yield(); Thread.sleep(500); if (firstAwakened != null) { firstAwakened.join(); } // Wake up everyone else. lock(id); rawNotifyAll(id); unlock(id); // Wait for everyone to die. for (Thread t : threads) { t.join(); } // Check threaded output. Iterator it = output.iterator(); // 1) Start with N locks and Waits. { int locks = 0; int waits = 0; for (int i = 0; i < 2*N; i++) { String s = it.next(); if (s.equals("Lock")) { locks++; } else if (s.equals("Wait")) { if (locks <= waits) { System.out.println(output); throw new RuntimeException("Wait before Lock"); } waits++; } else { System.out.println(output); throw new RuntimeException("Unexpected operation: " + s); } } } // 2) Expect Lock + Notify + Unlock. expect("Lock", it, output); expect("Notify", it, output); expect("Unlock", it, output); // 3) A single thread wakes up, runs, and dies. expect("Awakened", it, output); expect("Unlock", it, output); // 4) Expect Lock + NotifyAll + Unlock. expect("Lock", it, output); expect("NotifyAll", it, output); expect("Unlock", it, output); // 5) N-1 threads wake up, run, and die. { int expectedUnlocks = 0; int ops = 2 * (N-1); for (int i = 0; i < ops; i++) { String s = it.next(); if (s.equals("Awakened")) { expectedUnlocks++; } else if (s.equals("Unlock")) { expectedUnlocks--; if (expectedUnlocks < 0) { System.out.println(output); throw new RuntimeException("Unexpected unlock"); } } } } // 6) That should be it. if (it.hasNext()) { System.out.println(output); throw new RuntimeException("Unexpected trailing output, starting with " + it.next()); } output.clear(); System.out.println("Done"); } private static void expect(String s, Iterator it, List output) { String t = it.next(); if (!s.equals(t)) { System.out.println(output); throw new RuntimeException("Expected " + s + " but got " + t); } } private static void lock(long id) { appendToLog("Lock"); rawMonitorEnter(id); } private static void unlock(long id) { appendToLog("Unlock"); try { rawMonitorExit(id); } catch (RuntimeException e) { appendToLog(e.getMessage()); } } private static void rawWait(long id, long millis) { appendToLog("Wait"); try { rawMonitorWait(id, millis); } catch (RuntimeException e) { appendToLog(e.getMessage()); } } private static void rawNotify(long id) { appendToLog("Notify"); try { rawMonitorNotify(id); } catch (RuntimeException e) { appendToLog(e.getMessage()); } } private static void rawNotifyAll(long id) { appendToLog("NotifyAll"); try { rawMonitorNotifyAll(id); } catch (RuntimeException e) { appendToLog(e.getMessage()); } } private static synchronized void appendToLog(String s) { output.add(s); } private static void startWatchdog() { Runnable r = new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); // Give it a minute. long end = 60 * 1000 + start; for (;;) { long delta = end - System.currentTimeMillis(); if (delta <= 0) { break; } try { Thread.currentThread().sleep(delta); } catch (Exception e) { } } System.out.println("TIMEOUT!"); System.exit(1); } }; Thread t = new Thread(r); t.setDaemon(true); t.start(); } static volatile long sharedId; static List output; static Thread firstAwakened; private static native long createRawMonitor(); private static native void destroyRawMonitor(long id); private static native void rawMonitorEnter(long id); private static native void rawMonitorExit(long id); private static native void rawMonitorWait(long id, long millis); private static native void rawMonitorNotify(long id); private static native void rawMonitorNotifyAll(long id); }