com.github.benmanes.caffeine.testing.Threads.java Source code

Java tutorial

Introduction

Here is the source code for com.github.benmanes.caffeine.testing.Threads.java

Source

/*
 * Copyright 2015 Ben Manes. All Rights Reserved.
 *
 * 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 com.github.benmanes.caffeine.testing;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.testng.Assert.fail;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.testng.log4testng.Logger;

import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

/**
 * Shared utilities for multi-threaded tests.
 *
 * @author ben.manes@gmail.com (Ben Manes)
 */
public final class Threads {
    private static final Logger logger = Logger.getLogger(Threads.class);

    public static final int ITERATIONS = 40000;
    public static final int NTHREADS = 20;
    public static final int TIMEOUT = 30;

    private Threads() {
    }

    public static <A> void runTest(A collection, List<BiConsumer<A, Integer>> operations) {
        Queue<String> failures = new ConcurrentLinkedQueue<>();
        Runnable thrasher = new Thrasher<A>(collection, failures, operations);
        Threads.executeWithTimeOut(failures, () -> ConcurrentTestHarness.timeTasks(Threads.NTHREADS, thrasher));
        assertThat(failures, is(empty()));
    }

    public static void executeWithTimeOut(Queue<String> failures, Callable<Long> task) {
        ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).build());
        Future<Long> future = es.submit(task);
        try {
            long timeNS = future.get(TIMEOUT, TimeUnit.SECONDS);
            logger.debug("\nExecuted in " + TimeUnit.NANOSECONDS.toSeconds(timeNS) + " second(s)");
        } catch (ExecutionException e) {
            fail("Exception during test: " + e.toString(), e);
        } catch (TimeoutException e) {
            handleTimout(failures, es, e);
        } catch (InterruptedException e) {
            fail("", e);
        }
    }

    public static void handleTimout(Queue<String> failures, ExecutorService es, TimeoutException e) {
        for (StackTraceElement[] trace : Thread.getAllStackTraces().values()) {
            for (StackTraceElement element : trace) {
                logger.info("\tat " + element);
            }
            if (trace.length > 0) {
                logger.info("------");
            }
        }
        MoreExecutors.shutdownAndAwaitTermination(es, 10, TimeUnit.SECONDS);
        for (String failure : failures) {
            logger.debug(failure);
        }
        fail("Spun forever", e);
    }

    public static List<List<Integer>> workingSets(int nThreads, int iterations) {
        List<Integer> keys = IntStream.range(0, iterations).boxed()
                .map(i -> ThreadLocalRandom.current().nextInt(iterations / 100)).collect(Collectors.toList());
        return shuffle(nThreads, keys);
    }

    /**
     * Based on the passed in working set, creates N shuffled variants.
     *
     * @param samples the number of variants to create
     * @param baseline the base working set to build from
     */
    private static <T> List<List<T>> shuffle(int samples, Collection<T> baseline) {
        List<List<T>> workingSets = new ArrayList<>(samples);
        for (int i = 0; i < samples; i++) {
            List<T> workingSet = new ArrayList<>(baseline);
            Collections.shuffle(workingSet);
            workingSets.add(ImmutableList.copyOf(workingSet));
        }
        return ImmutableList.copyOf(workingSets);
    }

    /** Executes operations against the cache to simulate random load. */
    public static final class Thrasher<A> implements Runnable {
        private final List<BiConsumer<A, Integer>> operations;
        private final List<List<Integer>> sets;
        private final Queue<String> failures;
        private final AtomicInteger index;
        private final A collection;

        public Thrasher(A collection, Queue<String> failures, List<BiConsumer<A, Integer>> operations) {
            this.sets = workingSets(Threads.NTHREADS, Threads.ITERATIONS);
            this.index = new AtomicInteger();
            this.operations = operations;
            this.collection = collection;
            this.failures = failures;
        }

        @Override
        public void run() {
            int id = index.getAndIncrement();
            for (Integer e : sets.get(id)) {
                BiConsumer<A, Integer> operation = operations
                        .get(ThreadLocalRandom.current().nextInt(operations.size()));
                try {
                    operation.accept(collection, e);
                } catch (Throwable t) {
                    failures.add(String.format("Failed: key %s on operation %s%n%s", e, operation,
                            Throwables.getStackTraceAsString(t)));
                    throw t;
                }
            }
        }
    }
}