001/*
002 * Copyright 2015-2024 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns;
012
013import java.io.IOException;
014import java.util.ArrayList;
015import java.util.Collection;
016import java.util.Collections;
017import java.util.List;
018import java.util.concurrent.ArrayBlockingQueue;
019import java.util.concurrent.BlockingQueue;
020import java.util.concurrent.CancellationException;
021import java.util.concurrent.ExecutionException;
022import java.util.concurrent.ExecutorService;
023import java.util.concurrent.Future;
024import java.util.concurrent.RejectedExecutionHandler;
025import java.util.concurrent.ThreadFactory;
026import java.util.concurrent.ThreadPoolExecutor;
027import java.util.concurrent.TimeUnit;
028import java.util.concurrent.TimeoutException;
029
030import org.minidns.util.CallbackRecipient;
031import org.minidns.util.ExceptionCallback;
032import org.minidns.util.MultipleIoException;
033import org.minidns.util.SuccessCallback;
034
035public abstract class MiniDnsFuture<V, E extends Exception> implements Future<V>, CallbackRecipient<V, E> {
036
037    private boolean cancelled;
038
039    protected V result;
040
041    protected E exception;
042
043    private SuccessCallback<V> successCallback;
044
045    private ExceptionCallback<E> exceptionCallback;
046
047    @Override
048    public synchronized boolean cancel(boolean mayInterruptIfRunning) {
049        if (isDone()) {
050            return false;
051        }
052
053        cancelled = true;
054
055        if (mayInterruptIfRunning) {
056            notifyAll();
057        }
058
059        return true;
060    }
061
062    @Override
063    public final synchronized boolean isCancelled() {
064        return cancelled;
065    }
066
067    @Override
068    public final synchronized boolean isDone() {
069        return hasResult() || hasException();
070    }
071
072    public final synchronized boolean hasResult() {
073        return result != null;
074    }
075
076    public final synchronized boolean hasException() {
077        return exception != null;
078    }
079
080    @Override
081    public CallbackRecipient<V, E> onSuccess(SuccessCallback<V> successCallback) {
082        this.successCallback = successCallback;
083        maybeInvokeCallbacks();
084        return this;
085    }
086
087    @Override
088    public CallbackRecipient<V, E> onError(ExceptionCallback<E> exceptionCallback) {
089        this.exceptionCallback = exceptionCallback;
090        maybeInvokeCallbacks();
091        return this;
092    }
093
094    private V getOrThrowExecutionException() throws ExecutionException {
095        assert result != null || exception != null || cancelled;
096        if (result != null) {
097            return result;
098        }
099        if (exception != null) {
100            throw new ExecutionException(exception);
101        }
102
103        assert cancelled;
104        throw new CancellationException();
105    }
106
107    @Override
108    public final synchronized V get() throws InterruptedException, ExecutionException {
109        while (result == null && exception == null && !cancelled) {
110            wait();
111        }
112
113        return getOrThrowExecutionException();
114    }
115
116    public final synchronized V getOrThrow() throws E {
117        while (result == null && exception == null && !cancelled) {
118            try {
119                wait();
120            } catch (InterruptedException e) {
121                throw new RuntimeException(e);
122            }
123        }
124
125        if (exception != null) {
126            throw exception;
127        }
128
129        if (cancelled) {
130            throw new CancellationException();
131        }
132
133        assert result != null;
134        return result;
135    }
136
137    @Override
138    public final synchronized V get(long timeout, TimeUnit unit)
139                    throws InterruptedException, ExecutionException, TimeoutException {
140        final long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
141        while (result != null && exception != null && !cancelled) {
142            final long waitTimeRemaining = deadline - System.currentTimeMillis();
143            if (waitTimeRemaining > 0) {
144                wait(waitTimeRemaining);
145            }
146        }
147
148        if (cancelled) {
149            throw new CancellationException();
150        }
151
152        if (result == null || exception == null) {
153            throw new TimeoutException();
154        }
155
156        return getOrThrowExecutionException();
157    }
158
159    private static final ExecutorService EXECUTOR_SERVICE;
160
161    static {
162        ThreadFactory threadFactory = new ThreadFactory() {
163            @Override
164            public Thread newThread(Runnable r) {
165                Thread thread = new Thread(r);
166                thread.setDaemon(true);
167                thread.setName("MiniDnsFuture Thread");
168                return thread;
169            }
170        };
171        BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(128);
172        RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
173            @Override
174            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
175                r.run();
176            }
177        };
178        int cores = Runtime.getRuntime().availableProcessors();
179        int maximumPoolSize = cores <= 4 ? 2 : cores;
180        ExecutorService executorService = new ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, blockingQueue, threadFactory,
181                rejectedExecutionHandler);
182
183        EXECUTOR_SERVICE = executorService;
184    }
185
186    @SuppressWarnings("FutureReturnValueIgnored")
187    protected final synchronized void maybeInvokeCallbacks() {
188        if (cancelled) {
189            return;
190        }
191
192        if (result != null && successCallback != null) {
193            EXECUTOR_SERVICE.submit(new Runnable() {
194                @Override
195                public void run() {
196                    successCallback.onSuccess(result);
197                }
198            });
199        } else if (exception != null && exceptionCallback != null) {
200            EXECUTOR_SERVICE.submit(new Runnable() {
201                @Override
202                public void run() {
203                    exceptionCallback.processException(exception);
204                }
205            });
206        }
207    }
208
209    public static class InternalMiniDnsFuture<V, E extends Exception> extends MiniDnsFuture<V, E> {
210        public final synchronized void setResult(V result) {
211            if (isDone()) {
212                return;
213            }
214
215            this.result = result;
216            this.notifyAll();
217
218            maybeInvokeCallbacks();
219        }
220
221        public final synchronized void setException(E exception) {
222            if (isDone()) {
223                return;
224            }
225
226            this.exception = exception;
227            this.notifyAll();
228
229            maybeInvokeCallbacks();
230        }
231    }
232
233    public static <V, E extends Exception> MiniDnsFuture<V, E> from(V result) {
234        InternalMiniDnsFuture<V, E> future = new InternalMiniDnsFuture<>();
235        future.setResult(result);
236        return future;
237    }
238
239    public static <V> MiniDnsFuture<V, IOException> anySuccessfulOf(Collection<MiniDnsFuture<V, IOException>> futures) {
240        return anySuccessfulOf(futures, exceptions -> MultipleIoException.toIOException(exceptions));
241    }
242
243    public interface ExceptionsWrapper<EI extends Exception, EO extends Exception> {
244        EO wrap(List<EI> exceptions);
245    }
246
247    public static <V, EI extends Exception, EO extends Exception> MiniDnsFuture<V, EO> anySuccessfulOf(
248            Collection<MiniDnsFuture<V, EI>> futures,
249            ExceptionsWrapper<EI, EO> exceptionsWrapper) {
250        InternalMiniDnsFuture<V, EO> returnedFuture = new InternalMiniDnsFuture<>();
251
252        final List<EI> exceptions = Collections.synchronizedList(new ArrayList<>(futures.size()));
253
254        for (MiniDnsFuture<V, EI> future : futures) {
255            future.onSuccess(new SuccessCallback<V>() {
256                @Override
257                public void onSuccess(V result) {
258                    // Cancel all futures. Yes, this includes the future which just returned the
259                    // result and futures which already failed with an exception, but then cancel
260                    // will be a no-op.
261                    for (MiniDnsFuture<V, EI> futureToCancel : futures) {
262                        futureToCancel.cancel(true);
263                    }
264                    returnedFuture.setResult(result);
265                }
266            });
267            future.onError(new ExceptionCallback<EI>() {
268                @Override
269                public void processException(EI exception) {
270                    exceptions.add(exception);
271                    // Signal the main future about the exceptions, but only if all sub-futures returned an exception.
272                    if (exceptions.size() == futures.size()) {
273                        EO returnedException = exceptionsWrapper.wrap(exceptions);
274                        returnedFuture.setException(returnedException);
275                    }
276                }
277            });
278        }
279
280        return returnedFuture;
281    }
282}