/*
 * Copyright (C) 2011 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 java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CyclicBarrier;

public class Main implements Runnable {

    // Timeout in minutes. Make it larger than the run-test timeout to get a native thread dump by
    // ART on timeout when running on the host.
    private final static long TIMEOUT_VALUE = 7;

    private final static long MAX_SIZE = 1000;  // Maximum size of array-list to allocate.

    private final static int THREAD_COUNT = 16;

    // Use a couple of different forms of synchronizing to test some of these...
    private final static AtomicInteger counter = new AtomicInteger();
    private final static Object gate = new Object();
    private volatile static int waitCount = 0;

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[THREAD_COUNT];

        // This barrier is used to synchronize the threads starting to allocate.
        // Note: Even though a barrier is not allocation-free, this one is fine, as it will be used
        //       before filling the heap.
        CyclicBarrier startBarrier = new CyclicBarrier(threads.length);

        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Main(startBarrier));
            threads[i].start();
        }

        // Wait for the threads to finish.
        for (Thread thread : threads) {
            thread.join();
        }

        // Allocate objects to definitely run GC before quitting.
        allocateObjectsToRunGc();

        new ArrayList<Object>(50);
    }

    private static void allocateObjectsToRunGc() {
      ArrayList<Object> l = new ArrayList<Object>();
      try {
          for (int i = 0; i < 100000; i++) {
              l.add(new ArrayList<Object>(i));
          }
      } catch (OutOfMemoryError oom) {
      }
    }

    private Main(CyclicBarrier startBarrier) {
        this.startBarrier = startBarrier;
    }

    private ArrayList<Object> store;
    private CyclicBarrier startBarrier;

    public void run() {
        try {
            work();
        } catch (Throwable t) {
            // Any exception or error getting here is bad.
            try {
                // May need allocations...
                t.printStackTrace(System.out);
            } catch (Throwable tInner) {
            }
            System.exit(1);
        }
    }

    private void work() throws Exception {
        // Any exceptions except an OOME in the allocation loop are bad and handed off to the
        // caller which should abort the whole runtime.

        ArrayList<Object> l = new ArrayList<Object>();
        store = l;  // Keep it alive.

        // Wait for the start signal.
        startBarrier.await(TIMEOUT_VALUE, java.util.concurrent.TimeUnit.MINUTES);

        // Allocate.
        try {
            for (int i = 0; i < MAX_SIZE; i++) {
                l.add(new ArrayList<Object>(i));
            }
        } catch (OutOfMemoryError oome) {
            // Fine, we're done.
        }

        // Atomically increment the counter and check whether we were last.
        int number = counter.incrementAndGet();

        if (number < THREAD_COUNT) {
            // Not last.
            synchronized (gate) {
                // Increment the wait counter.
                waitCount++;
                gate.wait(TIMEOUT_VALUE * 1000 * 60);
            }
        } else {
            // Last. Wait until waitCount == THREAD_COUNT - 1.
            for (int loops = 0; ; loops++) {
                synchronized (gate) {
                    if (waitCount == THREAD_COUNT - 1) {
                        // OK, everyone's waiting. Notify and break out.
                        gate.notifyAll();
                        break;
                    } else if (loops > 40) {
                        // 1s wait, too many tries.
                        System.out.println("Waited too long for the last thread.");
                        System.exit(1);
                    }
                }
                // Wait a bit.
                Thread.sleep(25);
            }
        }

        store = null;  // Allow GC to reclaim it.
    }
}