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

org.osgi.util.promise.PromiseFactory Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) Contributors to the Eclipse Foundation
 *
 * 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.
 *
 * SPDX-License-Identifier: Apache-2.0 
 *******************************************************************************/

package org.osgi.util.promise;

import static java.util.Objects.requireNonNull;
import static org.osgi.util.promise.PromiseImpl.uncaughtException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.RunnableScheduledFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collector;

import org.osgi.annotation.versioning.ConsumerType;
import org.osgi.util.promise.PromiseImpl.InlineCallback;

/**
 * Promise factory to create Deferred and Promise objects.
 * 

* Instances of this class can be used to create Deferred and Promise objects * which use the executors used to construct this object for any callback or * scheduled operation execution. * * @Immutable * @author $Id: 9e941a7a33ba3a70cdb8821972dd2b69abbcd3f0 $ * @since 1.1 */ @ConsumerType public class PromiseFactory { /** * Defines the options for a Promise factory. *

* The default options are no options unless the boolean system property * {@code org.osgi.util.promise.allowCurrentThread} is set to {@code false}. * When this is the case, the option {@link Option#CALLBACKS_EXECUTOR_THREAD} * is a default option. * * @since 1.2 */ public enum Option { /** * Run callbacks on an executor thread. If this option is not set, * callbacks added to a resolved Promise may be immediately called on * the caller's thread to avoid a thread context switch. */ CALLBACKS_EXECUTOR_THREAD } /** * The default factory which uses the default callback executor and default * scheduled executor. */ final static PromiseFactory defaultFactory = new PromiseFactory( null, null); /** * The executor to use for callbacks. If {@code null}, the default callback * executor is used. */ private final Executor callbackExecutor; /** * The executor to use for scheduled operations. If {@code null}, the * default scheduled executor is used. */ private final ScheduledExecutorService scheduledExecutor; private final boolean allowCurrentThread; /** * Create a new PromiseFactory with the specified callback executor. *

* The default scheduled executor and default options will be used. * * @param callbackExecutor The executor to use for callbacks. {@code null} * can be specified for the default callback executor. */ public PromiseFactory(Executor callbackExecutor) { this(callbackExecutor, null, defaultOptions()); } /** * Create a new PromiseFactory with the specified callback executor and * specified scheduled executor. *

* The default options will be used. * * @param callbackExecutor The executor to use for callbacks. {@code null} * can be specified for the default callback executor. * @param scheduledExecutor The scheduled executor for use for scheduled * operations. {@code null} can be specified for the default * scheduled executor. */ public PromiseFactory(Executor callbackExecutor, ScheduledExecutorService scheduledExecutor) { this(callbackExecutor, scheduledExecutor, defaultOptions()); } private static Option[] defaultOptions() { boolean allowCurrentThread = Boolean.parseBoolean( System.getProperty("org.osgi.util.promise.allowCurrentThread", Boolean.TRUE.toString())); return allowCurrentThread ? new Option[0] : new Option[] { Option.CALLBACKS_EXECUTOR_THREAD }; } /** * Create a new PromiseFactory with the specified callback executor, * specified scheduled executor, and specified options. * * @param callbackExecutor The executor to use for callbacks. {@code null} * can be specified for the default callback executor. * @param scheduledExecutor The scheduled executor for use for scheduled * operations. {@code null} can be specified for the default * scheduled executor. * @param options Options for PromiseFactory. * @since 1.2 */ public PromiseFactory(Executor callbackExecutor, ScheduledExecutorService scheduledExecutor, Option... options) { this.callbackExecutor = callbackExecutor; this.scheduledExecutor = scheduledExecutor; boolean callbacksExecutorOnly = false; for (Option option : options) { if (option == Option.CALLBACKS_EXECUTOR_THREAD) { callbacksExecutorOnly = true; continue; } requireNonNull(option); throw new AssertionError("unrecognized option: " + option); } this.allowCurrentThread = !callbacksExecutorOnly; } /** * Returns the executor to use for callbacks. * * @return The executor to use for callbacks. This will be the default * callback executor if {@code null} was specified for the callback * executor when this PromiseFactory was created. */ public Executor executor() { if (callbackExecutor == null) { return DefaultExecutors.callbackExecutor(); } return callbackExecutor; } /** * Returns the scheduled executor to use for scheduled operations. * * @return The scheduled executor to use for scheduled operations. This will * be the default scheduled executor if {@code null} was specified * for the scheduled executor when this PromiseFactory was created. */ public ScheduledExecutorService scheduledExecutor() { if (scheduledExecutor == null) { return DefaultExecutors.scheduledExecutor(); } return scheduledExecutor; } /** * Create a new Deferred with the callback executor and scheduled executor * of this PromiseFactory object. *

* Use this method instead of {@link Deferred#Deferred()} to create a new * {@link Deferred} whose associated Promise uses executors other than the * default executors. * * @param The value type associated with the returned Deferred. * @return A new {@link Deferred} with the callback and scheduled executors * of this PromiseFactory object */ public Deferred deferred() { return new Deferred<>(this); } /** * Returns a new Promise that has been resolved with the specified value. *

* The returned Promise uses the callback executor and scheduled executor of * this PromiseFactory object. *

* Use this method instead of {@link Promises#resolved(Object)} to create a * Promise which uses executors other than the default executors. * * @param The value type associated with the returned Promise. * @param value The value of the resolved Promise. * @return A new Promise that has been resolved with the specified value. */ public Promise resolved(T value) { return new ResolvedPromiseImpl<>(value, this); } /** * Returns a new Promise that has been resolved with the specified failure. *

* The returned Promise uses the callback executor and scheduled executor of * this PromiseFactory object. *

* Use this method instead of {@link Promises#failed(Throwable)} to create a * Promise which uses executors other than the default executors. * * @param The value type associated with the returned Promise. * @param failure The failure of the resolved Promise. Must not be * {@code null}. * @return A new Promise that has been resolved with the specified failure. */ public Promise failed(Throwable failure) { return new FailedPromiseImpl<>(failure, this); } /** * Returns a new Promise that will hold the result of the specified task. *

* The returned Promise uses the callback executor and scheduled executor of * this PromiseFactory object. *

* The specified task will be executed on the {@link #executor() callback * executor}. * * @param The value type associated with the returned Promise. * @param task The task whose result will be available from the returned * Promise. * @return A new Promise that will hold the result of the specified task. */ public Promise submit(Callable< ? extends T> task) { DeferredPromiseImpl promise = new DeferredPromiseImpl<>(this); Runnable submit = promise.new Submit(task); try { executor().execute(submit); } catch (Exception t) { promise.tryResolve(null, t); } return promise.orDone(); } /** * Returns a new Promise that is a latch on the resolution of the specified * Promises. *

* The returned Promise uses the callback executor and scheduled executor of * this PromiseFactory object. *

* The returned Promise acts as a gate and must be resolved after all of the * specified Promises are resolved. * * @param The value type of the List value associated with the returned * Promise. * @param The value type of the specified Promises. * @param promises The Promises which must be resolved before the returned * Promise must be resolved. Must not be {@code null} and all of * the elements in the collection must not be {@code null}. * @return A Promise that must be successfully resolved with a List of the * values in the order of the specified Promises if all the * specified Promises are successfully resolved. The List in the * returned Promise is the property of the caller and is modifiable. * The returned Promise must be resolved with a failure of * {@link FailedPromisesException} if any of the specified Promises * are resolved with a failure. The failure * {@link FailedPromisesException} must contain all of the specified * Promises which resolved with a failure. */ public Promise> all( Collection> promises) { if (promises.isEmpty()) { List value = new ArrayList<>(); return resolved(value); } /* make a copy and capture the ordering */ List> list = new ArrayList<>(promises); DeferredPromiseImpl> chained = new DeferredPromiseImpl<>(this); All all = new All<>(chained, list); for (Promise p : list) { p.onResolve(all); } return chained.orDone(); } /** * A callback used to resolve the specified Promise when the specified list * of Promises are resolved for the {@link PromiseFactory#all(Collection)} * method. * * @ThreadSafe */ private static final class All implements Runnable, InlineCallback { private final DeferredPromiseImpl> chained; private final List> promises; private final AtomicInteger promiseCount; All(DeferredPromiseImpl> chained, List> promises) { this.chained = requireNonNull(chained); this.promises = requireNonNull(promises); this.promiseCount = new AtomicInteger(promises.size()); } @Override public void run() { if (promiseCount.decrementAndGet() != 0) { return; } List value = new ArrayList<>(promises.size()); List> failed = new ArrayList<>(promises.size()); for (Promise p : promises) { PromiseImpl.result(p, (v, f) -> { if (f != null) { failed.add(p); } else { value.add(v); } }); } if (failed.isEmpty()) { chained.tryResolve(value, null); } else { Throwable cause; try { cause = failed.get(0).getFailure(); } catch (Throwable e) { cause = e; } chained.tryResolve(null, new FailedPromisesException(failed, cause)); } } } /** * Returns an Executor implementation that executes tasks immediately on the * thread calling the {@code Executor.execute} method. * * @return An Executor implementation that executes tasks immediately on the * thread calling the {@code Executor.execute} method. */ public static Executor inlineExecutor() { return new InlineExecutor(); } boolean allowCurrentThread() { return allowCurrentThread; } /** * An Executor implementation which executes the task immediately on the * thread calling the {@code Executor.execute} method. * * @Immutable */ private static final class InlineExecutor implements Executor { InlineExecutor() { } @Override public void execute(Runnable callback) { callback.run(); } } /** * Default executors for Promises. * * @Immutable */ private static final class DefaultExecutors implements ThreadFactory, RejectedExecutionHandler, Runnable { private static final DefaultExecutors callbacks; private static final ScheduledExecutor scheduledExecutor; private static final ThreadPoolExecutor callbackExecutor; static { callbacks = new DefaultExecutors(); scheduledExecutor = new ScheduledExecutor(2, callbacks); callbackExecutor = new ThreadPoolExecutor(0, 64, 60L, TimeUnit.SECONDS, new SynchronousQueue(), callbacks, callbacks); } static Executor callbackExecutor() { return callbackExecutor; } static ScheduledExecutorService scheduledExecutor() { return scheduledExecutor; } private final AtomicBoolean shutdownHookInstalled; private final ThreadFactory delegateThreadFactory; private DefaultExecutors() { shutdownHookInstalled = new AtomicBoolean(); delegateThreadFactory = Executors.defaultThreadFactory(); } /** * Executor threads should not prevent VM from exiting. */ @Override public Thread newThread(Runnable r) { if (shutdownHookInstalled.compareAndSet(false, true)) { Thread shutdownThread = delegateThreadFactory.newThread(this); shutdownThread.setName( "ExecutorShutdownHook," + shutdownThread.getName()); try { Runtime.getRuntime().addShutdownHook(shutdownThread); } catch (IllegalStateException e) { // VM is already shutting down... callbackExecutor.shutdown(); scheduledExecutor.shutdown(); } } Thread t = delegateThreadFactory.newThread(r); t.setName("PromiseFactory," + t.getName()); t.setDaemon(true); return t; } /** * Call the callback using the caller's thread because the thread pool * rejected the execution. */ @Override public void rejectedExecution(Runnable callback, ThreadPoolExecutor executor) { try { callback.run(); } catch (Throwable t) { uncaughtException(t); } } /** * Shutdown hook */ @Override public void run() { // limit new thread creation callbackExecutor.setMaximumPoolSize( Math.max(1, callbackExecutor.getPoolSize())); // Run all delayed callbacks now scheduledExecutor.shutdown(); BlockingQueue queue = scheduledExecutor.getQueue(); if (!queue.isEmpty()) { for (Object r : queue.toArray()) { if (r instanceof RunnableScheduledFuture< ? >) { RunnableScheduledFuture< ? > future = (RunnableScheduledFuture< ? >) r; if ((future.getDelay(TimeUnit.NANOSECONDS) > 0L) && queue.remove(future)) { future.run(); scheduledExecutor.afterExecute(future, null); } } } scheduledExecutor.shutdown(); } try { scheduledExecutor.awaitTermination(20, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // Shutdown callback executor callbackExecutor.shutdown(); try { callbackExecutor.awaitTermination(20, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * ScheduledThreadPoolExecutor for scheduled execution. * * @ThreadSafe */ private static final class ScheduledExecutor extends ScheduledThreadPoolExecutor { ScheduledExecutor(int corePoolSize, ThreadFactory threadFactory) { super(corePoolSize, threadFactory); } /** * Handle uncaught exceptions */ @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if ((t == null) && (r instanceof Future< ? >)) { boolean interrupted = Thread.interrupted(); try { ((Future< ? >) r).get(); } catch (CancellationException e) { // ignore } catch (InterruptedException e) { interrupted = true; } catch (ExecutionException e) { t = e.getCause(); } finally { if (interrupted) { // restore interrupt status Thread.currentThread().interrupt(); } } } if (t != null) { uncaughtException(t); } } } } /** * Returns a new Promise that will be resolved with the result of the * specified CompletionStage. *

* The returned Promise uses the callback executor and scheduled executor of * this PromiseFactory object. *

* If the specified CompletionStage is completed normally, the returned * Promise is resolved with the value of the specified CompletionStage. If * the specified CompletionStage is completed exceptionally, the returned * Promise is resolved with the exception of the specified CompletionStage. *

* After the returned Promise is resolved with the specified * CompletionStage, all registered {@link Promise#onResolve(Runnable) * callbacks} are called and any {@link Promise#then(Success, Failure) * chained} Promises are resolved. This may occur asynchronously to this * method. *

* Resolving the returned Promise happens-before any registered * callback is called. That is, in a registered callback, * {@link Promise#isDone()} must return {@code true} and * {@link Promise#getValue()} and {@link Promise#getFailure()} must not * block. * * @param The value type associated with the returned Promise. * @param with A CompletionStage whose result will be used to resolve the * returned Promise. Must not be {@code null}. * @return A new Promise that will be resolved with the result of the * specified CompletionStage. * @since 1.2 */ public Promise resolvedWith(CompletionStage< ? extends T> with) { DeferredPromiseImpl chained = new DeferredPromiseImpl<>(this); with.whenComplete(chained::tryResolve); return chained.orDone(); } /** * Returns a new Promise that will be resolved with the specified Promise. *

* The returned Promise uses the callback executor and scheduled executor of * this PromiseFactory object. *

* If the specified Promise is successfully resolved, the returned Promise * is resolved with the value of the specified Promise. If the specified * Promise is resolved with a failure, the returned Promise is resolved with * the failure of the specified Promise. *

* After the returned Promise is resolved with the specified Promise, all * registered {@link Promise#onResolve(Runnable) callbacks} are called and * any {@link Promise#then(Success, Failure) chained} Promises are resolved. * This may occur asynchronously to this method. *

* Resolving the returned Promise happens-before any registered * callback is called. That is, in a registered callback, * {@link Promise#isDone()} must return {@code true} and * {@link Promise#getValue()} and {@link Promise#getFailure()} must not * block. * * @param The value type associated with the returned Promise. * @param with A Promise whose value or failure must be used to resolve the * returned Promise. Must not be {@code null}. * @return A new Promise that is resolved with the specified Promise. * @since 1.2 */ public Promise resolvedWith(Promise< ? extends T> with) { DeferredPromiseImpl chained = new DeferredPromiseImpl<>(this); with.onResolve(chained.new Chain(with)); return chained.orDone(); } /** * Returns a {@code Collector} that accumulates the results of the input * Promises into a new {@link #all(Collection)} Promise. * * @param The value type of the List value result of the collected * {@link #all(Collection)} Promise. * @param The value type of the input Promises. * @return A {@code Collector} which accumulates the results of all the * input Promises into a new {@link #all(Collection)} Promise. * @since 1.2 */ public Collector, ? ,Promise>> toPromise() { return Collector.of(ArrayList>::new, List::add, PromiseFactory::combiner, this::all); } private static > C combiner(C t, C u) { t.addAll(u); return t; } }