| /* |
| * Copyright (C) 2016 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. |
| */ |
| |
| import annotations.BootstrapMethod; |
| import annotations.CalledByIndy; |
| import java.lang.invoke.CallSite; |
| import java.lang.invoke.ConstantCallSite; |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.util.concurrent.CyclicBarrier; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| public class TestInvokeCustomWithConcurrentThreads extends TestBase implements Runnable { |
| private static final int NUMBER_OF_THREADS = 16; |
| |
| private static final AtomicInteger nextIndex = new AtomicInteger(0); |
| |
| private static final ThreadLocal<Integer> threadIndex = |
| new ThreadLocal<Integer>() { |
| @Override |
| protected Integer initialValue() { |
| return nextIndex.getAndIncrement(); |
| } |
| }; |
| |
| // Array of call sites instantiated, one per thread |
| private static final CallSite[] instantiated = new CallSite[NUMBER_OF_THREADS]; |
| |
| // Array of counters for how many times each instantiated call site is called |
| private static final AtomicInteger[] called = new AtomicInteger[NUMBER_OF_THREADS]; |
| |
| // Array of call site indices of which call site a thread invoked |
| private static final AtomicInteger[] targetted = new AtomicInteger[NUMBER_OF_THREADS]; |
| |
| // Synchronization barrier all threads will wait on in the bootstrap method. |
| private static final CyclicBarrier barrier = new CyclicBarrier(NUMBER_OF_THREADS); |
| |
| private TestInvokeCustomWithConcurrentThreads() {} |
| |
| private static int getThreadIndex() { |
| return threadIndex.get().intValue(); |
| } |
| |
| public static int notUsed(int x) { |
| return x; |
| } |
| |
| public void run() { |
| int x = setCalled(-1 /* argument dropped */); |
| notUsed(x); |
| } |
| |
| @CalledByIndy( |
| bootstrapMethod = |
| @BootstrapMethod( |
| enclosingType = TestInvokeCustomWithConcurrentThreads.class, |
| name = "linkerMethod", |
| parameterTypes = {MethodHandles.Lookup.class, String.class, MethodType.class} |
| ), |
| fieldOrMethodName = "setCalled", |
| returnType = int.class, |
| parameterTypes = {int.class} |
| ) |
| private static int setCalled(int index) { |
| called[index].getAndIncrement(); |
| targetted[getThreadIndex()].set(index); |
| return 0; |
| } |
| |
| @SuppressWarnings("unused") |
| private static CallSite linkerMethod( |
| MethodHandles.Lookup caller, String name, MethodType methodType) throws Throwable { |
| MethodHandle mh = |
| caller.findStatic(TestInvokeCustomWithConcurrentThreads.class, name, methodType); |
| assertEquals(methodType, mh.type()); |
| assertEquals(mh.type().parameterCount(), 1); |
| mh = MethodHandles.insertArguments(mh, 0, getThreadIndex()); |
| mh = MethodHandles.dropArguments(mh, 0, int.class); |
| assertEquals(mh.type().parameterCount(), 1); |
| assertEquals(methodType, mh.type()); |
| |
| // Wait for all threads to be in this method. |
| // Multiple call sites should be created, but only one |
| // invoked. |
| barrier.await(); |
| |
| instantiated[getThreadIndex()] = new ConstantCallSite(mh); |
| return instantiated[getThreadIndex()]; |
| } |
| |
| public static void test() throws Throwable { |
| // Initialize counters for which call site gets invoked |
| for (int i = 0; i < NUMBER_OF_THREADS; ++i) { |
| called[i] = new AtomicInteger(0); |
| targetted[i] = new AtomicInteger(0); |
| } |
| |
| // Run threads that each invoke-custom the call site |
| Thread[] threads = new Thread[NUMBER_OF_THREADS]; |
| for (int i = 0; i < NUMBER_OF_THREADS; ++i) { |
| threads[i] = new Thread(new TestInvokeCustomWithConcurrentThreads()); |
| threads[i].start(); |
| } |
| |
| // Wait for all threads to complete |
| for (int i = 0; i < NUMBER_OF_THREADS; ++i) { |
| threads[i].join(); |
| } |
| |
| // Check one call site instance won |
| int winners = 0; |
| int votes = 0; |
| for (int i = 0; i < NUMBER_OF_THREADS; ++i) { |
| assertNotEquals(instantiated[i], null); |
| if (called[i].get() != 0) { |
| winners++; |
| votes += called[i].get(); |
| } |
| } |
| |
| System.out.println("Winners " + winners + " Votes " + votes); |
| |
| // We assert this below but output details when there's an error as |
| // it's non-deterministic. |
| if (winners != 1) { |
| System.out.println("Threads did not the same call-sites:"); |
| for (int i = 0; i < NUMBER_OF_THREADS; ++i) { |
| System.out.format( |
| " Thread % 2d invoked call site instance #%02d\n", i, targetted[i].get()); |
| } |
| } |
| |
| // We assert this below but output details when there's an error as |
| // it's non-deterministic. |
| if (votes != NUMBER_OF_THREADS) { |
| System.out.println("Call-sites invocations :"); |
| for (int i = 0; i < NUMBER_OF_THREADS; ++i) { |
| System.out.format( |
| " Call site instance #%02d was invoked % 2d times\n", i, called[i].get()); |
| } |
| } |
| |
| assertEquals(winners, 1); |
| assertEquals(votes, NUMBER_OF_THREADS); |
| } |
| } |