All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.lucaventuri.fibry.Generator Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
package eu.lucaventuri.fibry;

import eu.lucaventuri.common.SystemUtils;
import eu.lucaventuri.concurrent.AntiFreeze;

import com.sun.management.OperatingSystemMXBean;

import java.lang.management.ManagementFactory;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * A generator is like a lazy Iterable, so it can no know implicitly if the elements are over.
 * In general, the generator should be able to be iterated multiple times
 */
public interface Generator extends Iterable {
    enum State {
        /** It is not known yet if there are other elements available */
        WAITING,
        /** There are other elements available */
        GENERATING,
        /** All the elements have been produced */
        FINISHED;
    }

    interface Yielder {
        void yield(T element);
    }

    /** This producer is the simplest, but the performance are suboptimal */
    interface GeneratorProducer {
        /** Calls yield as many times as needed to add the items, then simply exit the function **/
        void produceAllItems(Yielder yielder);
    }

    /**
     * This producer can reach top speed, however the last element must be returned, not yielded; returning null is not allowed.
     */
    interface AdvancedGeneratorProducer {
        /**
         * Produces all the items, yielding all of them except the last one, which will be returned
         *
         * @param yielder Yielder, to provide the elements
         * @return the last element (must be != null)
         */
        T produceAllItems(Yielder yielder);
    }

    default Stream toStream() {
        return streamFromIterable(this);
    }

    /**
     * Transforms a blocking queue in a generator; the difference being that we will explicitly says when the data are over.
     * This is an advanced method, and callers should preferably use fromProducer() or fromAdvancedProducer()
     */
    static  Iterator fromQueue(BlockingQueue queue, AtomicReference stateRef, final boolean maxThroughput) {
        return new Iterator() {
            @Override
            public boolean hasNext() {
                State state;

                // Waiting to understand if there is an element or not
                while ((state = stateRef.get()) == State.WAITING && queue.isEmpty()) {
                    if (!maxThroughput)
                        SystemUtils.sleep(1);
                }

                return state == State.GENERATING || !queue.isEmpty();
            }

            @Override
            public T next() {
                while (true) {
                    if (stateRef.get() == State.FINISHED && queue.isEmpty())
                        throw new NoSuchElementException();

                    try {
                        T elem = queue.poll(10, TimeUnit.MILLISECONDS);

                        // In case of null, it will retry and eventually throw an error in case of problems

                        if (elem != null)
                            return elem;
                    } catch (InterruptedException e) {
                        // Just retry
                    }
                }
            }
        };
    }

    /**
     * Simplest way to create a generator, but it can be slow if the queue size is small, so a queue size of 100+ is recommended if performance are not good
     */
    static  Generator fromProducer(GeneratorProducer producer, int queueSize) {
        return fromProducer(producer, queueSize, false);
    }

    /**
     * Simplest way to create a generator, but it can be slow if the queue size is small, so a queue size of 100+ is recommended if performance are not good
     */
    static  Generator fromProducer(GeneratorProducer producer, int queueSize, boolean maxThroughput) {
        return fromProducer(producer, queueSize, maxThroughput, 0, 0);
    }

    /**
     * Simplest way to create a generator, but it can be slow if the queue size is small, so a queue size of 100+ is recommended if performance are not good
     */
    static  Generator fromProducer(GeneratorProducer producer, int queueSize, boolean maxThroughput, int itemTimeoutMs, int fullTimeoutMs) {
        assert queueSize >= 1;

        return () -> {
            AtomicReference stateRef = new AtomicReference<>(State.WAITING);
            BlockingQueue queue = new LinkedBlockingDeque<>(queueSize);

            Stereotypes.def().runOnce(() -> {
                AntiFreeze frz = fullTimeoutMs <=0 ? null : new AntiFreeze(itemTimeoutMs, fullTimeoutMs);

                try {
                    producer.produceAllItems(elem -> {
                        safeOffer(queue, elem);
                        if (frz != null)
                            frz.notifyActivity();
                    });
                } finally {
                    stateRef.set(State.FINISHED);
                    if (frz != null)
                        frz.notifyFinished();
                }
            });

            return fromQueue(queue, stateRef, maxThroughput);
        };
    }

    /**
     * Simplest way to create a generator, but it can be slow if the queue size is small, so a queue size of 100+ is recommended if performance are not good
     */
    static  Generator fromParallelProducers(Supplier> producerSupplier, int numProducers, int queueSize) {
        return fromParallelProducers(producerSupplier, numProducers, queueSize, false);
    }

    /**
     * Simplest way to create a generator, but it can be slow if the queue size is small, so a queue size of 100+ is recommended if performance are not good
     */
    static  Generator fromParallelProducers(Supplier> producerSupplier, int numProducers, int queueSize, boolean maxThroughput) {
        assert queueSize >= 1;

        return () -> {
            AtomicReference stateRef = new AtomicReference<>(State.WAITING);
            BlockingQueue queue = new LinkedBlockingDeque<>(queueSize);
            CountDownLatch latch = new CountDownLatch(numProducers);
            AtomicInteger numElements = new AtomicInteger();

            for (int i = 0; i < numProducers; i++) {
                Stereotypes.def().runOnce(() -> {
                    try {
                        producerSupplier.get().produceAllItems(elem -> {
                            safeOffer(queue, elem);
                            numElements.incrementAndGet();
                        });
                        latch.countDown();
                        System.out.println("Counting down at " + numElements.get());

                        try {
                            latch.await();
                        } catch (InterruptedException e) {
                        }
                    } finally {
                        stateRef.set(State.FINISHED);
                        System.out.println("Finished at " + numElements.get());
                    }
                });
            }

            return fromQueue(queue, stateRef, maxThroughput);
        };
    }

    /**
     * This is the way to create a fast generator, with the small inconvenience that the last element should be returned, not yielded.
     * Failing to do so and returning null, will eventually result in a NoSuchElementException
     */
    static  Generator fromAdvancedProducer(AdvancedGeneratorProducer producer, int queueSize) {
        return fromAdvancedProducer(producer, queueSize, false);
    }

    /**
     * This is the way to create a fast generator, with the small inconvenience that the last element should be returned, not yielded.
     * Failing to do so and returning null, will eventually result in a NoSuchElementException
     */
    static  Generator fromAdvancedProducer(AdvancedGeneratorProducer producer, int queueSize, boolean maxThroughput) {
     return fromAdvancedProducer(producer, queueSize, maxThroughput, 0, 0);
    }

    /**
     * This is the way to create a fast generator, with the small inconvenience that the last element should be returned, not yielded.
     * Failing to do so and returning null, will eventually result in a NoSuchElementException
     */
    static  Generator fromAdvancedProducer(AdvancedGeneratorProducer producer, int queueSize, boolean maxThroughput, int itemTimeoutMs, int fullTimeoutMs) {
        assert queueSize >= 1;

        return () -> {
            AtomicReference stateRef = new AtomicReference<>(State.WAITING);
            BlockingQueue queue = new LinkedBlockingDeque<>(queueSize);

            Stereotypes.def().runOnce(() -> {
                AntiFreeze frz = fullTimeoutMs <=0 ? null : new AntiFreeze(itemTimeoutMs, fullTimeoutMs);

                try {
                    T lastElement = producer.produceAllItems(elem -> {
                        stateRef.set(State.GENERATING);
                        safeOffer(queue, elem);
                        if (frz != null)
                            frz.notifyActivity();
                    });
                    offerLastElement(stateRef, queue, lastElement);
                    if (frz != null)
                        frz.notifyActivity();
                } finally {
                    stateRef.set(State.FINISHED);
                    if (frz != null)
                        frz.notifyFinished();
                }
            });

            return fromQueue(queue, stateRef, maxThroughput);
        };
    }

    /**
     * This creates the fastest generator (slightly faster than advanced generators), with two small inconveniences:
     * - the last element should be returned, not yielded
     * - the generator needs to produce at least one element (the one that is returned)
     * Failing to do so and returning null, will eventually result in a NoSuchElementException
     */
    static  Generator fromNonEmptyAdvancedProduce(AdvancedGeneratorProducer producer, int queueSize) {
        return fromNonEmptyAdvancedProduce(producer, queueSize, false);
    }

    /**
     * This creates the fastest generator (slightly faster than advanced generators), with two small inconveniences:
     * - the last element should be returned, not yielded
     * - the generator needs to produce at least one element (the one that is returned)
     * Failing to do so and returning null, will eventually result in a NoSuchElementException
     */
    static  Generator fromNonEmptyAdvancedProduce(AdvancedGeneratorProducer producer, int queueSize, boolean maxThroughput) {
        assert queueSize >= 1;

        return () -> {
            AtomicReference stateRef = new AtomicReference<>(State.GENERATING);
            BlockingQueue queue = new LinkedBlockingDeque<>(queueSize);

            Stereotypes.def().runOnce(() -> {
                try {
                    T lastElement = producer.produceAllItems(elem -> {
                        safeOffer(queue, elem);
                    });
                    offerLastElement(stateRef, queue, lastElement);
                } finally {
                    stateRef.set(State.FINISHED);
                }
            });

            return fromQueue(queue, stateRef, maxThroughput);
        };
    }

    static  void offerLastElement(AtomicReference stateRef, BlockingQueue queue, T lastElement) {
        assert lastElement != null || stateRef.get() == State.WAITING;

        if (lastElement != null) {
            stateRef.set(State.WAITING);
            safeOffer(queue, lastElement);
        }
    }

    static  void safeOffer(BlockingQueue queue, T elem) {
        while (true) {
            try {
                queue.offer(elem, Integer.MAX_VALUE, TimeUnit.SECONDS);
                break;
            } catch (InterruptedException e) {
                // just retry
            }
        }
    }

    static  Stream streamFromIterator(Iterator iter) {
        Spliterator
                spliterator = Spliterators
                .spliteratorUnknownSize(iter, 0);

        return StreamSupport.stream(spliterator, false);
    }

    static  Stream streamFromIterable(Iterable iterable) {
        return streamFromIterator(iterable.iterator());
    }

    public static void main(String[] args) {
        OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();

        long time = osBean.getProcessCpuTime();

        Generator.fromProducer(yielder -> {
            SystemUtils.sleepEnsure(2000);
        }, 100, true).forEach(obj -> {});

        long time2 = osBean.getProcessCpuTime();
        System.out.println("Max throughput: " + ((time2 - time) / 1_000_000) + " ms");

        Generator.fromProducer(yielder -> {
            SystemUtils.sleepEnsure(2000);
        }, 100, false).forEach(obj -> {});

        long time3 = osBean.getProcessCpuTime();
        System.out.println("Normal throughput: " + ((time3 - time2) / 1_000_000) + " ms");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy