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

com.apple.foundationdb.async.MoreAsyncUtil Maven / Gradle / Ivy

There is a newer version: 2.8.110.0
Show newest version
/*
 * MoreAsyncUtil.java
 *
 * This source file is part of the FoundationDB open source project
 *
 * Copyright 2015-2018 Apple Inc. and the FoundationDB project authors
 *
 * 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.apple.foundationdb.async;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.util.LoggableException;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import static com.apple.foundationdb.async.AsyncUtil.collect;
import static com.apple.foundationdb.async.AsyncUtil.tag;
import static com.apple.foundationdb.async.AsyncUtil.whenAny;
import static com.apple.foundationdb.async.AsyncUtil.whileTrue;

/**
 * More helpers in the spirit of {@link AsyncUtil}.
 */
@API(API.Status.UNSTABLE)
public class MoreAsyncUtil {

    private static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor
            = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setDaemon(true).build());

    static {
        scheduledThreadPoolExecutor.setKeepAliveTime(30, TimeUnit.SECONDS);
        scheduledThreadPoolExecutor.allowCoreThreadTimeOut(true);
    }

    @Nonnull
    public static  AsyncIterable limitIterable(@Nonnull final AsyncIterable iterable,
                                                     final int limit) {
        return new AsyncIterable() {
            @Nonnull
            @Override
            public CloseableAsyncIterator iterator() {
                return new CloseableAsyncIterator() {
                    final AsyncIterator iterator = iterable.iterator();
                    int count = 0;

                    @Override
                    public CompletableFuture onHasNext() {
                        if (count < limit) {
                            return iterator.onHasNext();
                        } else {
                            return AsyncUtil.READY_FALSE;
                        }
                    }

                    @Override
                    public boolean hasNext() {
                        return (count < limit) && iterator.hasNext();
                    }

                    @Override
                    public T next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException();
                        }
                        count++;
                        return iterator.next();
                    }

                    @Override
                    public void close() {
                        closeIterator(iterator);
                    }

                    @Override
                    public void remove() {
                        iterator.remove();
                    }
                };
            }

            @Override
            public CompletableFuture> asList() {
                return collect(this);
            }
        };
    }

    /**
     * Filter items from an async iterable.
     * @param iterable the source
     * @param filter only items in iterable for which this function returns true will appear in the return value
     * @param  the source type
     * @return a new {@code AsyncIterable} that only contains those items in iterable for which filter returns {@code true}
     */
    @Nonnull
    public static  AsyncIterable filterIterable(@Nonnull final AsyncIterable iterable,
                                                      @Nonnull final Function filter) {
        return filterIterable(ForkJoinPool.commonPool(), iterable, filter);
    }

    @Nonnull
    public static  AsyncIterable filterIterable(@Nonnull Executor executor,
                                                      @Nonnull final AsyncIterable iterable,
                                                      @Nonnull final Function filter) {
        return new AsyncIterable() {
            @Nonnull
            @Override
            public CloseableAsyncIterator iterator() {
                return new CloseableAsyncIterator() {
                    final AsyncIterator iterator = iterable.iterator();
                    T next;
                    boolean haveNext;
                    @Nullable
                    CompletableFuture nextFuture;

                    @Nonnull
                    @Override
                    public CompletableFuture onHasNext() {
                        if (nextFuture != null) {
                            return nextFuture;
                        }
                        if (haveNext) {
                            return AsyncUtil.READY_TRUE;
                        }
                        nextFuture = whileTrue(() -> iterator.onHasNext()
                            .thenApply(hasNext -> {
                                if (!hasNext) {
                                    return false;
                                }
                                next = iterator.next();
                                haveNext = filter.apply(next);
                                return !haveNext;
                            }), executor)
                            .thenApply(v -> haveNext);
                        return nextFuture;
                    }

                    @Override
                    public boolean hasNext() {
                        if (nextFuture != null) {
                            nextFuture.join();
                            nextFuture = null;
                        }
                        while (!haveNext && iterator.hasNext()) {
                            next = iterator.next();
                            haveNext = filter.apply(next);
                        }
                        return haveNext;
                    }

                    @Override
                    public T next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException();
                        }
                        haveNext = false;
                        return next;
                    }

                    @Override
                    public void close() {
                        if (nextFuture != null) {
                            nextFuture.cancel(false);
                            nextFuture = null;
                        }
                        closeIterator(iterator);
                    }

                    @Override
                    public void remove() {
                        iterator.remove();
                    }
                };
            }

            @Override
            public CompletableFuture> asList() {
                return collect(this);
            }
        };
    }

    /**
     * Remove adjacent duplicates form iterable.
     * Note: if iterable is sorted, this will actually remove duplicates.
     * @param iterable the source
     * @param  the source type
     * @return a new {@code AsyncIterable} that only contains those items in iterable for which the previous item was different
     */
    @Nonnull
    public static  AsyncIterable dedupIterable(@Nonnull final AsyncIterable iterable) {
        return dedupIterable(ForkJoinPool.commonPool(), iterable);
    }

    @Nonnull
    public static  AsyncIterable dedupIterable(@Nonnull Executor executor,
                                                     @Nonnull final AsyncIterable iterable) {
        return filterIterable(executor, iterable,
                new Function() {
                    private Object lastObj;

                    @Nonnull
                    @Override
                    public Boolean apply(T obj) {
                        if ((lastObj != null) && lastObj.equals(obj)) {
                            return false;
                        } else {
                            lastObj = obj;
                            return true;
                        }
                    }
                });
    }

    /**
     * Create a new iterable that has the contents of all the parameters in order.
     * @param iterables a list of iterables to concatenate together
     * @param  the source type
     * @return a new {@code AsyncIterable} that starts with all the elements of the first iterable provided,
     * then all the elements of the second iterable and so on
     */
    @Nonnull
    @SuppressWarnings("unchecked") // parameterized vararg
    public static  AsyncIterable concatIterables(@Nonnull final AsyncIterable... iterables) {
        return concatIterables(ForkJoinPool.commonPool(), iterables);
    }

    @Nonnull
    @SuppressWarnings("unchecked") // parameterized vararg
    public static  AsyncIterable concatIterables(@Nonnull Executor executor, @Nonnull final AsyncIterable... iterables) {
        return new AsyncIterable() {
            @Nonnull
            @Override
            public CloseableAsyncIterator iterator() {
                return new CloseableAsyncIterator() {
                    int index = 0;
                    @Nullable
                    AsyncIterator current;
                    AsyncIterator removeFrom;
                    @Nullable
                    CompletableFuture nextFuture;

                    @Nonnull
                    @Override
                    public CompletableFuture onHasNext() {
                        if (nextFuture != null) {
                            return nextFuture;
                        }
                        if (index >= iterables.length) {
                            return AsyncUtil.READY_FALSE;
                        }
                        nextFuture = whileTrue(() -> {
                            if (current == null) {
                                current = iterables[index].iterator();
                            }
                            return current.onHasNext()
                                    .thenApply(hasNext -> {
                                        if (hasNext) {
                                            return false;
                                        } else {
                                            current = null;
                                            return (++index < iterables.length);
                                        }
                                    });
                        }, executor).thenApply(v -> (index < iterables.length));
                        return nextFuture;
                    }

                    @Override
                    public boolean hasNext() {
                        if (nextFuture != null) {
                            nextFuture.join();
                            nextFuture = null;
                        }
                        while (index < iterables.length) {
                            if (current == null) {
                                current = iterables[index].iterator();
                            }
                            if (current.hasNext()) {
                                return true;
                            }
                            current = null;
                            index++;
                        }
                        return false;
                    }

                    @Override
                    public T next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException();
                        }
                        removeFrom = current;
                        return current.next();
                    }

                    @Override
                    public void close() {
                        if (nextFuture != null) {
                            nextFuture.cancel(false);
                            nextFuture = null;
                        }
                        closeIterator(current);
                    }

                    @Override
                    public void remove() {
                        if (removeFrom != null) {
                            removeFrom.remove();
                            removeFrom = null;
                        } else {
                            throw new IllegalStateException("Nothing to remove");
                        }
                    }
                };
            }

            @Override
            public CompletableFuture> asList() {
                if (iterables.length == 0) {
                    return CompletableFuture.completedFuture(Collections.emptyList());
                } else if (iterables.length == 1) {
                    return iterables[0].asList();
                } else {
                    final List result = new ArrayList<>();
                    return tag(whileTrue(new Supplier>() {
                                int index = 0;

                                @Override
                                public CompletableFuture get() {
                                    return iterables[index++].asList()
                                            .thenApply(asList -> {
                                                result.addAll(asList);
                                                return (index < iterables.length);
                                            });
                                }
                            }, executor),
                            result);
                }
            }
        };
    }

    /**
     * Maps each value in an iterable to a new iterable and returns the concatenated results.
     * This will start a pipeline of asynchronous requests
     * for up to a requested number of elements of the iterable, in parallel with requests to the mapping results.
     * This does not pipeline the overlapping concatenations, i.e. it won't grab the first item of the
     * second result of func, until it has exhausted the first result of func.
     * @param iterable the source
     * @param func mapping function from each element of iterable to a new iterable
     * @param pipelineSize the number of elements to pipeline
     * @param  the type of the source
     * @param  the type of the destination iterables
     * @return the results of all the {@code AsyncIterable}s returned by func for each value of iterable, concatenated
     */
    @Nonnull
    public static  AsyncIterable mapConcatIterable(@Nonnull final AsyncIterable iterable,
                                                              @Nonnull final Function> func,
                                                              final int pipelineSize) {
        return mapConcatIterable(ForkJoinPool.commonPool(), iterable, func, pipelineSize);
    }

    @Nonnull
    public static  AsyncIterable mapConcatIterable(@Nonnull Executor executor,
                                                              @Nonnull final AsyncIterable iterable,
                                                              @Nonnull final Function> func,
                                                              final int pipelineSize) {
        return new AsyncIterable() {
            @Nonnull
            @Override
            public CloseableAsyncIterator iterator() {
                CloseableAsyncIterator it = new CloseableAsyncIterator() {
                    final AsyncIterator iterator = iterable.iterator();
                    final Queue> pipeline = new ArrayDeque<>(pipelineSize);
                    @Nullable
                    AsyncIterator removeFrom;
                    @Nullable
                    CompletableFuture nextFuture;

                    @Nonnull
                    @Override
                    public CompletableFuture onHasNext() {
                        if (nextFuture != null) {
                            return nextFuture;
                        }
                        nextFuture = whileTrue(() -> {
                            List> waitOn = new ArrayList<>(2);
                            CompletableFuture outer = iterator.onHasNext();
                            if (isCompletedNormally(outer)) {
                                if (outer.getNow(false) && (pipeline.size() < pipelineSize)) {
                                    AsyncIterator next = func.apply(iterator.next()).iterator();
                                    pipeline.add(next);
                                    next.onHasNext();
                                    return AsyncUtil.READY_TRUE; // return to the top of this whileTrue
                                }
                            } else {
                                waitOn.add(outer);
                            }

                            CompletableFuture inner;
                            AsyncIterator current = pipeline.peek();
                            if (current != null) {
                                inner = current.onHasNext();
                                if (isCompletedNormally(inner)) {
                                    if (inner.getNow(false)) {
                                        // inner onHasNext returned true, break out of whileTrue
                                        return AsyncUtil.READY_FALSE; // First available
                                    } else {
                                        // inner exhausted, return to top of this whileTrue
                                        pipeline.remove();
                                        return AsyncUtil.READY_TRUE;
                                    }
                                } else {
                                    waitOn.add(inner);
                                }
                            }
                            // TODO whenAny should special handle elements of 1
                            if (waitOn.size() == 1) {
                                return waitOn.get(0).thenApply(new AlwaysTrue<>());
                            } else {
                                return whenAny(waitOn).thenApply(new AlwaysTrue<>());
                            }
                        }, executor).thenApply(v -> !pipeline.isEmpty());
                        return nextFuture;
                    }

                    @Override
                    public boolean hasNext() {
                        // Always keep the pipeline full, even when called synchronously.
                        return onHasNext().join();
                    }

                    @Override
                    public T2 next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException();
                        }
                        nextFuture = null;
                        AsyncIterator current = pipeline.peek();
                        removeFrom = current;
                        return current.next();
                    }

                    @Override
                    public void close() {
                        if (nextFuture != null) {
                            nextFuture.cancel(false);
                            nextFuture = null;
                        }
                        for (AsyncIterator pending : pipeline) {
                            closeIterator(pending);
                        }
                        closeIterator(iterator);
                    }

                    @Override
                    public void remove() {
                        if (removeFrom != null) {
                            removeFrom.remove();
                            removeFrom = null;
                        } else {
                            throw new IllegalStateException("Nothing to remove");
                        }
                    }
                };
                it.onHasNext(); // Initial pipeline fill.
                return it;
            }

            @Override
            public CompletableFuture> asList() {
                final List result = new ArrayList<>();
                return tag(whileTrue(new Supplier>() {
                            final AsyncIterator iterator = iterable.iterator();
                            boolean more = false;

                            @Override
                            public CompletableFuture get() {
                                if (more) {
                                    more = false;
                                    return func.apply(iterator.next()).asList()
                                            .thenApply(items -> {
                                                result.addAll(items);
                                                return true;
                                            });
                                } else {
                                    more = true;
                                    return iterator.onHasNext();
                                }
                            }
                        }, executor),
                        result);
            }
        };
    }

    // Filtering and mapping implemented using general pipelined
    // fan-out.  These could be implemented slightly more efficiently,
    // but then they'd have to duplicate the pipeline logic.

    /**
     * Filters a single item, returning an {@link AsyncIterable} of either 0 elements or just the provided one.
     * If the filter returns a {@code true} future, the resulting {@code AsyncIterable} will have the given item,
     * otherwise it will be empty.
     * @param item an item to potentially be filtered
     * @param filter a function that returns an asynchronous future to determine whether or not
     * to return item
     * @return an {@code AsyncIterable} that will either contain item or nothing, depending on the result
     * of filter
     */
    @Nonnull
    static  AsyncIterable filterToIterable(final T item,
                                                 @Nonnull final Function> filter) {
        return new AsyncIterable() {
            @Nullable
            @Override
            public CloseableAsyncIterator iterator() {
                return new CloseableAsyncIterator() {
                    boolean used = false;
                    @Nullable
                    CompletableFuture nextFuture;

                    @Nullable
                    @Override
                    public CompletableFuture onHasNext() {
                        if (used) {
                            return AsyncUtil.READY_FALSE;
                        }
                        if (nextFuture == null) {
                            nextFuture = filter.apply(item);
                        }
                        return nextFuture;
                    }

                    @Override
                    public boolean hasNext() {
                        if (used) {
                            return false;
                        }
                        if (nextFuture != null) {
                            return nextFuture.join();
                        } else {
                            return filter.apply(item).join();
                        }
                    }

                    @Override
                    public T next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException();
                        }
                        used = true;
                        return item;
                    }

                    @Override
                    public void close() {
                        if (nextFuture != null) {
                            nextFuture.cancel(false);
                            nextFuture = null;
                        }
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }

            @Override
            public CompletableFuture> asList() {
                return filter.apply(item)
                    .thenApply((Function>) match -> match ? Collections.singletonList(item) : Collections.emptyList());
            }
        };
    }

    /**
     * Filter an iterable, pipelining the asynchronous filter functions.
     * Unlike filterIterable, the filter here is asynchronous.
     * As items comes back from iterable, a pipeline of filter futures is kept without advancing
     * the iterable.
     * @param iterable the source
     * @param filter only the values of iterable for which the future returned by this filter returns true
     * will be in the resulting iterable
     * @param pipelineSize the number of filter results to pipeline
     * @param  the source type
     * @return a new {@code AsyncIterable} containing the elements of iterable for which filter returns a true future
     */
    @Nonnull
    public static  AsyncIterable filterIterablePipelined(@Nonnull AsyncIterable iterable,
                                                               @Nonnull final Function> filter,
                                                               int pipelineSize) {
        return filterIterablePipelined(ForkJoinPool.commonPool(), iterable, filter, pipelineSize);
    }

    @Nonnull
    public static  AsyncIterable filterIterablePipelined(@Nonnull Executor executor,
                                                               @Nonnull AsyncIterable iterable,
                                                               @Nonnull final Function> filter,
                                                               int pipelineSize) {
        return mapConcatIterable(iterable,
                item -> filterToIterable(item, filter),
                pipelineSize);
    }

    /**
     * Converts a single item to an iterable of a different type.
     * @param item the source
     * @param func asynchronously map item to a new type
     * @param  the source type
     * @param  the destination type
     * @return a new {@code AsyncIterable} containing the result of func(item)
     */
    @Nonnull
    static  AsyncIterable mapToIterable(final T1 item,
                                                   @Nonnull final Function> func) {
        return new AsyncIterable() {
            @Nullable
            @Override
            public CloseableAsyncIterator iterator() {
                return new CloseableAsyncIterator() {
                    T2 result;
                    boolean used = false;
                    @Nullable
                    CompletableFuture nextFuture;

                    @Nullable
                    @Override
                    public CompletableFuture onHasNext() {
                        if (used) {
                            return AsyncUtil.READY_FALSE;
                        }
                        if (nextFuture == null) {
                            nextFuture = func.apply(item)
                                .thenApply(r -> {
                                    result = r;
                                    return true;
                                });
                        }
                        return nextFuture;
                    }

                    @Override
                    public boolean hasNext() {
                        return !used;
                    }

                    @Override
                    public T2 next() {
                        if (used) {
                            throw new NoSuchElementException();
                        }
                        if (nextFuture != null) {
                            nextFuture.join();
                        } else {
                            result = func.apply(item).join();
                        }
                        used = true;
                        return result;
                    }

                    @Override
                    public void close() {
                        if (nextFuture != null) {
                            nextFuture.cancel(false);
                            nextFuture = null;
                        }
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }

            @Override
            public CompletableFuture> asList() {
                return func.apply(item)
                    .thenApply(result -> Collections.singletonList(result));
            }
        };
    }

    /**
     * Maps an AsyncIterable using an asynchronous mapping function
     * @param iterable the source
     * @param func Maps items of iterable to a new value asynchronously
     * @param pipelineSize the number of map results to pipeline. As items comes back from iterable,
     * this will have up to this many func futures in waiting before waiting on them without advancing
     * the iterable.
     * @param  the source type
     * @param  the destination type
     * @return a new {@code AsyncIterable} with the results of applying func to each of the elements of iterable
     */
    @Nonnull
    public static  AsyncIterable mapIterablePipelined(@Nonnull AsyncIterable iterable,
                                                                 @Nonnull final Function> func,
                                                                 int pipelineSize) {
        return mapConcatIterable(iterable,
                item -> mapToIterable(item, func),
                pipelineSize);
    }

    /**
     * A holder for a (mutable) value.
     * @param  type of value to hold
     */
    public static class Holder {
        public T value;

        public Holder(T value) {
            this.value = value;
        }
    }

    /**
     * Reduce contents of iterator to single value.
     * @param iterator source of values
     * @param identity initial value for reduction
     * @param accumulator function that takes previous reduced value and computes new value combining iterator element
     * @param  the result type of the reduction
     * @param  the element type of the iterator
     * @return the reduced result
     */
    @Nullable
    public static  CompletableFuture reduce(@Nonnull AsyncIterator iterator, U identity,
                                                    BiFunction accumulator) {
        return reduce(ForkJoinPool.commonPool(), iterator, identity, accumulator);
    }

    @Nullable
    public static  CompletableFuture reduce(@Nonnull Executor executor,
                                                    @Nonnull AsyncIterator iterator, U identity,
                                                    BiFunction accumulator) {
        Holder holder = new Holder(identity);
        return whileTrue(() -> iterator.onHasNext().thenApply(hasNext -> {
            if (hasNext) {
                holder.value = accumulator.apply(holder.value, iterator.next());
            }
            return hasNext;
        }), executor).thenApply(vignore -> holder.value);
    }

    /**
     * Returns whether the given {@link CompletableFuture} has completed normally, i.e., not exceptionally.
     * If the future is yet to complete or if the future completed with an error, then this
     * will return false.
     * @param future the future to check for normal completion
     * @return whether the future has completed without exception
     */
    @API(API.Status.MAINTAINED)
    public static boolean isCompletedNormally(@Nonnull CompletableFuture future) {
        return future.isDone() && !future.isCompletedExceptionally();
    }

    /**
     * Creates a future that will be ready after the given delay. Creating the delayed future does
     * not use more than one thread for all of the futures together, and it is safe to create many delayed
     * futures at once. The guarantee given by this function is that the future will not be ready sooner
     * than the delay specified. It may, however, fire after the given delay (especially if there are multiple delayed
     * futures that are trying to fire at once).
     *
     * @param delay the time from now to delay execution
     * @param unit the time unit of the delay parameter
     * @return a {@link CompletableFuture} that will fire after the given delay
     */
    @API(API.Status.MAINTAINED)
    @Nonnull
    public static CompletableFuture delayedFuture(long delay, @Nonnull TimeUnit unit) {
        if (delay <= 0) {
            return AsyncUtil.DONE;
        }
        CompletableFuture future = new CompletableFuture<>();
        scheduledThreadPoolExecutor.schedule(() -> future.complete(null), delay, unit);
        return future;
    }

    /**
     * Get a completable future that will either complete within the specified deadline time or complete exceptionally
     * with {@link DeadlineExceededException}. If {@code deadlineTimeMillis} is set to {@link Long#MAX_VALUE}, then
     * no deadline is imposed on the future.
     *
     * @param deadlineTimeMillis the maximum time to wait for the asynchronous operation to complete, specified in milliseconds
     * @param supplier the {@link Supplier} of the asynchronous result
     * @param  the return type for the get operation
     * @return a future that will either complete with the result of the asynchronous get operation or
     * complete exceptionally if the deadline is exceeded
     */
    @API(API.Status.EXPERIMENTAL)
    public static  CompletableFuture getWithDeadline(long deadlineTimeMillis,
                                                           @Nonnull Supplier> supplier) {
        final CompletableFuture valueFuture = supplier.get();
        if (deadlineTimeMillis == Long.MAX_VALUE) {
            return valueFuture;
        }
        return CompletableFuture.anyOf(MoreAsyncUtil.delayedFuture(deadlineTimeMillis, TimeUnit.MILLISECONDS), valueFuture)
                .thenCompose(ignore -> {
                    if (!valueFuture.isDone()) {
                        // if the future is not ready then we exceeded the timeout
                        valueFuture.completeExceptionally(new DeadlineExceededException(deadlineTimeMillis));
                    }
                    return valueFuture;
                });
    }

    /**
     * Close the given iterator, or at least cancel it.
     * @param iterator iterator to close
     */
    @API(API.Status.MAINTAINED)
    public static void closeIterator(@Nonnull Iterator iterator) {
        if (iterator instanceof CloseableAsyncIterator) {
            ((CloseableAsyncIterator)iterator).close();
        } else if (iterator instanceof AsyncIterator) {
            ((AsyncIterator)iterator).cancel();
        } else if (iterator instanceof AutoCloseable) {
            try {
                ((AutoCloseable)iterator).close();
            } catch (RuntimeException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new RuntimeException(ex.getMessage(), ex);
            }
        }
    }

    /**
     * A {@code Boolean} function that is always true.
     * @param  the type of the (ignored) argument to the function
     */
    public static class AlwaysTrue implements Function {

        @Nonnull
        @Override
        public Boolean apply(T t) {
            return true;
        }
    }

    /**
     * This is a static class, and should not be instantiated.
     **/
    private MoreAsyncUtil() {}

    /**
     * Exception that will be thrown when the supplier in {@link #getWithDeadline(long, Supplier)} fails to
     * complete within the specified deadline time.
     */
    @SuppressWarnings("serial")
    public static class DeadlineExceededException extends LoggableException {
        private DeadlineExceededException(long deadlineTimeMillis) {
            super("deadline exceeded");
            addLogInfo("deadlineTimeMillis", deadlineTimeMillis);
        }
    }
}