| /* |
| * 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 { |
| Main.bindAgentJNIForClass(Test923.class); |
| 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<String>(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<Thread> threads = new ArrayList<Thread>(); |
| 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<String> 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<String> it, List<String> 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<String> 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); |
| } |