import java.util.concurrent.*;
import java.util.*;
import java.util.stream.*;

public class Test {

	public static void main(final String[] args) throws Exception {
		System.out.println("Program launch count: " + args[0]);
		// anything <= 8192 doesn't reproduce the issue given the implementation in Arrays.parallelSort()
		final int numElements = 8193;
		final Long[] data = new Long[numElements]; 
		for (int i = 0; i < data.length; i++) {
			data[i] = Long.valueOf(i);
		}
		// fire off tasks using ForkJoinPool.commonPool(). the tasks will operate
		// over the map and use a parallel sorted stream to do some computation
		final int numTasks = 10;
		final ForkJoinPool executor = ForkJoinPool.commonPool();
		System.out.println("ForkJoinPool parallelism: " + executor.getParallelism());
		final List<Future<Void>> results = new ArrayList<>();
		for (int i = 0; i < numTasks; i++) {
			final Future<Void> f = executor.submit(new Task(data));
			results.add(f);
		}

		// wait for results
		try {
			for (final Future<Void> result : results) {
				result.get(1, TimeUnit.MINUTES);
			}
		} catch (TimeoutException e) {
			System.err.println("FAILURE - Tasks did not complete in expected time, this indicates a hang, thread dump available below:");
			dumpAllThreadStackTrace();
			System.exit(1); // indicate a failed execution
		}
		System.out.println("Done");
	}

	private static final class Task implements Callable<Void> {
		private final Long[] data;

		private Task(final Long[] data) {
			this.data = data;
		}

		@Override
		public Void call() {
			Arrays.parallelSort(data);
        	System.out.println("Computation completed in " + Thread.currentThread().getName());
        	return null;
		}
	}

	private static void dumpAllThreadStackTrace() {
		final Map<Thread, StackTraceElement[]> allThreadDumps = Thread.getAllStackTraces();
		final StringBuilder sb = new StringBuilder("Thread dump generated at " + new Date() + " on Java version: " + System.getProperty("java.version") + "\n");
		for (final Map.Entry<Thread, StackTraceElement[]> entry : allThreadDumps.entrySet()) {
			final Thread t = entry.getKey();
			sb.append("\n" + t.getName() + ":\n");
			final StackTraceElement[] trace = entry.getValue();
			if (trace.length == 0) {
				sb.append("\t<no stacktrace>\n");
			} else {
				for (final StackTraceElement traceElement : trace) {
	            	sb.append("\tat " + traceElement + "\n");
	            }
	        }
		}
		System.err.println(sb.toString());
	}

}