/*
 * Written by Bill Scherer and Doug Lea with assistance from members
 * of JCP JSR-166 Expert Group and released to the public domain, as
 * explained at http://creativecommons.org/publicdomain/zero/1.0/
 */

import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;

public class TimeoutExchangerLoops {
    static final int NCPUS = Runtime.getRuntime().availableProcessors();

    static final int  DEFAULT_THREADS = NCPUS + 2;
    static final long DEFAULT_PATIENCE_NANOS = 500000;
    static final long DEFAULT_TRIAL_MILLIS   = 10000;

    public static void main(String[] args) throws Exception {
        int maxThreads = DEFAULT_THREADS;
        long trialMillis = DEFAULT_TRIAL_MILLIS;
        long patienceNanos = DEFAULT_PATIENCE_NANOS;
        int nReps = 3;

        // Parse and check args
        int argc = 0;
        while (argc < args.length) {
            String option = args[argc++];
            if (option.equals("-t"))
                trialMillis = Integer.parseInt(args[argc]);
            else if (option.equals("-p"))
                patienceNanos = Long.parseLong(args[argc]);
            else if (option.equals("-r"))
                nReps = Integer.parseInt(args[argc]);
            else
                maxThreads = Integer.parseInt(option);
            argc++;
        }

        // Display runtime parameters
        System.out.print("TimeoutExchangerTest");
        System.out.print(" -t " + trialMillis);
        System.out.print(" -p " + patienceNanos);
        System.out.print(" -r " + nReps);
        System.out.print(" max threads " + maxThreads);
        System.out.println();

        System.out.println("Warmups..");
        long warmupTime = 1000;
        long sleepTime = 500;
        if (false) {
            for (int k = 0; k < 10; ++k) {
                for (int j = 0; j < 10; ++j) {
                    oneRun(2, (j + 1) * 1000, patienceNanos);
                    Thread.sleep(sleepTime);
                }
            }
        }

        oneRun(3, warmupTime, patienceNanos);
        Thread.sleep(sleepTime);

        for (int i = maxThreads; i >= 2; i -= 1) {
            oneRun(i, warmupTime, patienceNanos);
            Thread.sleep(sleepTime);
        }

        for (int j = 0; j < nReps; ++j) {
            System.out.println("Replication " + j);
            for (int i = 2; i <= maxThreads; i += 2) {
                oneRun(i, trialMillis, patienceNanos);
                Thread.sleep(sleepTime);
            }
        }
    }

    static void oneRun(int nThreads, long trialMillis, long patienceNanos)
        throws Exception {
        System.out.printf("%4d threads", nThreads);
        System.out.printf("%9dms", trialMillis);
        final CountDownLatch start = new CountDownLatch(1);
        Exchanger x = new Exchanger();
        Runner[] runners = new Runner[nThreads];
        Thread[] threads = new Thread[nThreads];
        for (int i = 0; i < nThreads; ++i) {
            runners[i] = new Runner(x, patienceNanos, start);
            threads[i] = new Thread(runners[i]);
            threads[i].start();
        }
        long startTime = System.nanoTime();
        start.countDown();
        Thread.sleep(trialMillis);
        for (int i = 0; i < nThreads; ++i)
            threads[i].interrupt();
        long elapsed = System.nanoTime() - startTime;
        for (int i = 0; i < nThreads; ++i)
            threads[i].join();
        int iters = 0;
        long fails = 0;
        for (int i = 0; i < nThreads; ++i) {
            iters += runners[i].iters;
            fails += runners[i].failures;
        }
        if (iters <= 0) iters = 1;
        long rate = iters * 1000L * 1000L * 1000L / elapsed;
        long npt = elapsed / iters;
        double failRate = (fails * 100.0) / (double) iters;
        System.out.printf("%9d it/s ", rate);
        System.out.printf("%9d ns/it", npt);
        System.out.printf("%9.5f%% fails", failRate);
        System.out.println();
        //        x.printStats();
    }


    static final class Runner implements Runnable {
        final Exchanger exchanger;
        final CountDownLatch start;
        final long patience;
        volatile int iters;
        volatile int failures;
        Runner(Exchanger x, long patience, CountDownLatch start) {
            this.exchanger = x;
            this.patience = patience;
            this.start = start;
        }

        public void run() {
            int i = 0;
            try {
                Exchanger x = exchanger;
                Object m = new Integer(17);
                long p = patience;
                start.await();
                for (;;) {
                    try {
                        Object e = x.exchange(m, p, TimeUnit.NANOSECONDS);
                        if (e == null || e == m)
                            throw new Error();
                        m = e;
                        ++i;
                    } catch (TimeoutException to) {
                        if (Thread.interrupted()) {
                            iters = i;
                            return;
                        }
                        ++i;
                        ++failures;
                    }
                }
            } catch (InterruptedException ie) {
                iters = i;
            }
        }
    }
}
