added the async code from Vert.x and Netty
This commit is contained in:
parent
156af9f273
commit
19e0a1aaec
131 changed files with 12987 additions and 43 deletions
28
NOTICE.txt
28
NOTICE.txt
|
@ -1,4 +1,30 @@
|
||||||
The work in org.xbib.event.async is based upon the work in
|
The work in org.xbib.event.async is based upon io.vertx.core in
|
||||||
|
|
||||||
|
https://github.com/eclipse-vertx/vert.x
|
||||||
|
|
||||||
|
branched as of 26 Apr 2023.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
The work in org.xbib.event, org.xbib.event.thread, org.xbib.event.loop, org.xbib.event.util is based on netty
|
||||||
|
|
||||||
|
https://github.com/netty/netty/
|
||||||
|
|
||||||
|
branch 4.1 as of 26 Apr 2023.
|
||||||
|
|
||||||
|
Licence: Apache 2.0
|
||||||
|
|
||||||
|
|
||||||
|
The work in org.xbib.event.io is based upon the work in
|
||||||
|
|
||||||
https://github.com/javasync/RxIo
|
https://github.com/javasync/RxIo
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = event
|
name = event
|
||||||
version = 0.0.1
|
version = 0.0.2
|
||||||
|
|
||||||
org.gradle.warning.mode = ALL
|
org.gradle.warning.mode = ALL
|
||||||
|
|
|
@ -5,9 +5,9 @@ idea {
|
||||||
outputDir file('build/classes/java/main')
|
outputDir file('build/classes/java/main')
|
||||||
testOutputDir file('build/classes/java/test')
|
testOutputDir file('build/classes/java/test')
|
||||||
}
|
}
|
||||||
|
project {
|
||||||
|
jdkName = '17'
|
||||||
|
languageLevel = '17'
|
||||||
|
vcs = 'Git'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.convention.findPlugin(JavaPluginConvention)) {
|
|
||||||
//sourceSets.main.output.classesDirs = file("build/classes/java/main")
|
|
||||||
//sourceSets.test.output.classesDirs = file("build/classes/java/test")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
module org.xbib.event {
|
module org.xbib.event {
|
||||||
exports org.xbib.event;
|
exports org.xbib.event;
|
||||||
exports org.xbib.event.async;
|
|
||||||
exports org.xbib.event.bus;
|
exports org.xbib.event.bus;
|
||||||
exports org.xbib.event.clock;
|
exports org.xbib.event.clock;
|
||||||
exports org.xbib.event.persistence;
|
exports org.xbib.event.persistence;
|
||||||
exports org.xbib.event.queue;
|
exports org.xbib.event.queue;
|
||||||
exports org.xbib.event.syslog;
|
exports org.xbib.event.syslog;
|
||||||
exports org.xbib.event.yield;
|
exports org.xbib.event.yield;
|
||||||
|
exports org.xbib.event.io;
|
||||||
|
exports org.xbib.event.loop;
|
||||||
|
exports org.xbib.event.loop.selector;
|
||||||
requires org.xbib.datastructures.api;
|
requires org.xbib.datastructures.api;
|
||||||
requires org.xbib.datastructures.common;
|
requires org.xbib.datastructures.common;
|
||||||
requires org.xbib.datastructures.json.tiny;
|
requires org.xbib.datastructures.json.tiny;
|
||||||
|
|
43
src/main/java/org/xbib/event/AbstractFuture.java
Normal file
43
src/main/java/org/xbib/event/AbstractFuture.java
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract {@link Future} implementation which does not allow for cancellation.
|
||||||
|
*
|
||||||
|
* @param <V>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractFuture<V> implements Future<V> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get() throws InterruptedException, ExecutionException {
|
||||||
|
await();
|
||||||
|
|
||||||
|
Throwable cause = cause();
|
||||||
|
if (cause == null) {
|
||||||
|
return getNow();
|
||||||
|
}
|
||||||
|
if (cause instanceof CancellationException) {
|
||||||
|
throw (CancellationException) cause;
|
||||||
|
}
|
||||||
|
throw new ExecutionException(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
if (await(timeout, unit)) {
|
||||||
|
Throwable cause = cause();
|
||||||
|
if (cause == null) {
|
||||||
|
return getNow();
|
||||||
|
}
|
||||||
|
if (cause instanceof CancellationException) {
|
||||||
|
throw (CancellationException) cause;
|
||||||
|
}
|
||||||
|
throw new ExecutionException(cause);
|
||||||
|
}
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
}
|
135
src/main/java/org/xbib/event/CompleteFuture.java
Normal file
135
src/main/java/org/xbib/event/CompleteFuture.java
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import org.xbib.event.loop.EventExecutor;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A skeletal {@link Future} implementation which represents a {@link Future} which has been completed already.
|
||||||
|
*/
|
||||||
|
public abstract class CompleteFuture<V> extends AbstractFuture<V> {
|
||||||
|
|
||||||
|
private final EventExecutor executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param executor the {@link EventExecutor} associated with this future
|
||||||
|
*/
|
||||||
|
protected CompleteFuture(EventExecutor executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link EventExecutor} which is used by this {@link CompleteFuture}.
|
||||||
|
*/
|
||||||
|
protected EventExecutor executor() {
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
|
||||||
|
DefaultPromise.notifyListener(executor(), this, Objects.requireNonNull(listener, "listener"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners) {
|
||||||
|
for (GenericFutureListener<? extends Future<? super V>> l:
|
||||||
|
Objects.requireNonNull(listeners, "listeners")) {
|
||||||
|
|
||||||
|
if (l == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DefaultPromise.notifyListener(executor(), this, l);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener) {
|
||||||
|
// NOOP
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners) {
|
||||||
|
// NOOP
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> await() throws InterruptedException {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> sync() throws InterruptedException {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> syncUninterruptibly() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean await(long timeoutMillis) throws InterruptedException {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> awaitUninterruptibly() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitUninterruptibly(long timeoutMillis) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDone() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancellable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @param mayInterruptIfRunning this value has no effect in this implementation.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
71
src/main/java/org/xbib/event/DefaultFutureListeners.java
Normal file
71
src/main/java/org/xbib/event/DefaultFutureListeners.java
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
final class DefaultFutureListeners {
|
||||||
|
|
||||||
|
private GenericFutureListener<? extends Future<?>>[] listeners;
|
||||||
|
private int size;
|
||||||
|
private int progressiveSize; // the number of progressive listeners
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
DefaultFutureListeners(
|
||||||
|
GenericFutureListener<? extends Future<?>> first, GenericFutureListener<? extends Future<?>> second) {
|
||||||
|
listeners = new GenericFutureListener[2];
|
||||||
|
listeners[0] = first;
|
||||||
|
listeners[1] = second;
|
||||||
|
size = 2;
|
||||||
|
if (first instanceof GenericProgressiveFutureListener) {
|
||||||
|
progressiveSize ++;
|
||||||
|
}
|
||||||
|
if (second instanceof GenericProgressiveFutureListener) {
|
||||||
|
progressiveSize ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(GenericFutureListener<? extends Future<?>> l) {
|
||||||
|
GenericFutureListener<? extends Future<?>>[] listeners = this.listeners;
|
||||||
|
final int size = this.size;
|
||||||
|
if (size == listeners.length) {
|
||||||
|
this.listeners = listeners = Arrays.copyOf(listeners, size << 1);
|
||||||
|
}
|
||||||
|
listeners[size] = l;
|
||||||
|
this.size = size + 1;
|
||||||
|
|
||||||
|
if (l instanceof GenericProgressiveFutureListener) {
|
||||||
|
progressiveSize ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(GenericFutureListener<? extends Future<?>> l) {
|
||||||
|
final GenericFutureListener<? extends Future<?>>[] listeners = this.listeners;
|
||||||
|
int size = this.size;
|
||||||
|
for (int i = 0; i < size; i ++) {
|
||||||
|
if (listeners[i] == l) {
|
||||||
|
int listenersToMove = size - i - 1;
|
||||||
|
if (listenersToMove > 0) {
|
||||||
|
System.arraycopy(listeners, i + 1, listeners, i, listenersToMove);
|
||||||
|
}
|
||||||
|
listeners[-- size] = null;
|
||||||
|
this.size = size;
|
||||||
|
|
||||||
|
if (l instanceof GenericProgressiveFutureListener) {
|
||||||
|
progressiveSize --;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericFutureListener<? extends Future<?>>[] listeners() {
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int progressiveSize() {
|
||||||
|
return progressiveSize;
|
||||||
|
}
|
||||||
|
}
|
116
src/main/java/org/xbib/event/DefaultProgressivePromise.java
Normal file
116
src/main/java/org/xbib/event/DefaultProgressivePromise.java
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import org.xbib.event.loop.EventExecutor;
|
||||||
|
|
||||||
|
public class DefaultProgressivePromise<V> extends DefaultPromise<V> implements ProgressivePromise<V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* It is preferable to use {@link EventExecutor#newProgressivePromise()} to create a new progressive promise
|
||||||
|
*
|
||||||
|
* @param executor
|
||||||
|
* the {@link EventExecutor} which is used to notify the promise when it progresses or it is complete
|
||||||
|
*/
|
||||||
|
public DefaultProgressivePromise(EventExecutor executor) {
|
||||||
|
super(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DefaultProgressivePromise() { /* only for subclasses */ }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> setProgress(long progress, long total) {
|
||||||
|
if (total < 0) {
|
||||||
|
// total unknown
|
||||||
|
total = -1; // normalize
|
||||||
|
if (progress < 0) {
|
||||||
|
throw new IllegalArgumentException("progress must not be less than zero");
|
||||||
|
}
|
||||||
|
} else if (progress < 0 || progress > total) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"progress: " + progress + " (expected: 0 <= progress <= total (" + total + "))");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDone()) {
|
||||||
|
throw new IllegalStateException("complete already");
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyProgressiveListeners(progress, total);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryProgress(long progress, long total) {
|
||||||
|
if (total < 0) {
|
||||||
|
total = -1;
|
||||||
|
if (progress < 0 || isDone()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (progress < 0 || progress > total || isDone()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyProgressiveListeners(progress, total);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
|
||||||
|
super.addListener(listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners) {
|
||||||
|
super.addListeners(listeners);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener) {
|
||||||
|
super.removeListener(listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners) {
|
||||||
|
super.removeListeners(listeners);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> sync() throws InterruptedException {
|
||||||
|
super.sync();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> syncUninterruptibly() {
|
||||||
|
super.syncUninterruptibly();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> await() throws InterruptedException {
|
||||||
|
super.await();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> awaitUninterruptibly() {
|
||||||
|
super.awaitUninterruptibly();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> setSuccess(V result) {
|
||||||
|
super.setSuccess(result);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProgressivePromise<V> setFailure(Throwable cause) {
|
||||||
|
super.setFailure(cause);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
854
src/main/java/org/xbib/event/DefaultPromise.java
Normal file
854
src/main/java/org/xbib/event/DefaultPromise.java
Normal file
|
@ -0,0 +1,854 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import org.xbib.event.loop.BlockingOperationException;
|
||||||
|
import org.xbib.event.loop.EventExecutor;
|
||||||
|
import org.xbib.event.thread.InternalThreadLocalMap;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
|
public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultPromise.class.getName());
|
||||||
|
private static final Logger rejectedExecutionLogger =
|
||||||
|
Logger.getLogger(DefaultPromise.class.getName() + ".rejectedExecution");
|
||||||
|
private static final int MAX_LISTENER_STACK_DEPTH = Math.min(8,
|
||||||
|
Integer.getInteger("org.xbib.defaultPromise.maxListenerStackDepth", 8));
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private static final AtomicReferenceFieldUpdater<DefaultPromise, Object> RESULT_UPDATER =
|
||||||
|
AtomicReferenceFieldUpdater.newUpdater(DefaultPromise.class, Object.class, "result");
|
||||||
|
private static final Object SUCCESS = new Object();
|
||||||
|
private static final Object UNCANCELLABLE = new Object();
|
||||||
|
private static final CauseHolder CANCELLATION_CAUSE_HOLDER = new CauseHolder(
|
||||||
|
StacklessCancellationException.newInstance(DefaultPromise.class, "cancel(...)"));
|
||||||
|
private static final StackTraceElement[] CANCELLATION_STACK = CANCELLATION_CAUSE_HOLDER.cause.getStackTrace();
|
||||||
|
|
||||||
|
private volatile Object result;
|
||||||
|
private final EventExecutor executor;
|
||||||
|
/**
|
||||||
|
* One or more listeners. Can be a {@link GenericFutureListener} or a {@link DefaultFutureListeners}.
|
||||||
|
* If {@code null}, it means either 1) no listeners were added yet or 2) all listeners were notified.
|
||||||
|
*
|
||||||
|
* Threading - synchronized(this). We must support adding listeners when there is no EventExecutor.
|
||||||
|
*/
|
||||||
|
private Object listeners;
|
||||||
|
/**
|
||||||
|
* Threading - synchronized(this). We are required to hold the monitor to use Java's underlying wait()/notifyAll().
|
||||||
|
*/
|
||||||
|
private short waiters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Threading - synchronized(this). We must prevent concurrent notification and FIFO listener notification if the
|
||||||
|
* executor changes.
|
||||||
|
*/
|
||||||
|
private boolean notifyingListeners;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* It is preferable to use {@link EventExecutor#newPromise()} to create a new promise
|
||||||
|
*
|
||||||
|
* @param executor
|
||||||
|
* the {@link EventExecutor} which is used to notify the promise once it is complete.
|
||||||
|
* It is assumed this executor will protect against {@link StackOverflowError} exceptions.
|
||||||
|
* The executor may be used to avoid {@link StackOverflowError} by executing a {@link Runnable} if the stack
|
||||||
|
* depth exceeds a threshold.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public DefaultPromise(EventExecutor executor) {
|
||||||
|
this.executor = Objects.requireNonNull(executor, "executor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #executor()} for expectations of the executor.
|
||||||
|
*/
|
||||||
|
protected DefaultPromise() {
|
||||||
|
// only for subclasses
|
||||||
|
executor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> setSuccess(V result) {
|
||||||
|
if (setSuccess0(result)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("complete already: " + this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trySuccess(V result) {
|
||||||
|
return setSuccess0(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> setFailure(Throwable cause) {
|
||||||
|
if (setFailure0(cause)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("complete already: " + this, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryFailure(Throwable cause) {
|
||||||
|
return setFailure0(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setUncancellable() {
|
||||||
|
if (RESULT_UPDATER.compareAndSet(this, null, UNCANCELLABLE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Object result = this.result;
|
||||||
|
return !isDone0(result) || !isCancelled0(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuccess() {
|
||||||
|
Object result = this.result;
|
||||||
|
return result != null && result != UNCANCELLABLE && !(result instanceof CauseHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancellable() {
|
||||||
|
return result == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class LeanCancellationException extends CancellationException {
|
||||||
|
private static final long serialVersionUID = 2794674970981187807L;
|
||||||
|
|
||||||
|
// Suppress a warning since the method doesn't need synchronization
|
||||||
|
@Override
|
||||||
|
public Throwable fillInStackTrace() { // lgtm[java/non-sync-override]
|
||||||
|
setStackTrace(CANCELLATION_STACK);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return CancellationException.class.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable cause() {
|
||||||
|
return cause0(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Throwable cause0(Object result) {
|
||||||
|
if (!(result instanceof CauseHolder)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (result == CANCELLATION_CAUSE_HOLDER) {
|
||||||
|
CancellationException ce = new LeanCancellationException();
|
||||||
|
if (RESULT_UPDATER.compareAndSet(this, CANCELLATION_CAUSE_HOLDER, new CauseHolder(ce))) {
|
||||||
|
return ce;
|
||||||
|
}
|
||||||
|
result = this.result;
|
||||||
|
}
|
||||||
|
return ((CauseHolder) result).cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
|
||||||
|
Objects.requireNonNull(listener, "listener");
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
addListener0(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDone()) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners) {
|
||||||
|
Objects.requireNonNull(listeners, "listeners");
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
for (GenericFutureListener<? extends Future<? super V>> listener : listeners) {
|
||||||
|
if (listener == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
addListener0(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDone()) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> removeListener(final GenericFutureListener<? extends Future<? super V>> listener) {
|
||||||
|
Objects.requireNonNull(listener, "listener");
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
removeListener0(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> removeListeners(final GenericFutureListener<? extends Future<? super V>>... listeners) {
|
||||||
|
Objects.requireNonNull(listeners, "listeners");
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
for (GenericFutureListener<? extends Future<? super V>> listener : listeners) {
|
||||||
|
if (listener == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
removeListener0(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> await() throws InterruptedException {
|
||||||
|
if (isDone()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedException(toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDeadLock();
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
while (!isDone()) {
|
||||||
|
incWaiters();
|
||||||
|
try {
|
||||||
|
wait();
|
||||||
|
} finally {
|
||||||
|
decWaiters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> awaitUninterruptibly() {
|
||||||
|
if (isDone()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDeadLock();
|
||||||
|
|
||||||
|
boolean interrupted = false;
|
||||||
|
synchronized (this) {
|
||||||
|
while (!isDone()) {
|
||||||
|
incWaiters();
|
||||||
|
try {
|
||||||
|
wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Interrupted while waiting.
|
||||||
|
interrupted = true;
|
||||||
|
} finally {
|
||||||
|
decWaiters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
return await0(unit.toNanos(timeout), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean await(long timeoutMillis) throws InterruptedException {
|
||||||
|
return await0(MILLISECONDS.toNanos(timeoutMillis), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
|
||||||
|
try {
|
||||||
|
return await0(unit.toNanos(timeout), false);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Should not be raised at all.
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitUninterruptibly(long timeoutMillis) {
|
||||||
|
try {
|
||||||
|
return await0(MILLISECONDS.toNanos(timeoutMillis), false);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Should not be raised at all.
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public V getNow() {
|
||||||
|
Object result = this.result;
|
||||||
|
if (result instanceof CauseHolder || result == SUCCESS || result == UNCANCELLABLE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (V) result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public V get() throws InterruptedException, ExecutionException {
|
||||||
|
Object result = this.result;
|
||||||
|
if (!isDone0(result)) {
|
||||||
|
await();
|
||||||
|
result = this.result;
|
||||||
|
}
|
||||||
|
if (result == SUCCESS || result == UNCANCELLABLE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Throwable cause = cause0(result);
|
||||||
|
if (cause == null) {
|
||||||
|
return (V) result;
|
||||||
|
}
|
||||||
|
if (cause instanceof CancellationException) {
|
||||||
|
throw (CancellationException) cause;
|
||||||
|
}
|
||||||
|
throw new ExecutionException(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
Object result = this.result;
|
||||||
|
if (!isDone0(result)) {
|
||||||
|
if (!await(timeout, unit)) {
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
result = this.result;
|
||||||
|
}
|
||||||
|
if (result == SUCCESS || result == UNCANCELLABLE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Throwable cause = cause0(result);
|
||||||
|
if (cause == null) {
|
||||||
|
return (V) result;
|
||||||
|
}
|
||||||
|
if (cause instanceof CancellationException) {
|
||||||
|
throw (CancellationException) cause;
|
||||||
|
}
|
||||||
|
throw new ExecutionException(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @param mayInterruptIfRunning this value has no effect in this implementation.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||||
|
if (RESULT_UPDATER.compareAndSet(this, null, CANCELLATION_CAUSE_HOLDER)) {
|
||||||
|
if (checkNotifyWaiters()) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return isCancelled0(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDone() {
|
||||||
|
return isDone0(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> sync() throws InterruptedException {
|
||||||
|
await();
|
||||||
|
rethrowIfFailed();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<V> syncUninterruptibly() {
|
||||||
|
awaitUninterruptibly();
|
||||||
|
rethrowIfFailed();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toStringBuilder().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StringBuilder toStringBuilder() {
|
||||||
|
StringBuilder buf = new StringBuilder(64)
|
||||||
|
.append(getClass().getSimpleName())
|
||||||
|
.append('@')
|
||||||
|
.append(Integer.toHexString(hashCode()));
|
||||||
|
|
||||||
|
Object result = this.result;
|
||||||
|
if (result == SUCCESS) {
|
||||||
|
buf.append("(success)");
|
||||||
|
} else if (result == UNCANCELLABLE) {
|
||||||
|
buf.append("(uncancellable)");
|
||||||
|
} else if (result instanceof CauseHolder) {
|
||||||
|
buf.append("(failure: ")
|
||||||
|
.append(((CauseHolder) result).cause)
|
||||||
|
.append(')');
|
||||||
|
} else if (result != null) {
|
||||||
|
buf.append("(success: ")
|
||||||
|
.append(result)
|
||||||
|
.append(')');
|
||||||
|
} else {
|
||||||
|
buf.append("(incomplete)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the executor used to notify listeners when this promise is complete.
|
||||||
|
* <p>
|
||||||
|
* It is assumed this executor will protect against {@link StackOverflowError} exceptions.
|
||||||
|
* The executor may be used to avoid {@link StackOverflowError} by executing a {@link Runnable} if the stack
|
||||||
|
* depth exceeds a threshold.
|
||||||
|
* @return The executor used to notify listeners when this promise is complete.
|
||||||
|
*/
|
||||||
|
protected EventExecutor executor() {
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkDeadLock() {
|
||||||
|
EventExecutor e = executor();
|
||||||
|
if (e != null && e.inEventLoop()) {
|
||||||
|
throw new BlockingOperationException(toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify a listener that a future has completed.
|
||||||
|
* <p>
|
||||||
|
* This method has a fixed depth of {@link #MAX_LISTENER_STACK_DEPTH} that will limit recursion to prevent
|
||||||
|
* {@link StackOverflowError} and will stop notifying listeners added after this threshold is exceeded.
|
||||||
|
* @param eventExecutor the executor to use to notify the listener {@code listener}.
|
||||||
|
* @param future the future that is complete.
|
||||||
|
* @param listener the listener to notify.
|
||||||
|
*/
|
||||||
|
protected static void notifyListener(
|
||||||
|
EventExecutor eventExecutor, final Future<?> future, final GenericFutureListener<?> listener) {
|
||||||
|
notifyListenerWithStackOverFlowProtection(
|
||||||
|
Objects.requireNonNull(eventExecutor, "eventExecutor"),
|
||||||
|
Objects.requireNonNull(future, "future"),
|
||||||
|
Objects.requireNonNull(listener, "listener"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListeners() {
|
||||||
|
EventExecutor executor = executor();
|
||||||
|
if (executor.inEventLoop()) {
|
||||||
|
final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
|
||||||
|
final int stackDepth = threadLocals.futureListenerStackDepth();
|
||||||
|
if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
|
||||||
|
threadLocals.setFutureListenerStackDepth(stackDepth + 1);
|
||||||
|
try {
|
||||||
|
notifyListenersNow();
|
||||||
|
} finally {
|
||||||
|
threadLocals.setFutureListenerStackDepth(stackDepth);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
safeExecute(executor, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
notifyListenersNow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logic in this method should be identical to {@link #notifyListeners()} but
|
||||||
|
* cannot share code because the listener(s) cannot be cached for an instance of {@link DefaultPromise} since the
|
||||||
|
* listener(s) may be changed and is protected by a synchronized operation.
|
||||||
|
*/
|
||||||
|
private static void notifyListenerWithStackOverFlowProtection(final EventExecutor executor,
|
||||||
|
final Future<?> future,
|
||||||
|
final GenericFutureListener<?> listener) {
|
||||||
|
if (executor.inEventLoop()) {
|
||||||
|
final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
|
||||||
|
final int stackDepth = threadLocals.futureListenerStackDepth();
|
||||||
|
if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
|
||||||
|
threadLocals.setFutureListenerStackDepth(stackDepth + 1);
|
||||||
|
try {
|
||||||
|
notifyListener0(future, listener);
|
||||||
|
} finally {
|
||||||
|
threadLocals.setFutureListenerStackDepth(stackDepth);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
safeExecute(executor, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
notifyListener0(future, listener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListenersNow() {
|
||||||
|
Object listeners;
|
||||||
|
synchronized (this) {
|
||||||
|
// Only proceed if there are listeners to notify and we are not already notifying listeners.
|
||||||
|
if (notifyingListeners || this.listeners == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notifyingListeners = true;
|
||||||
|
listeners = this.listeners;
|
||||||
|
this.listeners = null;
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
if (listeners instanceof DefaultFutureListeners) {
|
||||||
|
notifyListeners0((DefaultFutureListeners) listeners);
|
||||||
|
} else {
|
||||||
|
notifyListener0(this, (GenericFutureListener<?>) listeners);
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (this.listeners == null) {
|
||||||
|
// Nothing can throw from within this method, so setting notifyingListeners back to false does not
|
||||||
|
// need to be in a finally block.
|
||||||
|
notifyingListeners = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listeners = this.listeners;
|
||||||
|
this.listeners = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListeners0(DefaultFutureListeners listeners) {
|
||||||
|
GenericFutureListener<?>[] a = listeners.listeners();
|
||||||
|
int size = listeners.size();
|
||||||
|
for (int i = 0; i < size; i ++) {
|
||||||
|
notifyListener0(this, a[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
private static void notifyListener0(Future future, GenericFutureListener l) {
|
||||||
|
try {
|
||||||
|
l.operationComplete(future);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (logger.isLoggable(Level.WARNING)) {
|
||||||
|
logger.log(Level.WARNING, "An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addListener0(GenericFutureListener<? extends Future<? super V>> listener) {
|
||||||
|
if (listeners == null) {
|
||||||
|
listeners = listener;
|
||||||
|
} else if (listeners instanceof DefaultFutureListeners) {
|
||||||
|
((DefaultFutureListeners) listeners).add(listener);
|
||||||
|
} else {
|
||||||
|
listeners = new DefaultFutureListeners((GenericFutureListener<?>) listeners, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeListener0(GenericFutureListener<? extends Future<? super V>> listener) {
|
||||||
|
if (listeners instanceof DefaultFutureListeners) {
|
||||||
|
((DefaultFutureListeners) listeners).remove(listener);
|
||||||
|
} else if (listeners == listener) {
|
||||||
|
listeners = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setSuccess0(V result) {
|
||||||
|
return setValue0(result == null ? SUCCESS : result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setFailure0(Throwable cause) {
|
||||||
|
return setValue0(new CauseHolder(Objects.requireNonNull(cause, "cause")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setValue0(Object objResult) {
|
||||||
|
if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
|
||||||
|
RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
|
||||||
|
if (checkNotifyWaiters()) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are any waiters and if so notify these.
|
||||||
|
* @return {@code true} if there are any listeners attached to the promise, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
private synchronized boolean checkNotifyWaiters() {
|
||||||
|
if (waiters > 0) {
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
return listeners != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incWaiters() {
|
||||||
|
if (waiters == Short.MAX_VALUE) {
|
||||||
|
throw new IllegalStateException("too many waiters: " + this);
|
||||||
|
}
|
||||||
|
++waiters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decWaiters() {
|
||||||
|
--waiters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rethrowIfFailed() {
|
||||||
|
Throwable cause = cause();
|
||||||
|
if (cause == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new RuntimeException(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean await0(long timeoutNanos, boolean interruptable) throws InterruptedException {
|
||||||
|
if (isDone()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeoutNanos <= 0) {
|
||||||
|
return isDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interruptable && Thread.interrupted()) {
|
||||||
|
throw new InterruptedException(toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDeadLock();
|
||||||
|
|
||||||
|
long startTime = System.nanoTime();
|
||||||
|
long waitTime = timeoutNanos;
|
||||||
|
boolean interrupted = false;
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (isDone()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
incWaiters();
|
||||||
|
try {
|
||||||
|
wait(waitTime / 1000000, (int) (waitTime % 1000000));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
if (interruptable) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
interrupted = true;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
decWaiters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isDone()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
waitTime = timeoutNanos - (System.nanoTime() - startTime);
|
||||||
|
if (waitTime <= 0) {
|
||||||
|
return isDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify all progressive listeners.
|
||||||
|
* <p>
|
||||||
|
* No attempt is made to ensure notification order if multiple calls are made to this method before
|
||||||
|
* the original invocation completes.
|
||||||
|
* <p>
|
||||||
|
* This will do an iteration over all listeners to get all of type {@link GenericProgressiveFutureListener}s.
|
||||||
|
* @param progress the new progress.
|
||||||
|
* @param total the total progress.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void notifyProgressiveListeners(final long progress, final long total) {
|
||||||
|
final Object listeners = progressiveListeners();
|
||||||
|
if (listeners == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProgressiveFuture<V> self = (ProgressiveFuture<V>) this;
|
||||||
|
|
||||||
|
EventExecutor executor = executor();
|
||||||
|
if (executor.inEventLoop()) {
|
||||||
|
if (listeners instanceof GenericProgressiveFutureListener[]) {
|
||||||
|
notifyProgressiveListeners0(
|
||||||
|
self, (GenericProgressiveFutureListener<?>[]) listeners, progress, total);
|
||||||
|
} else {
|
||||||
|
notifyProgressiveListener0(
|
||||||
|
self, (GenericProgressiveFutureListener<ProgressiveFuture<V>>) listeners, progress, total);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (listeners instanceof GenericProgressiveFutureListener[]) {
|
||||||
|
final GenericProgressiveFutureListener<?>[] array =
|
||||||
|
(GenericProgressiveFutureListener<?>[]) listeners;
|
||||||
|
safeExecute(executor, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
notifyProgressiveListeners0(self, array, progress, total);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
final GenericProgressiveFutureListener<ProgressiveFuture<V>> l =
|
||||||
|
(GenericProgressiveFutureListener<ProgressiveFuture<V>>) listeners;
|
||||||
|
safeExecute(executor, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
notifyProgressiveListener0(self, l, progress, total);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link GenericProgressiveFutureListener}, an array of {@link GenericProgressiveFutureListener}, or
|
||||||
|
* {@code null}.
|
||||||
|
*/
|
||||||
|
private synchronized Object progressiveListeners() {
|
||||||
|
Object listeners = this.listeners;
|
||||||
|
if (listeners == null) {
|
||||||
|
// No listeners added
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listeners instanceof DefaultFutureListeners) {
|
||||||
|
// Copy DefaultFutureListeners into an array of listeners.
|
||||||
|
DefaultFutureListeners dfl = (DefaultFutureListeners) listeners;
|
||||||
|
int progressiveSize = dfl.progressiveSize();
|
||||||
|
switch (progressiveSize) {
|
||||||
|
case 0:
|
||||||
|
return null;
|
||||||
|
case 1:
|
||||||
|
for (GenericFutureListener<?> l: dfl.listeners()) {
|
||||||
|
if (l instanceof GenericProgressiveFutureListener) {
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
GenericFutureListener<?>[] array = dfl.listeners();
|
||||||
|
GenericProgressiveFutureListener<?>[] copy = new GenericProgressiveFutureListener[progressiveSize];
|
||||||
|
for (int i = 0, j = 0; j < progressiveSize; i ++) {
|
||||||
|
GenericFutureListener<?> l = array[i];
|
||||||
|
if (l instanceof GenericProgressiveFutureListener) {
|
||||||
|
copy[j ++] = (GenericProgressiveFutureListener<?>) l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
} else if (listeners instanceof GenericProgressiveFutureListener) {
|
||||||
|
return listeners;
|
||||||
|
} else {
|
||||||
|
// Only one listener was added and it's not a progressive listener.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void notifyProgressiveListeners0(
|
||||||
|
ProgressiveFuture<?> future, GenericProgressiveFutureListener<?>[] listeners, long progress, long total) {
|
||||||
|
for (GenericProgressiveFutureListener<?> l: listeners) {
|
||||||
|
if (l == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
notifyProgressiveListener0(future, l, progress, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
private static void notifyProgressiveListener0(
|
||||||
|
ProgressiveFuture future, GenericProgressiveFutureListener l, long progress, long total) {
|
||||||
|
try {
|
||||||
|
l.operationProgressed(future, progress, total);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (logger.isLoggable(Level.WARNING)) {
|
||||||
|
logger.log(Level.WARNING, "An exception was thrown by " + l.getClass().getName() + ".operationProgressed()", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCancelled0(Object result) {
|
||||||
|
return result instanceof CauseHolder && ((CauseHolder) result).cause instanceof CancellationException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDone0(Object result) {
|
||||||
|
return result != null && result != UNCANCELLABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CauseHolder {
|
||||||
|
final Throwable cause;
|
||||||
|
CauseHolder(Throwable cause) {
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void safeExecute(EventExecutor executor, Runnable task) {
|
||||||
|
try {
|
||||||
|
executor.execute(task);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
rejectedExecutionLogger.log(Level.SEVERE, "Failed to submit a listener notification task. Event loop shut down?", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StacklessCancellationException extends CancellationException {
|
||||||
|
|
||||||
|
private StacklessCancellationException() { }
|
||||||
|
|
||||||
|
// Override fillInStackTrace() so we not populate the backtrace via a native call and so leak the
|
||||||
|
// Classloader.
|
||||||
|
@Override
|
||||||
|
public Throwable fillInStackTrace() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
static StacklessCancellationException newInstance(Class<?> clazz, String method) {
|
||||||
|
return unknownStackTrace(new StacklessCancellationException(), clazz, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link StackTraceElement} for the given {@link Throwable}, using the {@link Class} and method name.
|
||||||
|
*/
|
||||||
|
private static <T extends Throwable> T unknownStackTrace(T cause, Class<?> clazz, String method) {
|
||||||
|
cause.setStackTrace(new StackTraceElement[] { new StackTraceElement(clazz.getName(), method, null, -1)});
|
||||||
|
return cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
package org.xbib.event;
|
|
||||||
|
|
||||||
public class EventService {
|
|
||||||
|
|
||||||
public EventService() {
|
|
||||||
}
|
|
||||||
}
|
|
51
src/main/java/org/xbib/event/FailedFuture.java
Normal file
51
src/main/java/org/xbib/event/FailedFuture.java
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import org.xbib.event.loop.EventExecutor;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CompleteFuture} which is failed already. It is
|
||||||
|
* recommended to use {@link EventExecutor#newFailedFuture(Throwable)}
|
||||||
|
* instead of calling the constructor of this future.
|
||||||
|
*/
|
||||||
|
public final class FailedFuture<V> extends CompleteFuture<V> {
|
||||||
|
|
||||||
|
private final Throwable cause;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param executor the {@link EventExecutor} associated with this future
|
||||||
|
* @param cause the cause of failure
|
||||||
|
*/
|
||||||
|
public FailedFuture(EventExecutor executor, Throwable cause) {
|
||||||
|
super(executor);
|
||||||
|
this.cause = Objects.requireNonNull(cause, "cause");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable cause() {
|
||||||
|
return cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> sync() {
|
||||||
|
throw new RuntimeException(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<V> syncUninterruptibly() {
|
||||||
|
throw new RuntimeException(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V getNow() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
package org.xbib.event;
|
|
||||||
|
|
||||||
public class FileFollowEvent {
|
|
||||||
|
|
||||||
public FileFollowEvent() {
|
|
||||||
}
|
|
||||||
}
|
|
39
src/main/java/org/xbib/event/Future.java
Normal file
39
src/main/java/org/xbib/event/Future.java
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public interface Future<V> extends java.util.concurrent.Future<V> {
|
||||||
|
boolean isSuccess();
|
||||||
|
|
||||||
|
boolean isCancellable();
|
||||||
|
|
||||||
|
Throwable cause();
|
||||||
|
|
||||||
|
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> var1);
|
||||||
|
|
||||||
|
Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... var1);
|
||||||
|
|
||||||
|
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> var1);
|
||||||
|
|
||||||
|
Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... var1);
|
||||||
|
|
||||||
|
Future<V> sync() throws InterruptedException;
|
||||||
|
|
||||||
|
Future<V> syncUninterruptibly();
|
||||||
|
|
||||||
|
Future<V> await() throws InterruptedException;
|
||||||
|
|
||||||
|
Future<V> awaitUninterruptibly();
|
||||||
|
|
||||||
|
boolean await(long var1, TimeUnit var3) throws InterruptedException;
|
||||||
|
|
||||||
|
boolean await(long var1) throws InterruptedException;
|
||||||
|
|
||||||
|
boolean awaitUninterruptibly(long var1, TimeUnit var3);
|
||||||
|
|
||||||
|
boolean awaitUninterruptibly(long var1);
|
||||||
|
|
||||||
|
V getNow();
|
||||||
|
|
||||||
|
boolean cancel(boolean var1);
|
||||||
|
}
|
4
src/main/java/org/xbib/event/FutureListener.java
Normal file
4
src/main/java/org/xbib/event/FutureListener.java
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
public interface FutureListener<V> extends GenericFutureListener<Future<V>> {
|
||||||
|
}
|
7
src/main/java/org/xbib/event/GenericFutureListener.java
Normal file
7
src/main/java/org/xbib/event/GenericFutureListener.java
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import java.util.EventListener;
|
||||||
|
|
||||||
|
public interface GenericFutureListener<F extends Future<?>> extends EventListener {
|
||||||
|
void operationComplete(F future) throws Exception;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
public interface GenericProgressiveFutureListener<F extends ProgressiveFuture<?>> extends GenericFutureListener<F> {
|
||||||
|
/**
|
||||||
|
* Invoked when the operation has progressed.
|
||||||
|
*
|
||||||
|
* @param progress the progress of the operation so far (cumulative)
|
||||||
|
* @param total the number that signifies the end of the operation when {@code progress} reaches at it.
|
||||||
|
* {@code -1} if the end of operation is unknown.
|
||||||
|
*/
|
||||||
|
void operationProgressed(F future, long progress, long total) throws Exception;
|
||||||
|
}
|
31
src/main/java/org/xbib/event/ProgressiveFuture.java
Normal file
31
src/main/java/org/xbib/event/ProgressiveFuture.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Future} which is used to indicate the progress of an operation.
|
||||||
|
*/
|
||||||
|
public interface ProgressiveFuture<V> extends Future<V> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressiveFuture<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressiveFuture<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressiveFuture<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressiveFuture<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressiveFuture<V> sync() throws InterruptedException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressiveFuture<V> syncUninterruptibly();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressiveFuture<V> await() throws InterruptedException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressiveFuture<V> awaitUninterruptibly();
|
||||||
|
}
|
50
src/main/java/org/xbib/event/ProgressivePromise.java
Normal file
50
src/main/java/org/xbib/event/ProgressivePromise.java
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special {@link ProgressiveFuture} which is writable.
|
||||||
|
*/
|
||||||
|
public interface ProgressivePromise<V> extends Promise<V>, ProgressiveFuture<V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current progress of the operation and notifies the listeners that implement
|
||||||
|
* {@link GenericProgressiveFutureListener}.
|
||||||
|
*/
|
||||||
|
ProgressivePromise<V> setProgress(long progress, long total);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to set the current progress of the operation and notifies the listeners that implement
|
||||||
|
* {@link GenericProgressiveFutureListener}. If the operation is already complete or the progress is out of range,
|
||||||
|
* this method does nothing but returning {@code false}.
|
||||||
|
*/
|
||||||
|
boolean tryProgress(long progress, long total);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> setSuccess(V result);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> setFailure(Throwable cause);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> await() throws InterruptedException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> awaitUninterruptibly();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> sync() throws InterruptedException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ProgressivePromise<V> syncUninterruptibly();
|
||||||
|
}
|
29
src/main/java/org/xbib/event/Promise.java
Normal file
29
src/main/java/org/xbib/event/Promise.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
public interface Promise<V> extends Future<V> {
|
||||||
|
Promise<V> setSuccess(V value);
|
||||||
|
|
||||||
|
boolean trySuccess(V value);
|
||||||
|
|
||||||
|
Promise<V> setFailure(Throwable value);
|
||||||
|
|
||||||
|
boolean tryFailure(Throwable value);
|
||||||
|
|
||||||
|
boolean setUncancellable();
|
||||||
|
|
||||||
|
Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> value);
|
||||||
|
|
||||||
|
Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... value);
|
||||||
|
|
||||||
|
Promise<V> removeListener(GenericFutureListener<? extends Future<? super V>> value);
|
||||||
|
|
||||||
|
Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... value);
|
||||||
|
|
||||||
|
Promise<V> await() throws InterruptedException;
|
||||||
|
|
||||||
|
Promise<V> awaitUninterruptibly();
|
||||||
|
|
||||||
|
Promise<V> sync() throws InterruptedException;
|
||||||
|
|
||||||
|
Promise<V> syncUninterruptibly();
|
||||||
|
}
|
175
src/main/java/org/xbib/event/PromiseTask.java
Normal file
175
src/main/java/org/xbib/event/PromiseTask.java
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import org.xbib.event.loop.EventExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.RunnableFuture;
|
||||||
|
|
||||||
|
public class PromiseTask<V> extends DefaultPromise<V> implements RunnableFuture<V> {
|
||||||
|
|
||||||
|
private static final class RunnableAdapter<T> implements Callable<T> {
|
||||||
|
final Runnable task;
|
||||||
|
final T result;
|
||||||
|
|
||||||
|
RunnableAdapter(Runnable task, T result) {
|
||||||
|
this.task = task;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T call() {
|
||||||
|
task.run();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Callable(task: " + task + ", result: " + result + ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Runnable COMPLETED = new SentinelRunnable("COMPLETED");
|
||||||
|
private static final Runnable CANCELLED = new SentinelRunnable("CANCELLED");
|
||||||
|
private static final Runnable FAILED = new SentinelRunnable("FAILED");
|
||||||
|
|
||||||
|
private static class SentinelRunnable implements Runnable {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
SentinelRunnable(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() { } // no-op
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strictly of type Callable<V> or Runnable
|
||||||
|
private Object task;
|
||||||
|
|
||||||
|
public PromiseTask(EventExecutor executor, Runnable runnable, V result) {
|
||||||
|
super(executor);
|
||||||
|
task = result == null ? runnable : new RunnableAdapter<V>(runnable, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PromiseTask(EventExecutor executor, Runnable runnable) {
|
||||||
|
super(executor);
|
||||||
|
task = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PromiseTask(EventExecutor executor, Callable<V> callable) {
|
||||||
|
super(executor);
|
||||||
|
task = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int hashCode() {
|
||||||
|
return System.identityHashCode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean equals(Object obj) {
|
||||||
|
return this == obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
V runTask() throws Throwable {
|
||||||
|
final Object task = this.task;
|
||||||
|
if (task instanceof Callable) {
|
||||||
|
return ((Callable<V>) task).call();
|
||||||
|
}
|
||||||
|
((Runnable) task).run();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
if (setUncancellableInternal()) {
|
||||||
|
V result = runTask();
|
||||||
|
setSuccessInternal(result);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
setFailureInternal(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean clearTaskAfterCompletion(boolean done, Runnable result) {
|
||||||
|
if (done) {
|
||||||
|
// The only time where it might be possible for the sentinel task
|
||||||
|
// to be called is in the case of a periodic ScheduledFutureTask,
|
||||||
|
// in which case it's a benign race with cancellation and the (null)
|
||||||
|
// return value is not used.
|
||||||
|
task = result;
|
||||||
|
}
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Promise<V> setFailure(Throwable cause) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Promise<V> setFailureInternal(Throwable cause) {
|
||||||
|
super.setFailure(cause);
|
||||||
|
clearTaskAfterCompletion(true, FAILED);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean tryFailure(Throwable cause) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean tryFailureInternal(Throwable cause) {
|
||||||
|
return clearTaskAfterCompletion(super.tryFailure(cause), FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Promise<V> setSuccess(V result) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Promise<V> setSuccessInternal(V result) {
|
||||||
|
super.setSuccess(result);
|
||||||
|
clearTaskAfterCompletion(true, COMPLETED);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean trySuccess(V result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean trySuccessInternal(V result) {
|
||||||
|
return clearTaskAfterCompletion(super.trySuccess(result), COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean setUncancellable() {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean setUncancellableInternal() {
|
||||||
|
return super.setUncancellable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||||
|
return clearTaskAfterCompletion(super.cancel(mayInterruptIfRunning), CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StringBuilder toStringBuilder() {
|
||||||
|
StringBuilder buf = super.toStringBuilder();
|
||||||
|
buf.setCharAt(buf.length() - 1, ',');
|
||||||
|
|
||||||
|
return buf.append(" task: ")
|
||||||
|
.append(task)
|
||||||
|
.append(')');
|
||||||
|
}
|
||||||
|
}
|
8
src/main/java/org/xbib/event/ScheduledFuture.java
Normal file
8
src/main/java/org/xbib/event/ScheduledFuture.java
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a scheduled asynchronous operation.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("ClassNameSameAsAncestorName")
|
||||||
|
public interface ScheduledFuture<V> extends Future<V>, java.util.concurrent.ScheduledFuture<V> {
|
||||||
|
}
|
223
src/main/java/org/xbib/event/ScheduledFutureTask.java
Normal file
223
src/main/java/org/xbib/event/ScheduledFutureTask.java
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import org.xbib.event.loop.AbstractScheduledEventExecutor;
|
||||||
|
import org.xbib.event.loop.EventExecutor;
|
||||||
|
import org.xbib.event.util.DefaultPriorityQueue;
|
||||||
|
import org.xbib.event.util.PriorityQueueNode;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Delayed;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
|
||||||
|
public final class ScheduledFutureTask<V> extends PromiseTask<V> implements ScheduledFuture<V>, PriorityQueueNode {
|
||||||
|
private static final long START_TIME = System.nanoTime();
|
||||||
|
|
||||||
|
public static long nanoTime() {
|
||||||
|
return System.nanoTime() - START_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long deadlineNanos(long delay) {
|
||||||
|
long deadlineNanos = nanoTime() + delay;
|
||||||
|
// Guard against overflow
|
||||||
|
return deadlineNanos < 0 ? Long.MAX_VALUE : deadlineNanos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long initialNanoTime() {
|
||||||
|
return START_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set once when added to priority queue
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
private long deadlineNanos;
|
||||||
|
/* 0 - no repeat, >0 - repeat at fixed rate, <0 - repeat with fixed delay */
|
||||||
|
private final long periodNanos;
|
||||||
|
|
||||||
|
private int queueIndex = INDEX_NOT_IN_QUEUE;
|
||||||
|
|
||||||
|
public ScheduledFutureTask(AbstractScheduledEventExecutor executor,
|
||||||
|
Runnable runnable, long nanoTime) {
|
||||||
|
|
||||||
|
super(executor, runnable);
|
||||||
|
deadlineNanos = nanoTime;
|
||||||
|
periodNanos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScheduledFutureTask(AbstractScheduledEventExecutor executor,
|
||||||
|
Runnable runnable, long nanoTime, long period) {
|
||||||
|
|
||||||
|
super(executor, runnable);
|
||||||
|
deadlineNanos = nanoTime;
|
||||||
|
periodNanos = validatePeriod(period);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScheduledFutureTask(AbstractScheduledEventExecutor executor,
|
||||||
|
Callable<V> callable, long nanoTime, long period) {
|
||||||
|
|
||||||
|
super(executor, callable);
|
||||||
|
deadlineNanos = nanoTime;
|
||||||
|
periodNanos = validatePeriod(period);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScheduledFutureTask(AbstractScheduledEventExecutor executor,
|
||||||
|
Callable<V> callable, long nanoTime) {
|
||||||
|
|
||||||
|
super(executor, callable);
|
||||||
|
deadlineNanos = nanoTime;
|
||||||
|
periodNanos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long validatePeriod(long period) {
|
||||||
|
if (period == 0) {
|
||||||
|
throw new IllegalArgumentException("period: 0 (expected: != 0)");
|
||||||
|
}
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScheduledFutureTask<V> setId(long id) {
|
||||||
|
if (this.id == 0L) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected EventExecutor executor() {
|
||||||
|
return super.executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long deadlineNanos() {
|
||||||
|
return deadlineNanos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConsumed() {
|
||||||
|
// Optimization to avoid checking system clock again
|
||||||
|
// after deadline has passed and task has been dequeued
|
||||||
|
if (periodNanos == 0) {
|
||||||
|
assert nanoTime() >= deadlineNanos;
|
||||||
|
deadlineNanos = 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long delayNanos() {
|
||||||
|
return deadlineToDelayNanos(deadlineNanos());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long deadlineToDelayNanos(long deadlineNanos) {
|
||||||
|
return deadlineNanos == 0L ? 0L : Math.max(0L, deadlineNanos - nanoTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long delayNanos(long currentTimeNanos) {
|
||||||
|
return deadlineNanos == 0L ? 0L
|
||||||
|
: Math.max(0L, deadlineNanos() - (currentTimeNanos - START_TIME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDelay(TimeUnit unit) {
|
||||||
|
return unit.convert(delayNanos(), TimeUnit.NANOSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Delayed o) {
|
||||||
|
if (this == o) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduledFutureTask<?> that = (ScheduledFutureTask<?>) o;
|
||||||
|
long d = deadlineNanos() - that.deadlineNanos();
|
||||||
|
if (d < 0) {
|
||||||
|
return -1;
|
||||||
|
} else if (d > 0) {
|
||||||
|
return 1;
|
||||||
|
} else if (id < that.id) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
assert id != that.id;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
assert executor().inEventLoop();
|
||||||
|
try {
|
||||||
|
if (delayNanos() > 0L) {
|
||||||
|
// Not yet expired, need to add or remove from queue
|
||||||
|
if (isCancelled()) {
|
||||||
|
scheduledExecutor().scheduledTaskQueue().removeTyped(this);
|
||||||
|
} else {
|
||||||
|
scheduledExecutor().scheduleFromEventLoop(this);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (periodNanos == 0) {
|
||||||
|
if (setUncancellableInternal()) {
|
||||||
|
V result = runTask();
|
||||||
|
setSuccessInternal(result);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// check if is done as it may was cancelled
|
||||||
|
if (!isCancelled()) {
|
||||||
|
runTask();
|
||||||
|
if (!executor().isShutdown()) {
|
||||||
|
if (periodNanos > 0) {
|
||||||
|
deadlineNanos += periodNanos;
|
||||||
|
} else {
|
||||||
|
deadlineNanos = nanoTime() - periodNanos;
|
||||||
|
}
|
||||||
|
if (!isCancelled()) {
|
||||||
|
scheduledExecutor().scheduledTaskQueue().add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
setFailureInternal(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractScheduledEventExecutor scheduledExecutor() {
|
||||||
|
return (AbstractScheduledEventExecutor) executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @param mayInterruptIfRunning this value has no effect in this implementation.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||||
|
boolean canceled = super.cancel(mayInterruptIfRunning);
|
||||||
|
if (canceled) {
|
||||||
|
scheduledExecutor().removeScheduled(this);
|
||||||
|
}
|
||||||
|
return canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean cancelWithoutRemove(boolean mayInterruptIfRunning) {
|
||||||
|
return super.cancel(mayInterruptIfRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StringBuilder toStringBuilder() {
|
||||||
|
StringBuilder buf = super.toStringBuilder();
|
||||||
|
buf.setCharAt(buf.length() - 1, ',');
|
||||||
|
|
||||||
|
return buf.append(" deadline: ")
|
||||||
|
.append(deadlineNanos)
|
||||||
|
.append(", period: ")
|
||||||
|
.append(periodNanos)
|
||||||
|
.append(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int priorityQueueIndex(DefaultPriorityQueue<?> queue) {
|
||||||
|
return queueIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void priorityQueueIndex(DefaultPriorityQueue<?> queue, int i) {
|
||||||
|
queueIndex = i;
|
||||||
|
}
|
||||||
|
}
|
37
src/main/java/org/xbib/event/SucceededFuture.java
Normal file
37
src/main/java/org/xbib/event/SucceededFuture.java
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package org.xbib.event;
|
||||||
|
|
||||||
|
import org.xbib.event.loop.EventExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CompleteFuture} which is succeeded already. It is
|
||||||
|
* recommended to use {@link EventExecutor#newSucceededFuture(Object)} instead of
|
||||||
|
* calling the constructor of this future.
|
||||||
|
*/
|
||||||
|
public final class SucceededFuture<V> extends CompleteFuture<V> {
|
||||||
|
private final V result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param executor the {@link EventExecutor} associated with this future
|
||||||
|
*/
|
||||||
|
public SucceededFuture(EventExecutor executor, V result) {
|
||||||
|
super(executor);
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable cause() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V getNow() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
189
src/main/java/org/xbib/event/async/Async.java
Normal file
189
src/main/java/org/xbib/event/async/Async.java
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
import org.xbib.event.async.impl.AsyncBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entry point into the Async API.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface Async {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance using default options.
|
||||||
|
*
|
||||||
|
* @return the instance
|
||||||
|
*/
|
||||||
|
static Async getInstance() {
|
||||||
|
return getInstance(new AsyncOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a non clustered instance using the specified options
|
||||||
|
*
|
||||||
|
* @param options the options to use
|
||||||
|
* @return the instance
|
||||||
|
*/
|
||||||
|
static Async getInstance(AsyncOptions options) {
|
||||||
|
return new AsyncBuilder(options).init().newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current context
|
||||||
|
*
|
||||||
|
* @return The current context or {@code null} if there is no current context
|
||||||
|
*/
|
||||||
|
static Context currentContext() {
|
||||||
|
return ContextInternal.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current context, or creates one if there isn't one
|
||||||
|
*
|
||||||
|
* @return The current context (created if didn't exist)
|
||||||
|
*/
|
||||||
|
Context getOrCreateContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a one-shot timer to fire after {@code delay} milliseconds, at which point {@code handler} will be called with
|
||||||
|
* the id of the timer.
|
||||||
|
*
|
||||||
|
* @param delay the delay in milliseconds, after which the timer will fire
|
||||||
|
* @param handler the handler that will be called with the timer ID when the timer fires
|
||||||
|
* @return the unique ID of the timer
|
||||||
|
*/
|
||||||
|
long setTimer(long delay, Handler<Long> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a one-shot timer as a read stream.
|
||||||
|
*
|
||||||
|
* @param delay the delay in milliseconds, after which the timer will fire
|
||||||
|
* @return the timer stream
|
||||||
|
*/
|
||||||
|
TimeoutStream timerStream(long delay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a periodic timer to fire every {@code delay} milliseconds, at which point {@code handler} will be called with
|
||||||
|
* the id of the timer.
|
||||||
|
*
|
||||||
|
* @param delay the delay in milliseconds, after which the timer will fire
|
||||||
|
* @param handler the handler that will be called with the timer ID when the timer fires
|
||||||
|
* @return the unique ID of the timer
|
||||||
|
*/
|
||||||
|
default long setPeriodic(long delay, Handler<Long> handler) {
|
||||||
|
return setPeriodic(delay, delay, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a periodic timer to fire every {@code delay} milliseconds with initial delay, at which point {@code handler} will be called with
|
||||||
|
* the id of the timer.
|
||||||
|
*
|
||||||
|
* @param initialDelay the initial delay in milliseconds
|
||||||
|
* @param delay the delay in milliseconds, after which the timer will fire
|
||||||
|
* @param handler the handler that will be called with the timer ID when the timer fires
|
||||||
|
* @return the unique ID of the timer
|
||||||
|
*/
|
||||||
|
long setPeriodic(long initialDelay, long delay, Handler<Long> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a periodic timer as a read stream.
|
||||||
|
*
|
||||||
|
* @param delay the delay in milliseconds, after which the timer will fire
|
||||||
|
* @return the periodic stream
|
||||||
|
*/
|
||||||
|
default TimeoutStream periodicStream(long delay) {
|
||||||
|
return periodicStream(0, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a periodic timer as a read stream.
|
||||||
|
*
|
||||||
|
* @param initialDelay the initial delay in milliseconds
|
||||||
|
* @param delay the delay in milliseconds, after which the timer will fire
|
||||||
|
* @return the periodic stream
|
||||||
|
*/
|
||||||
|
TimeoutStream periodicStream(long initialDelay, long delay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the timer with the specified {@code id}.
|
||||||
|
*
|
||||||
|
* @param id The id of the timer to cancel
|
||||||
|
* @return true if the timer was successfully cancelled, or false if the timer does not exist.
|
||||||
|
*/
|
||||||
|
boolean cancelTimer(long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts the handler on the event queue for the current context so it will be run asynchronously ASAP after all
|
||||||
|
* preceeding events have been handled.
|
||||||
|
*
|
||||||
|
* @param action - a handler representing the action to execute
|
||||||
|
*/
|
||||||
|
void runOnContext(Handler<Void> action);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the Async instance and release any resources held by it.
|
||||||
|
* <p>
|
||||||
|
* The instance cannot be used after it has been closed.
|
||||||
|
* <p>
|
||||||
|
* The actual close is asynchronous and may not complete until after the call has returned.
|
||||||
|
*
|
||||||
|
* @return a future completed with the result
|
||||||
|
*/
|
||||||
|
Future<Void> close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely execute some blocking code.
|
||||||
|
* <p>
|
||||||
|
* Executes the blocking code in the handler {@code blockingCodeHandler} using a thread from the worker pool.
|
||||||
|
* <p>
|
||||||
|
* When the code is complete the handler {@code resultHandler} will be called with the result on the original context
|
||||||
|
* (e.g. on the original event loop of the caller).
|
||||||
|
* <p>
|
||||||
|
* A {@code Future} instance is passed into {@code blockingCodeHandler}. When the blocking code successfully completes,
|
||||||
|
* the handler should call the {@link Promise#complete} or {@link Promise#complete(Object)} method, or the {@link Promise#fail}
|
||||||
|
* method if it failed.
|
||||||
|
* <p>
|
||||||
|
* In the {@code blockingCodeHandler} the current context remains the original context and therefore any task
|
||||||
|
* scheduled in the {@code blockingCodeHandler} will be executed on the this context and not on the worker thread.
|
||||||
|
* <p>
|
||||||
|
* The blocking code should block for a reasonable amount of time (i.e no more than a few seconds). Long blocking operations
|
||||||
|
* or polling operations (i.e a thread that spin in a loop polling events in a blocking fashion) are precluded.
|
||||||
|
* <p>
|
||||||
|
* When the blocking operation lasts more than the 10 seconds, a message will be printed on the console by the
|
||||||
|
* blocked thread checker.
|
||||||
|
* <p>
|
||||||
|
* Long blocking operations should use a dedicated thread managed by the application, which can interact with
|
||||||
|
* subscribers using the event-bus or {@link Context#runOnContext(Handler)}
|
||||||
|
*
|
||||||
|
* @param blockingCodeHandler handler representing the blocking code to run
|
||||||
|
* @param ordered if true then if executeBlocking is called several times on the same context, the executions
|
||||||
|
* for that context will be executed serially, not in parallel. if false then they will be no ordering
|
||||||
|
* guarantees
|
||||||
|
* @param <T> the type of the result
|
||||||
|
* @return a future completed when the blocking code is complete
|
||||||
|
*/
|
||||||
|
default <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler, boolean ordered) {
|
||||||
|
Context context = getOrCreateContext();
|
||||||
|
return context.executeBlocking(blockingCodeHandler, ordered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #executeBlocking(Handler, boolean)} called with ordered = true.
|
||||||
|
*/
|
||||||
|
default <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler) {
|
||||||
|
return executeBlocking(blockingCodeHandler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a default exception handler for {@link Context}, set on {@link Context#exceptionHandler(Handler)} at creation.
|
||||||
|
*
|
||||||
|
* @param handler the exception handler
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
Async exceptionHandler(Handler<Throwable> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current default exception handler
|
||||||
|
*/
|
||||||
|
Handler<Throwable> exceptionHandler();
|
||||||
|
}
|
447
src/main/java/org/xbib/event/async/AsyncOptions.java
Normal file
447
src/main/java/org/xbib/event/async/AsyncOptions.java
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instances of this class are used to configure {@link Async} instances.
|
||||||
|
*/
|
||||||
|
public class AsyncOptions {
|
||||||
|
|
||||||
|
private static final String DISABLE_TCCL_PROP_NAME = "org.xbib.disableTCCL";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default number of event loop threads to be used = 2 * number of cores on the machine
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_EVENT_LOOP_POOL_SIZE = 2 * Runtime.getRuntime().availableProcessors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default number of threads in the worker pool = 20
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_WORKER_POOL_SIZE = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default number of threads in the internal blocking pool (used by some internal operations) = 20
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_INTERNAL_BLOCKING_POOL_SIZE = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of blocked thread check interval = 1000 ms.
|
||||||
|
*/
|
||||||
|
public static final long DEFAULT_BLOCKED_THREAD_CHECK_INTERVAL = TimeUnit.SECONDS.toMillis(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of blocked thread check interval unit = {@link TimeUnit#MILLISECONDS}
|
||||||
|
*/
|
||||||
|
public static final TimeUnit DEFAULT_BLOCKED_THREAD_CHECK_INTERVAL_UNIT = TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of max event loop execute time = 2000000000 ns (2 seconds)
|
||||||
|
*/
|
||||||
|
public static final long DEFAULT_MAX_EVENT_LOOP_EXECUTE_TIME = TimeUnit.SECONDS.toNanos(2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of max event loop execute time unit = {@link TimeUnit#NANOSECONDS}
|
||||||
|
*/
|
||||||
|
public static final TimeUnit DEFAULT_MAX_EVENT_LOOP_EXECUTE_TIME_UNIT = TimeUnit.NANOSECONDS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of max worker execute time = 60000000000 ns (60 seconds)
|
||||||
|
*/
|
||||||
|
public static final long DEFAULT_MAX_WORKER_EXECUTE_TIME = TimeUnit.SECONDS.toNanos(60);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of max worker execute time unit = {@link TimeUnit#NANOSECONDS}
|
||||||
|
*/
|
||||||
|
public static final TimeUnit DEFAULT_MAX_WORKER_EXECUTE_TIME_UNIT = TimeUnit.NANOSECONDS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of warning exception time 5000000000 ns (5 seconds)
|
||||||
|
* If a thread is blocked longer than this threshold, the warning log
|
||||||
|
* contains a stack trace
|
||||||
|
*/
|
||||||
|
private static final long DEFAULT_WARNING_EXCEPTION_TIME = TimeUnit.SECONDS.toNanos(5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of warning exception time unit = {@link TimeUnit#NANOSECONDS}
|
||||||
|
*/
|
||||||
|
public static final TimeUnit DEFAULT_WARNING_EXCEPTION_TIME_UNIT = TimeUnit.NANOSECONDS;
|
||||||
|
|
||||||
|
public static final boolean DEFAULT_DISABLE_TCCL = Boolean.getBoolean(DISABLE_TCCL_PROP_NAME);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default value to false for aligning with the old behavior
|
||||||
|
* By default, threads are NOT daemons - we want them to prevent JVM exit so embedded user
|
||||||
|
* doesn't have to explicitly prevent JVM from exiting.
|
||||||
|
*/
|
||||||
|
public static final boolean DEFAULT_USE_DAEMON_THREAD = false;
|
||||||
|
|
||||||
|
private int eventLoopPoolSize = DEFAULT_EVENT_LOOP_POOL_SIZE;
|
||||||
|
private int workerPoolSize = DEFAULT_WORKER_POOL_SIZE;
|
||||||
|
private int internalBlockingPoolSize = DEFAULT_INTERNAL_BLOCKING_POOL_SIZE;
|
||||||
|
private long blockedThreadCheckInterval = DEFAULT_BLOCKED_THREAD_CHECK_INTERVAL;
|
||||||
|
private long maxEventLoopExecuteTime = DEFAULT_MAX_EVENT_LOOP_EXECUTE_TIME;
|
||||||
|
private long maxWorkerExecuteTime = DEFAULT_MAX_WORKER_EXECUTE_TIME;
|
||||||
|
private long warningExceptionTime = DEFAULT_WARNING_EXCEPTION_TIME;
|
||||||
|
private TimeUnit maxEventLoopExecuteTimeUnit = DEFAULT_MAX_EVENT_LOOP_EXECUTE_TIME_UNIT;
|
||||||
|
private TimeUnit maxWorkerExecuteTimeUnit = DEFAULT_MAX_WORKER_EXECUTE_TIME_UNIT;
|
||||||
|
private TimeUnit warningExceptionTimeUnit = DEFAULT_WARNING_EXCEPTION_TIME_UNIT;
|
||||||
|
private TimeUnit blockedThreadCheckIntervalUnit = DEFAULT_BLOCKED_THREAD_CHECK_INTERVAL_UNIT;
|
||||||
|
private boolean disableTCCL = DEFAULT_DISABLE_TCCL;
|
||||||
|
private Boolean useDaemonThread = DEFAULT_USE_DAEMON_THREAD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
public AsyncOptions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor
|
||||||
|
*
|
||||||
|
* @param other The other {@code AsyncOptions} to copy when creating this
|
||||||
|
*/
|
||||||
|
public AsyncOptions(AsyncOptions other) {
|
||||||
|
this.eventLoopPoolSize = other.getEventLoopPoolSize();
|
||||||
|
this.workerPoolSize = other.getWorkerPoolSize();
|
||||||
|
this.blockedThreadCheckInterval = other.getBlockedThreadCheckInterval();
|
||||||
|
this.maxEventLoopExecuteTime = other.getMaxEventLoopExecuteTime();
|
||||||
|
this.maxWorkerExecuteTime = other.getMaxWorkerExecuteTime();
|
||||||
|
this.internalBlockingPoolSize = other.getInternalBlockingPoolSize();
|
||||||
|
this.warningExceptionTime = other.warningExceptionTime;
|
||||||
|
this.maxEventLoopExecuteTimeUnit = other.maxEventLoopExecuteTimeUnit;
|
||||||
|
this.maxWorkerExecuteTimeUnit = other.maxWorkerExecuteTimeUnit;
|
||||||
|
this.warningExceptionTimeUnit = other.warningExceptionTimeUnit;
|
||||||
|
this.blockedThreadCheckIntervalUnit = other.blockedThreadCheckIntervalUnit;
|
||||||
|
this.disableTCCL = other.disableTCCL;
|
||||||
|
this.useDaemonThread = other.useDaemonThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of event loop threads to be used by the instance.
|
||||||
|
*
|
||||||
|
* @return the number of threads
|
||||||
|
*/
|
||||||
|
public int getEventLoopPoolSize() {
|
||||||
|
return eventLoopPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the number of event loop threads to be used by the instance.
|
||||||
|
*
|
||||||
|
* @param eventLoopPoolSize the number of threads
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setEventLoopPoolSize(int eventLoopPoolSize) {
|
||||||
|
if (eventLoopPoolSize < 1) {
|
||||||
|
throw new IllegalArgumentException("eventLoopPoolSize must be > 0");
|
||||||
|
}
|
||||||
|
this.eventLoopPoolSize = eventLoopPoolSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum number of worker threads to be used by the instance.
|
||||||
|
* <p>
|
||||||
|
* Worker threads are used for running blocking code.
|
||||||
|
*
|
||||||
|
* @return the maximum number of worker threads
|
||||||
|
*/
|
||||||
|
public int getWorkerPoolSize() {
|
||||||
|
return workerPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the maximum number of worker threads to be used by the instance.
|
||||||
|
*
|
||||||
|
* @param workerPoolSize the number of threads
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setWorkerPoolSize(int workerPoolSize) {
|
||||||
|
if (workerPoolSize < 1) {
|
||||||
|
throw new IllegalArgumentException("workerPoolSize must be > 0");
|
||||||
|
}
|
||||||
|
this.workerPoolSize = workerPoolSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of blocked thread check period, in {@link AsyncOptions#setBlockedThreadCheckIntervalUnit blockedThreadCheckIntervalUnit}.
|
||||||
|
* <p>
|
||||||
|
* This setting determines how often we will check whether event loop threads are executing for too long.
|
||||||
|
* <p>
|
||||||
|
* The default value of {@link AsyncOptions#setBlockedThreadCheckIntervalUnit blockedThreadCheckIntervalUnit} is {@link TimeUnit#MILLISECONDS}.
|
||||||
|
*
|
||||||
|
* @return the value of blocked thread check period, in {@link AsyncOptions#setBlockedThreadCheckIntervalUnit blockedThreadCheckIntervalUnit}.
|
||||||
|
*/
|
||||||
|
public long getBlockedThreadCheckInterval() {
|
||||||
|
return blockedThreadCheckInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of blocked thread check period, in {@link AsyncOptions#setBlockedThreadCheckIntervalUnit blockedThreadCheckIntervalUnit}.
|
||||||
|
* <p>
|
||||||
|
* The default value of {@link AsyncOptions#setBlockedThreadCheckIntervalUnit blockedThreadCheckIntervalUnit} is {@link TimeUnit#MILLISECONDS}
|
||||||
|
*
|
||||||
|
* @param blockedThreadCheckInterval the value of blocked thread check period, in {@link AsyncOptions#setBlockedThreadCheckIntervalUnit blockedThreadCheckIntervalUnit}.
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setBlockedThreadCheckInterval(long blockedThreadCheckInterval) {
|
||||||
|
if (blockedThreadCheckInterval < 1) {
|
||||||
|
throw new IllegalArgumentException("blockedThreadCheckInterval must be > 0");
|
||||||
|
}
|
||||||
|
this.blockedThreadCheckInterval = blockedThreadCheckInterval;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of max event loop execute time, in {@link AsyncOptions#setMaxEventLoopExecuteTimeUnit maxEventLoopExecuteTimeUnit}.
|
||||||
|
* <p>
|
||||||
|
* We will automatically log a warning if it detects that event loop threads haven't returned within this time.
|
||||||
|
* <p>
|
||||||
|
* This can be used to detect where the user is blocking an event loop thread, contrary to the Golden Rule of the
|
||||||
|
* holy Event Loop.
|
||||||
|
* <p>
|
||||||
|
* The default value of {@link AsyncOptions#setMaxEventLoopExecuteTimeUnit maxEventLoopExecuteTimeUnit} is {@link TimeUnit#NANOSECONDS}
|
||||||
|
*
|
||||||
|
* @return the value of max event loop execute time, in {@link AsyncOptions#setMaxEventLoopExecuteTimeUnit maxEventLoopExecuteTimeUnit}.
|
||||||
|
*/
|
||||||
|
public long getMaxEventLoopExecuteTime() {
|
||||||
|
return maxEventLoopExecuteTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of max event loop execute time, in {@link AsyncOptions#setMaxEventLoopExecuteTimeUnit maxEventLoopExecuteTimeUnit}.
|
||||||
|
* <p>
|
||||||
|
* The default value of {@link AsyncOptions#setMaxEventLoopExecuteTimeUnit maxEventLoopExecuteTimeUnit}is {@link TimeUnit#NANOSECONDS}
|
||||||
|
*
|
||||||
|
* @param maxEventLoopExecuteTime the value of max event loop execute time, in {@link AsyncOptions#setMaxEventLoopExecuteTimeUnit maxEventLoopExecuteTimeUnit}.
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setMaxEventLoopExecuteTime(long maxEventLoopExecuteTime) {
|
||||||
|
if (maxEventLoopExecuteTime < 1) {
|
||||||
|
throw new IllegalArgumentException("maxEventLoopExecuteTime must be > 0");
|
||||||
|
}
|
||||||
|
this.maxEventLoopExecuteTime = maxEventLoopExecuteTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of max worker execute time, in {@link AsyncOptions#setMaxWorkerExecuteTimeUnit maxWorkerExecuteTimeUnit}.
|
||||||
|
* <p>
|
||||||
|
* We will automatically log a warning if it detects that worker threads haven't returned within this time.
|
||||||
|
* <p>
|
||||||
|
* This can be used to detect where the user is blocking a worker thread for too long. Although worker threads
|
||||||
|
* can be blocked longer than event loop threads, they shouldn't be blocked for long periods of time.
|
||||||
|
* <p>
|
||||||
|
* The default value of {@link AsyncOptions#setMaxWorkerExecuteTimeUnit maxWorkerExecuteTimeUnit} is {@link TimeUnit#NANOSECONDS}
|
||||||
|
*
|
||||||
|
* @return The value of max worker execute time, in {@link AsyncOptions#setMaxWorkerExecuteTimeUnit maxWorkerExecuteTimeUnit}.
|
||||||
|
*/
|
||||||
|
public long getMaxWorkerExecuteTime() {
|
||||||
|
return maxWorkerExecuteTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of max worker execute time, in {@link AsyncOptions#setMaxWorkerExecuteTimeUnit maxWorkerExecuteTimeUnit}.
|
||||||
|
* <p>
|
||||||
|
* The default value of {@link AsyncOptions#setMaxWorkerExecuteTimeUnit maxWorkerExecuteTimeUnit} is {@link TimeUnit#NANOSECONDS}
|
||||||
|
*
|
||||||
|
* @param maxWorkerExecuteTime the value of max worker execute time, in {@link AsyncOptions#setMaxWorkerExecuteTimeUnit maxWorkerExecuteTimeUnit}.
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setMaxWorkerExecuteTime(long maxWorkerExecuteTime) {
|
||||||
|
if (maxWorkerExecuteTime < 1) {
|
||||||
|
throw new IllegalArgumentException("maxWorkerpExecuteTime must be > 0");
|
||||||
|
}
|
||||||
|
this.maxWorkerExecuteTime = maxWorkerExecuteTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of internal blocking pool size.
|
||||||
|
* <p>
|
||||||
|
* We maintain a pool for internal blocking operations.
|
||||||
|
*
|
||||||
|
* @return the value of internal blocking pool size
|
||||||
|
*/
|
||||||
|
public int getInternalBlockingPoolSize() {
|
||||||
|
return internalBlockingPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value of internal blocking pool size
|
||||||
|
*
|
||||||
|
* @param internalBlockingPoolSize the maximumn number of threads in the internal blocking pool
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setInternalBlockingPoolSize(int internalBlockingPoolSize) {
|
||||||
|
if (internalBlockingPoolSize < 1) {
|
||||||
|
throw new IllegalArgumentException("internalBlockingPoolSize must be > 0");
|
||||||
|
}
|
||||||
|
this.internalBlockingPoolSize = internalBlockingPoolSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the threshold value above this, the blocked warning contains a stack trace. in {@link AsyncOptions#setWarningExceptionTimeUnit warningExceptionTimeUnit}.
|
||||||
|
* <p>
|
||||||
|
* The default value of {@link AsyncOptions#setWarningExceptionTimeUnit warningExceptionTimeUnit} is {@link TimeUnit#NANOSECONDS}
|
||||||
|
*
|
||||||
|
* @return the warning exception time threshold
|
||||||
|
*/
|
||||||
|
public long getWarningExceptionTime() {
|
||||||
|
return warningExceptionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the threshold value above this, the blocked warning contains a stack trace. in {@link AsyncOptions#setWarningExceptionTimeUnit warningExceptionTimeUnit}.
|
||||||
|
* The default value of {@link AsyncOptions#setWarningExceptionTimeUnit warningExceptionTimeUnit} is {@link TimeUnit#NANOSECONDS}
|
||||||
|
*
|
||||||
|
* @param warningExceptionTime
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setWarningExceptionTime(long warningExceptionTime) {
|
||||||
|
if (warningExceptionTime < 1) {
|
||||||
|
throw new IllegalArgumentException("warningExceptionTime must be > 0");
|
||||||
|
}
|
||||||
|
this.warningExceptionTime = warningExceptionTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the time unit of {@code maxEventLoopExecuteTime}
|
||||||
|
*/
|
||||||
|
public TimeUnit getMaxEventLoopExecuteTimeUnit() {
|
||||||
|
return maxEventLoopExecuteTimeUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time unit of {@code maxEventLoopExecuteTime}.
|
||||||
|
*
|
||||||
|
* @param maxEventLoopExecuteTimeUnit the time unit of {@code maxEventLoopExecuteTime}
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setMaxEventLoopExecuteTimeUnit(TimeUnit maxEventLoopExecuteTimeUnit) {
|
||||||
|
this.maxEventLoopExecuteTimeUnit = maxEventLoopExecuteTimeUnit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the time unit of {@code maxWorkerExecuteTime}
|
||||||
|
*/
|
||||||
|
public TimeUnit getMaxWorkerExecuteTimeUnit() {
|
||||||
|
return maxWorkerExecuteTimeUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time unit of {@code maxWorkerExecuteTime}.
|
||||||
|
*
|
||||||
|
* @param maxWorkerExecuteTimeUnit the time unit of {@code maxWorkerExecuteTime}
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setMaxWorkerExecuteTimeUnit(TimeUnit maxWorkerExecuteTimeUnit) {
|
||||||
|
this.maxWorkerExecuteTimeUnit = maxWorkerExecuteTimeUnit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the time unit of {@code warningExceptionTime}
|
||||||
|
*/
|
||||||
|
public TimeUnit getWarningExceptionTimeUnit() {
|
||||||
|
return warningExceptionTimeUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time unit of {@code warningExceptionTime}.
|
||||||
|
*
|
||||||
|
* @param warningExceptionTimeUnit the time unit of {@code warningExceptionTime}
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setWarningExceptionTimeUnit(TimeUnit warningExceptionTimeUnit) {
|
||||||
|
this.warningExceptionTimeUnit = warningExceptionTimeUnit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the time unit of {@code blockedThreadCheckInterval}
|
||||||
|
*/
|
||||||
|
public TimeUnit getBlockedThreadCheckIntervalUnit() {
|
||||||
|
return blockedThreadCheckIntervalUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time unit of {@code blockedThreadCheckInterval}.
|
||||||
|
*
|
||||||
|
* @param blockedThreadCheckIntervalUnit the time unit of {@code warningExceptionTime}
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setBlockedThreadCheckIntervalUnit(TimeUnit blockedThreadCheckIntervalUnit) {
|
||||||
|
this.blockedThreadCheckIntervalUnit = blockedThreadCheckIntervalUnit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether we set the {@link Context} classloader as the thread context classloader on actions executed on that {@link Context}
|
||||||
|
*/
|
||||||
|
public boolean getDisableTCCL() {
|
||||||
|
return disableTCCL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures whether we set the {@link Context} classloader as the thread context classloader on actions executed on that {@link Context}.
|
||||||
|
*
|
||||||
|
* When a {@link Context} is created the current thread classloader is captured and associated with this classloader.
|
||||||
|
*
|
||||||
|
* This setting overrides the (legacy) system property {@code org.xbib.disableTCCL} and provides control at the
|
||||||
|
* Async instance level.
|
||||||
|
*
|
||||||
|
* @param disableTCCL {@code true} to disable thread context classloader update by Async
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
public AsyncOptions setDisableTCCL(boolean disableTCCL) {
|
||||||
|
this.disableTCCL = disableTCCL;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether we want to use daemon thread.
|
||||||
|
*
|
||||||
|
* @return {@code true} means daemon, {@code false} means not daemon(user), {@code null} means do
|
||||||
|
* not change the daemon option of the created thread.
|
||||||
|
*/
|
||||||
|
public Boolean getUseDaemonThread() {
|
||||||
|
return useDaemonThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the thread as daemon thread or user thread.
|
||||||
|
* <p/>
|
||||||
|
* For keeping the old behavior, the default value is {@code false} instead of {@code null}.
|
||||||
|
*
|
||||||
|
* @param daemon {@code true} means daemon, {@code false} means not daemon(user), {@code null}
|
||||||
|
* means do not change the daemon option of the created thread.
|
||||||
|
*/
|
||||||
|
public AsyncOptions setUseDaemonThread(Boolean daemon) {
|
||||||
|
this.useDaemonThread = daemon;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AsyncOptions{" +
|
||||||
|
"eventLoopPoolSize=" + eventLoopPoolSize +
|
||||||
|
", workerPoolSize=" + workerPoolSize +
|
||||||
|
", internalBlockingPoolSize=" + internalBlockingPoolSize +
|
||||||
|
", blockedThreadCheckIntervalUnit=" + blockedThreadCheckIntervalUnit +
|
||||||
|
", blockedThreadCheckInterval=" + blockedThreadCheckInterval +
|
||||||
|
", maxEventLoopExecuteTimeUnit=" + maxEventLoopExecuteTimeUnit +
|
||||||
|
", maxEventLoopExecuteTime=" + maxEventLoopExecuteTime +
|
||||||
|
", maxWorkerExecuteTimeUnit=" + maxWorkerExecuteTimeUnit +
|
||||||
|
", maxWorkerExecuteTime=" + maxWorkerExecuteTime +
|
||||||
|
", warningExceptionTimeUnit=" + warningExceptionTimeUnit +
|
||||||
|
", warningExceptionTime=" + warningExceptionTime +
|
||||||
|
", disableTCCL=" + disableTCCL +
|
||||||
|
", useDaemonThread=" + useDaemonThread +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
188
src/main/java/org/xbib/event/async/AsyncResult.java
Normal file
188
src/main/java/org/xbib/event/async/AsyncResult.java
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates the result of an asynchronous operation.
|
||||||
|
* <p>
|
||||||
|
* Many operations in Vert.x APIs provide results back by passing an instance of this in a {@link Handler}.
|
||||||
|
* <p>
|
||||||
|
* The result can either have failed or succeeded.
|
||||||
|
* <p>
|
||||||
|
* If it failed then the cause of the failure is available with {@link #cause}.
|
||||||
|
* <p>
|
||||||
|
* If it succeeded then the actual result is available with {@link #result}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface AsyncResult<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of the operation. This will be null if the operation failed.
|
||||||
|
*
|
||||||
|
* @return the result or null if the operation failed.
|
||||||
|
*/
|
||||||
|
T result();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Throwable describing failure. This will be null if the operation succeeded.
|
||||||
|
*
|
||||||
|
* @return the cause or null if the operation succeeded.
|
||||||
|
*/
|
||||||
|
Throwable cause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did it succeed?
|
||||||
|
*
|
||||||
|
* @return true if it succeded or false otherwise
|
||||||
|
*/
|
||||||
|
boolean succeeded();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did it fail?
|
||||||
|
*
|
||||||
|
* @return true if it failed or false otherwise
|
||||||
|
*/
|
||||||
|
boolean failed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a {@code mapper} function on this async result.<p>
|
||||||
|
*
|
||||||
|
* The {@code mapper} is called with the completed value and this mapper returns a value. This value will complete the result returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this async result is failed, the failure will be propagated to the returned async result and the {@code mapper} will not be called.
|
||||||
|
*
|
||||||
|
* @param mapper the mapper function
|
||||||
|
* @return the mapped async result
|
||||||
|
*/
|
||||||
|
default <U> AsyncResult<U> map(Function<T, U> mapper) {
|
||||||
|
if (mapper == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
return new AsyncResult<U>() {
|
||||||
|
@Override
|
||||||
|
public U result() {
|
||||||
|
if (succeeded()) {
|
||||||
|
return mapper.apply(AsyncResult.this.result());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable cause() {
|
||||||
|
return AsyncResult.this.cause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean succeeded() {
|
||||||
|
return AsyncResult.this.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean failed() {
|
||||||
|
return AsyncResult.this.failed();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the result of this async result to a specific {@code value}.<p>
|
||||||
|
*
|
||||||
|
* When this async result succeeds, this {@code value} will succeeed the async result returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this async result fails, the failure will be propagated to the returned async result.
|
||||||
|
*
|
||||||
|
* @param value the value that eventually completes the mapped async result
|
||||||
|
* @return the mapped async result
|
||||||
|
*/
|
||||||
|
default <V> AsyncResult<V> map(V value) {
|
||||||
|
return map(t -> value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the result of this async result to {@code null}.<p>
|
||||||
|
*
|
||||||
|
* This is a convenience for {@code asyncResult.map((T) null)} or {@code asyncResult.map((Void) null)}.<p>
|
||||||
|
*
|
||||||
|
* When this async result succeeds, {@code null} will succeeed the async result returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this async result fails, the failure will be propagated to the returned async result.
|
||||||
|
*
|
||||||
|
* @return the mapped async result
|
||||||
|
*/
|
||||||
|
default <V> AsyncResult<V> mapEmpty() {
|
||||||
|
return map((V)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a {@code mapper} function on this async result.<p>
|
||||||
|
*
|
||||||
|
* The {@code mapper} is called with the failure and this mapper returns a value. This value will complete the result returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this async result is succeeded, the value will be propagated to the returned async result and the {@code mapper} will not be called.
|
||||||
|
*
|
||||||
|
* @param mapper the mapper function
|
||||||
|
* @return the mapped async result
|
||||||
|
*/
|
||||||
|
default AsyncResult<T> otherwise(Function<Throwable, T> mapper) {
|
||||||
|
if (mapper == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
return new AsyncResult<T>() {
|
||||||
|
@Override
|
||||||
|
public T result() {
|
||||||
|
if (AsyncResult.this.succeeded()) {
|
||||||
|
return AsyncResult.this.result();
|
||||||
|
} else if (AsyncResult.this.failed()) {
|
||||||
|
return mapper.apply(AsyncResult.this.cause());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable cause() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean succeeded() {
|
||||||
|
return AsyncResult.this.succeeded() || AsyncResult.this.failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean failed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the failure of this async result to a specific {@code value}.<p>
|
||||||
|
*
|
||||||
|
* When this async result fails, this {@code value} will succeeed the async result returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this async succeeds, the result will be propagated to the returned async result.
|
||||||
|
*
|
||||||
|
* @param value the value that eventually completes the mapped async result
|
||||||
|
* @return the mapped async result
|
||||||
|
*/
|
||||||
|
default AsyncResult<T> otherwise(T value) {
|
||||||
|
return otherwise(err -> value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the failure of this async result to {@code null}.<p>
|
||||||
|
*
|
||||||
|
* This is a convenience for {@code asyncResult.otherwise((T) null)}.<p>
|
||||||
|
*
|
||||||
|
* When this async result fails, the {@code null} will succeeed the async result returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this async succeeds, the result will be propagated to the returned async result.
|
||||||
|
*
|
||||||
|
* @return the mapped async result
|
||||||
|
*/
|
||||||
|
default AsyncResult<T> otherwiseEmpty() {
|
||||||
|
return otherwise(err -> null);
|
||||||
|
}
|
||||||
|
}
|
16
src/main/java/org/xbib/event/async/Closeable.java
Normal file
16
src/main/java/org/xbib/event/async/Closeable.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A closeable resource.
|
||||||
|
* <p/>
|
||||||
|
* This interface is mostly used for internal resource management.
|
||||||
|
*/
|
||||||
|
public interface Closeable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close this resource, the {@code completion} promise must be notified when the operation has completed.
|
||||||
|
*
|
||||||
|
* @param completion the promise to signal when close has completed
|
||||||
|
*/
|
||||||
|
void close(Promise<Void> completion);
|
||||||
|
}
|
245
src/main/java/org/xbib/event/async/CompositeFuture.java
Normal file
245
src/main/java/org/xbib/event/async/CompositeFuture.java
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.future.CompositeFutureImpl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The composite future wraps a list of {@link Future futures}, it is useful when several futures
|
||||||
|
* needs to be coordinated.
|
||||||
|
* The handlers set for the coordinated futures are overridden by the handler of the composite future.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface CompositeFuture extends Future<CompositeFuture> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a composite future, succeeded when all futures are succeeded, failed when any future is failed.
|
||||||
|
* <p/>
|
||||||
|
* The returned future fails as soon as one of {@code f1} or {@code f2} fails.
|
||||||
|
*
|
||||||
|
* @param f1 future
|
||||||
|
* @param f2 future
|
||||||
|
* @return the composite future
|
||||||
|
*/
|
||||||
|
static <T1, T2> CompositeFuture all(Future<T1> f1, Future<T2> f2) {
|
||||||
|
return CompositeFutureImpl.all(f1, f2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #all(Future, Future)} but with 3 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3> CompositeFuture all(Future<T1> f1, Future<T2> f2, Future<T3> f3) {
|
||||||
|
return CompositeFutureImpl.all(f1, f2, f3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #all(Future, Future)} but with 4 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4> CompositeFuture all(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4) {
|
||||||
|
return CompositeFutureImpl.all(f1, f2, f3, f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #all(Future, Future)} but with 5 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4, T5> CompositeFuture all(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4, Future<T5> f5) {
|
||||||
|
return CompositeFutureImpl.all(f1, f2, f3, f4, f5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #all(Future, Future)} but with 6 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4, T5, T6> CompositeFuture all(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4, Future<T5> f5, Future<T6> f6) {
|
||||||
|
return CompositeFutureImpl.all(f1, f2, f3, f4, f5, f6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #all(Future, Future)} but with a list of futures.<p>
|
||||||
|
*
|
||||||
|
* When the list is empty, the returned future will be already completed.
|
||||||
|
*/
|
||||||
|
static CompositeFuture all(List<Future> futures) {
|
||||||
|
return CompositeFutureImpl.all(futures.toArray(new Future[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a composite future, succeeded when any futures is succeeded, failed when all futures are failed.
|
||||||
|
* <p/>
|
||||||
|
* The returned future succeeds as soon as one of {@code f1} or {@code f2} succeeds.
|
||||||
|
*
|
||||||
|
* @param f1 future
|
||||||
|
* @param f2 future
|
||||||
|
* @return the composite future
|
||||||
|
*/
|
||||||
|
static <T1, T2> CompositeFuture any(Future<T1> f1, Future<T2> f2) {
|
||||||
|
return CompositeFutureImpl.any(f1, f2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #any(Future, Future)} but with 3 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3> CompositeFuture any(Future<T1> f1, Future<T2> f2, Future<T3> f3) {
|
||||||
|
return CompositeFutureImpl.any(f1, f2, f3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #any(Future, Future)} but with 4 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4> CompositeFuture any(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4) {
|
||||||
|
return CompositeFutureImpl.any(f1, f2, f3, f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #any(Future, Future)} but with 5 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4, T5> CompositeFuture any(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4, Future<T5> f5) {
|
||||||
|
return CompositeFutureImpl.any(f1, f2, f3, f4, f5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #any(Future, Future)} but with 6 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4, T5, T6> CompositeFuture any(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4, Future<T5> f5, Future<T6> f6) {
|
||||||
|
return CompositeFutureImpl.any(f1, f2, f3, f4, f5, f6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #any(Future, Future)} but with a list of futures.<p>
|
||||||
|
*
|
||||||
|
* When the list is empty, the returned future will be already completed.
|
||||||
|
*/
|
||||||
|
static CompositeFuture any(List<Future> futures) {
|
||||||
|
return CompositeFutureImpl.any(futures.toArray(new Future[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a composite future, succeeded when all futures are succeeded, failed when any future is failed.
|
||||||
|
* <p/>
|
||||||
|
* It always wait until all its futures are completed and will not fail as soon as one of {@code f1} or {@code f2} fails.
|
||||||
|
*
|
||||||
|
* @param f1 future
|
||||||
|
* @param f2 future
|
||||||
|
* @return the composite future
|
||||||
|
*/
|
||||||
|
static <T1, T2> CompositeFuture join(Future<T1> f1, Future<T2> f2) {
|
||||||
|
return CompositeFutureImpl.join(f1, f2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #join(Future, Future)} but with 3 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3> CompositeFuture join(Future<T1> f1, Future<T2> f2, Future<T3> f3) {
|
||||||
|
return CompositeFutureImpl.join(f1, f2, f3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #join(Future, Future)} but with 4 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4> CompositeFuture join(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4) {
|
||||||
|
return CompositeFutureImpl.join(f1, f2, f3, f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #join(Future, Future)} but with 5 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4, T5> CompositeFuture join(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4, Future<T5> f5) {
|
||||||
|
return CompositeFutureImpl.join(f1, f2, f3, f4, f5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #join(Future, Future)} but with 6 futures.
|
||||||
|
*/
|
||||||
|
static <T1, T2, T3, T4, T5, T6> CompositeFuture join(Future<T1> f1, Future<T2> f2, Future<T3> f3, Future<T4> f4, Future<T5> f5, Future<T6> f6) {
|
||||||
|
return CompositeFutureImpl.join(f1, f2, f3, f4, f5, f6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #join(Future, Future)} but with a list of futures.<p>
|
||||||
|
*
|
||||||
|
* When the list is empty, the returned future will be already completed.
|
||||||
|
*/
|
||||||
|
static CompositeFuture join(List<Future> futures) {
|
||||||
|
return CompositeFutureImpl.join(futures.toArray(new Future[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CompositeFuture onComplete(Handler<AsyncResult<CompositeFuture>> handler);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default CompositeFuture onSuccess(Handler<CompositeFuture> handler) {
|
||||||
|
Future.super.onSuccess(handler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default CompositeFuture onFailure(Handler<Throwable> handler) {
|
||||||
|
Future.super.onFailure(handler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a cause of a wrapped future
|
||||||
|
*
|
||||||
|
* @param index the wrapped future index
|
||||||
|
*/
|
||||||
|
Throwable cause(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a wrapped future is succeeded
|
||||||
|
*
|
||||||
|
* @param index the wrapped future index
|
||||||
|
*/
|
||||||
|
boolean succeeded(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a wrapped future is failed
|
||||||
|
*
|
||||||
|
* @param index the wrapped future index
|
||||||
|
*/
|
||||||
|
boolean failed(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a wrapped future is completed
|
||||||
|
*
|
||||||
|
* @param index the wrapped future index
|
||||||
|
*/
|
||||||
|
boolean isComplete(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result of a wrapped future
|
||||||
|
*
|
||||||
|
* @param index the wrapped future index
|
||||||
|
*/
|
||||||
|
<T> T resultAt(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of wrapped future
|
||||||
|
*/
|
||||||
|
int size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a list of the current completed values. If one future is not yet resolved or is failed, {@code} null
|
||||||
|
* will be used
|
||||||
|
*/
|
||||||
|
default <T> List<T> list() {
|
||||||
|
int size = size();
|
||||||
|
ArrayList<T> list = new ArrayList<>(size);
|
||||||
|
for (int index = 0;index < size;index++) {
|
||||||
|
list.add(resultAt(index));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a list of all the eventual failure causes. If no future failed, returns a list of null values.
|
||||||
|
*/
|
||||||
|
default List<Throwable> causes() {
|
||||||
|
int size = size();
|
||||||
|
ArrayList<Throwable> list = new ArrayList<>(size);
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
list.add(cause(index));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
212
src/main/java/org/xbib/event/async/Context.java
Normal file
212
src/main/java/org/xbib/event/async/Context.java
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.AsyncThread;
|
||||||
|
import org.xbib.event.loop.EventLoop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The execution context of a {@link Handler} execution.
|
||||||
|
* <p>
|
||||||
|
* When we provide an event to a handler, the execution is associated with a {@code Context}.
|
||||||
|
* <p>
|
||||||
|
* Usually a context is an *event-loop context* and is tied to a specific event loop thread. So executions for that
|
||||||
|
* context always occur on that exact same event loop thread.
|
||||||
|
* <p>
|
||||||
|
* In the case of running inline blocking code a worker context will be associated with the execution
|
||||||
|
* which will use a thread from the worker thread pool.
|
||||||
|
* <p>
|
||||||
|
* When a handler is set by a thread associated with a specific context, we will guarantee that when that handler
|
||||||
|
* is executed, that execution will be associated with the same context.
|
||||||
|
* <p>
|
||||||
|
* If a handler is set by a thread not associated with a context. Then a new context will
|
||||||
|
* be created for that handler.
|
||||||
|
* <p>
|
||||||
|
* In other words, a context is propagated.
|
||||||
|
* <p>
|
||||||
|
* This class also allows arbitrary data to be {@link #put} and {@link #get} on the context so it can be shared easily
|
||||||
|
* amongst different handlers.
|
||||||
|
* <p>
|
||||||
|
* This class also provides {@link #runOnContext} which allows an action to be executed asynchronously using the same context.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface Context {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the current thread a worker thread?
|
||||||
|
* <p>
|
||||||
|
* NOTE! This is not always the same as calling {@link Context#isWorkerContext}. If you are running blocking code
|
||||||
|
* from an event loop context, then this will return true but {@link Context#isWorkerContext} will return false.
|
||||||
|
*
|
||||||
|
* @return true if current thread is a worker thread, false otherwise
|
||||||
|
*/
|
||||||
|
static boolean isOnWorkerThread() {
|
||||||
|
Thread t = Thread.currentThread();
|
||||||
|
return t instanceof AsyncThread && ((AsyncThread) t).isWorker();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the current thread an event thread?
|
||||||
|
* <p>
|
||||||
|
* NOTE! This is not always the same as calling {@link Context#isEventLoopContext}. If you are running blocking code
|
||||||
|
* from an event loop context, then this will return false but {@link Context#isEventLoopContext} will return true.
|
||||||
|
*
|
||||||
|
* @return true if current thread is an event thread, false otherwise
|
||||||
|
*/
|
||||||
|
static boolean isOnEventLoopThread() {
|
||||||
|
Thread t = Thread.currentThread();
|
||||||
|
return t instanceof AsyncThread && !((AsyncThread) t).isWorker();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the current thread an async thread? That's either a worker thread or an event loop thread
|
||||||
|
*
|
||||||
|
* @return true if current thread is an async thread, false otherwise
|
||||||
|
*/
|
||||||
|
static boolean isAsyncThread() {
|
||||||
|
return Thread.currentThread() instanceof AsyncThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the specified action asynchronously on the same context, some time after the current execution has completed.
|
||||||
|
*
|
||||||
|
* @param action the action to run
|
||||||
|
*/
|
||||||
|
void runOnContext(Handler<Void> action);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely execute some blocking code.
|
||||||
|
* <p>
|
||||||
|
* Executes the blocking code in the handler {@code blockingCodeHandler} using a thread from the worker pool.
|
||||||
|
* <p>
|
||||||
|
* When the code is complete the handler {@code resultHandler} will be called with the result on the original context
|
||||||
|
* (e.g. on the original event loop of the caller).
|
||||||
|
* <p>
|
||||||
|
* A {@code Future} instance is passed into {@code blockingCodeHandler}. When the blocking code successfully completes,
|
||||||
|
* the handler should call the {@link Promise#complete} or {@link Promise#complete(Object)} method, or the {@link Promise#fail}
|
||||||
|
* method if it failed.
|
||||||
|
* <p>
|
||||||
|
* The blocking code should block for a reasonable amount of time (i.e no more than a few seconds). Long blocking operations
|
||||||
|
* or polling operations (i.e a thread that spin in a loop polling events in a blocking fashion) are precluded.
|
||||||
|
* <p>
|
||||||
|
* When the blocking operation lasts more than the 10 seconds, a message will be printed on the console by the
|
||||||
|
* blocked thread checker.
|
||||||
|
* <p>
|
||||||
|
* Long blocking operations should use a dedicated thread managed by the application, which can interact with
|
||||||
|
* subscribers using the event-bus or {@link Context#runOnContext(Handler)}
|
||||||
|
*
|
||||||
|
* @param blockingCodeHandler handler representing the blocking code to run
|
||||||
|
* @param ordered if true then if executeBlocking is called several times on the same context, the executions
|
||||||
|
* for that context will be executed serially, not in parallel. if false then they will be no ordering
|
||||||
|
* guarantees
|
||||||
|
* @param <T> the type of the result
|
||||||
|
* @return a future completed when the blocking code is complete
|
||||||
|
*/
|
||||||
|
<T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler, boolean ordered);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke {@link #executeBlocking(Handler, boolean)} with order = true.
|
||||||
|
* @param blockingCodeHandler handler representing the blocking code to run
|
||||||
|
* @param <T> the type of the result
|
||||||
|
* @return a future completed when the blocking code is complete
|
||||||
|
*/
|
||||||
|
default <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler) {
|
||||||
|
return executeBlocking(blockingCodeHandler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the current context an event loop context?
|
||||||
|
* <p>
|
||||||
|
* NOTE! when running blocking code using {@link Async#executeBlocking(Handler, boolean)},
|
||||||
|
* the context will still an event loop context and this {@link this#isEventLoopContext()}
|
||||||
|
* will return true.
|
||||||
|
*
|
||||||
|
* @return true if false otherwise
|
||||||
|
*/
|
||||||
|
boolean isEventLoopContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the current context a worker context?
|
||||||
|
* <p>
|
||||||
|
* NOTE! when running blocking code using {@link Async#executeBlocking(Handler, boolean)},
|
||||||
|
* the context will still an event loop context and this {@link this#isWorkerContext()}
|
||||||
|
* will return false.
|
||||||
|
*
|
||||||
|
* @return true if the current context is a worker context, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isWorkerContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get some data from the context.
|
||||||
|
*
|
||||||
|
* @param key the key of the data
|
||||||
|
* @param <T> the type of the data
|
||||||
|
* @return the data
|
||||||
|
*/
|
||||||
|
<T> T get(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put some data in the context.
|
||||||
|
* <p>
|
||||||
|
* This can be used to share data between different handlers that share a context
|
||||||
|
*
|
||||||
|
* @param key the key of the data
|
||||||
|
* @param value the data
|
||||||
|
*/
|
||||||
|
void put(Object key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove some data from the context.
|
||||||
|
*
|
||||||
|
* @param key the key to remove
|
||||||
|
* @return true if removed successfully, false otherwise
|
||||||
|
*/
|
||||||
|
boolean remove(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get some local data from the context.
|
||||||
|
*
|
||||||
|
* @param key the key of the data
|
||||||
|
* @param <T> the type of the data
|
||||||
|
* @return the data
|
||||||
|
*/
|
||||||
|
<T> T getLocal(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put some local data in the context.
|
||||||
|
* <p>
|
||||||
|
* This can be used to share data between different handlers that share a context
|
||||||
|
*
|
||||||
|
* @param key the key of the data
|
||||||
|
* @param value the data
|
||||||
|
*/
|
||||||
|
void putLocal(Object key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove some local data from the context.
|
||||||
|
*
|
||||||
|
* @param key the key to remove
|
||||||
|
* @return true if removed successfully, false otherwise
|
||||||
|
*/
|
||||||
|
boolean removeLocal(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The Async instance that created the context
|
||||||
|
*/
|
||||||
|
Async owner();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an exception handler called when the context runs an action throwing an uncaught throwable.<p/>
|
||||||
|
*
|
||||||
|
* When this handler is called, {@link Async#currentContext()} will return this context.
|
||||||
|
*
|
||||||
|
* @param handler the exception handler
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
Context exceptionHandler(Handler<Throwable> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current exception handler of this context
|
||||||
|
*/
|
||||||
|
Handler<Throwable> exceptionHandler();
|
||||||
|
|
||||||
|
EventLoop eventLoop();
|
||||||
|
}
|
67
src/main/java/org/xbib/event/async/EventException.java
Normal file
67
src/main/java/org/xbib/event/async/EventException.java
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a general purpose exception class that is often thrown if things go wrong.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class EventException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance given a message
|
||||||
|
*
|
||||||
|
* @param message the message
|
||||||
|
*/
|
||||||
|
public EventException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance given a message and a cause
|
||||||
|
*
|
||||||
|
* @param message the message
|
||||||
|
* @param cause the cause
|
||||||
|
*/
|
||||||
|
public EventException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance given a cause
|
||||||
|
*
|
||||||
|
* @param cause the cause
|
||||||
|
*/
|
||||||
|
public EventException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance given a message
|
||||||
|
*
|
||||||
|
* @param message the message
|
||||||
|
* @param noStackTrace disable stack trace capture
|
||||||
|
*/
|
||||||
|
public EventException(String message, boolean noStackTrace) {
|
||||||
|
super(message, null, !noStackTrace, !noStackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance given a message
|
||||||
|
*
|
||||||
|
* @param message the message
|
||||||
|
* @param cause the cause
|
||||||
|
* @param noStackTrace disable stack trace capture
|
||||||
|
*/
|
||||||
|
public EventException(String message, Throwable cause, boolean noStackTrace) {
|
||||||
|
super(message, cause, !noStackTrace, !noStackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance given a message
|
||||||
|
*
|
||||||
|
* @param cause the cause
|
||||||
|
* @param noStackTrace disable stack trace capture
|
||||||
|
*/
|
||||||
|
public EventException(Throwable cause, boolean noStackTrace) {
|
||||||
|
super(null, cause, !noStackTrace, !noStackTrace);
|
||||||
|
}
|
||||||
|
}
|
414
src/main/java/org/xbib/event/async/Future.java
Normal file
414
src/main/java/org/xbib/event/async/Future.java
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
import org.xbib.event.async.impl.future.FailedFuture;
|
||||||
|
import org.xbib.event.async.impl.future.SucceededFuture;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of an action that may, or may not, have occurred yet.
|
||||||
|
*/
|
||||||
|
public interface Future<T> extends AsyncResult<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that hasn't completed yet and that is passed to the {@code handler} before it is returned.
|
||||||
|
*
|
||||||
|
* @param handler the handler
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return the future.
|
||||||
|
*/
|
||||||
|
static <T> Future<T> future(Handler<Promise<T>> handler) {
|
||||||
|
Promise<T> promise = Promise.promise();
|
||||||
|
try {
|
||||||
|
handler.handle(promise);
|
||||||
|
} catch (Throwable e){
|
||||||
|
promise.tryFail(e);
|
||||||
|
}
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a succeeded future with a null result
|
||||||
|
*
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return the future
|
||||||
|
*/
|
||||||
|
static <T> Future<T> succeededFuture() {
|
||||||
|
return (Future<T>) SucceededFuture.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created a succeeded future with the specified result.
|
||||||
|
*
|
||||||
|
* @param result the result
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return the future
|
||||||
|
*/
|
||||||
|
static <T> Future<T> succeededFuture(T result) {
|
||||||
|
if (result == null) {
|
||||||
|
return succeededFuture();
|
||||||
|
} else {
|
||||||
|
return new SucceededFuture<>(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a failed future with the specified failure cause.
|
||||||
|
*
|
||||||
|
* @param t the failure cause as a Throwable
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return the future
|
||||||
|
*/
|
||||||
|
static <T> Future<T> failedFuture(Throwable t) {
|
||||||
|
return new FailedFuture<>(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a failed future with the specified failure message.
|
||||||
|
*
|
||||||
|
* @param failureMessage the failure message
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return the future
|
||||||
|
*/
|
||||||
|
static <T> Future<T> failedFuture(String failureMessage) {
|
||||||
|
return new FailedFuture<>(failureMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the future completed?
|
||||||
|
* <p>
|
||||||
|
* It's completed if it's either succeeded or failed.
|
||||||
|
*
|
||||||
|
* @return true if completed, false if not
|
||||||
|
*/
|
||||||
|
boolean isComplete();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a handler to be notified of the result.
|
||||||
|
* <p>
|
||||||
|
* <em><strong>WARNING</strong></em>: this is a terminal operation.
|
||||||
|
* If several {@code handler}s are registered, there is no guarantee that they will be invoked in order of registration.
|
||||||
|
*
|
||||||
|
* @param handler the handler that will be called with the result
|
||||||
|
* @return a reference to this, so it can be used fluently
|
||||||
|
*/
|
||||||
|
Future<T> onComplete(Handler<AsyncResult<T>> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a handler to be notified of the succeeded result.
|
||||||
|
* <p>
|
||||||
|
* <em><strong>WARNING</strong></em>: this is a terminal operation.
|
||||||
|
* If several {@code handler}s are registered, there is no guarantee that they will be invoked in order of registration.
|
||||||
|
*
|
||||||
|
* @param handler the handler that will be called with the succeeded result
|
||||||
|
* @return a reference to this, so it can be used fluently
|
||||||
|
*/
|
||||||
|
default Future<T> onSuccess(Handler<T> handler) {
|
||||||
|
return onComplete(ar -> {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
handler.handle(ar.result());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a handler to be notified of the failed result.
|
||||||
|
* <p>
|
||||||
|
* <em><strong>WARNING</strong></em>: this is a terminal operation.
|
||||||
|
* If several {@code handler}s are registered, there is no guarantee that they will be invoked in order of registration.
|
||||||
|
*
|
||||||
|
* @param handler the handler that will be called with the failed result
|
||||||
|
* @return a reference to this, so it can be used fluently
|
||||||
|
*/
|
||||||
|
default Future<T> onFailure(Handler<Throwable> handler) {
|
||||||
|
return onComplete(ar -> {
|
||||||
|
if (ar.failed()) {
|
||||||
|
handler.handle(ar.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of the operation. This will be null if the operation failed.
|
||||||
|
*
|
||||||
|
* @return the result or null if the operation failed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
T result();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Throwable describing failure. This will be null if the operation succeeded.
|
||||||
|
*
|
||||||
|
* @return the cause or null if the operation succeeded.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
Throwable cause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did it succeed?
|
||||||
|
*
|
||||||
|
* @return true if it succeded or false otherwise
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
boolean succeeded();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did it fail?
|
||||||
|
*
|
||||||
|
* @return true if it failed or false otherwise
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
boolean failed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link #compose(Function)}.
|
||||||
|
*/
|
||||||
|
default <U> Future<U> flatMap(Function<T, Future<U>> mapper) {
|
||||||
|
return compose(mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose this future with a {@code mapper} function.<p>
|
||||||
|
*
|
||||||
|
* When this future (the one on which {@code compose} is called) succeeds, the {@code mapper} will be called with
|
||||||
|
* the completed value and this mapper returns another future object. This returned future completion will complete
|
||||||
|
* the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* If the {@code mapper} throws an exception, the returned future will be failed with this exception.<p>
|
||||||
|
*
|
||||||
|
* When this future fails, the failure will be propagated to the returned future and the {@code mapper}
|
||||||
|
* will not be called.
|
||||||
|
*
|
||||||
|
* @param mapper the mapper function
|
||||||
|
* @return the composed future
|
||||||
|
*/
|
||||||
|
default <U> Future<U> compose(Function<T, Future<U>> mapper) {
|
||||||
|
return compose(mapper, Future::failedFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a failure of this Future by returning the result of another Future.
|
||||||
|
* If the mapper fails, then the returned future will be failed with this failure.
|
||||||
|
*
|
||||||
|
* @param mapper A function which takes the exception of a failure and returns a new future.
|
||||||
|
* @return A recovered future
|
||||||
|
*/
|
||||||
|
default Future<T> recover(Function<Throwable, Future<T>> mapper) {
|
||||||
|
return compose(Future::succeededFuture, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose this future with a {@code successMapper} and {@code failureMapper} functions.<p>
|
||||||
|
*
|
||||||
|
* When this future (the one on which {@code compose} is called) succeeds, the {@code successMapper} will be called with
|
||||||
|
* the completed value and this mapper returns another future object. This returned future completion will complete
|
||||||
|
* the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this future (the one on which {@code compose} is called) fails, the {@code failureMapper} will be called with
|
||||||
|
* the failure and this mapper returns another future object. This returned future completion will complete
|
||||||
|
* the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* If any mapper function throws an exception, the returned future will be failed with this exception.<p>
|
||||||
|
*
|
||||||
|
* @param successMapper the function mapping the success
|
||||||
|
* @param failureMapper the function mapping the failure
|
||||||
|
* @return the composed future
|
||||||
|
*/
|
||||||
|
<U> Future<U> compose(Function<T, Future<U>> successMapper, Function<Throwable, Future<U>> failureMapper);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform this future with a {@code mapper} functions.<p>
|
||||||
|
*
|
||||||
|
* When this future (the one on which {@code transform} is called) completes, the {@code mapper} will be called with
|
||||||
|
* the async result and this mapper returns another future object. This returned future completion will complete
|
||||||
|
* the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* If any mapper function throws an exception, the returned future will be failed with this exception.<p>
|
||||||
|
*
|
||||||
|
* @param mapper the function mapping the future
|
||||||
|
* @return the transformed future
|
||||||
|
*/
|
||||||
|
<U> Future<U> transform(Function<AsyncResult<T>, Future<U>> mapper);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose this future with a {@code mapper} that will be always be called.
|
||||||
|
*
|
||||||
|
* <p>When this future (the one on which {@code eventually} is called) completes, the {@code mapper} will be called
|
||||||
|
* and this mapper returns another future object. This returned future completion will complete the future returned
|
||||||
|
* by this method call with the original result of the future.
|
||||||
|
*
|
||||||
|
* <p>The outcome of the future returned by the {@code mapper} will not influence the nature
|
||||||
|
* of the returned future.
|
||||||
|
*
|
||||||
|
* @param mapper the function returning the future.
|
||||||
|
* @return the composed future
|
||||||
|
*/
|
||||||
|
<U> Future<T> eventually(Function<Void, Future<U>> mapper);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a {@code mapper} function on this future.<p>
|
||||||
|
*
|
||||||
|
* When this future succeeds, the {@code mapper} will be called with the completed value and this mapper
|
||||||
|
* returns a value. This value will complete the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* If the {@code mapper} throws an exception, the returned future will be failed with this exception.<p>
|
||||||
|
*
|
||||||
|
* When this future fails, the failure will be propagated to the returned future and the {@code mapper}
|
||||||
|
* will not be called.
|
||||||
|
*
|
||||||
|
* @param mapper the mapper function
|
||||||
|
* @return the mapped future
|
||||||
|
*/
|
||||||
|
<U> Future<U> map(Function<T, U> mapper);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the result of a future to a specific {@code value}.<p>
|
||||||
|
*
|
||||||
|
* When this future succeeds, this {@code value} will complete the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this future fails, the failure will be propagated to the returned future.
|
||||||
|
*
|
||||||
|
* @param value the value that eventually completes the mapped future
|
||||||
|
* @return the mapped future
|
||||||
|
*/
|
||||||
|
<V> Future<V> map(V value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the result of a future to {@code null}.<p>
|
||||||
|
*
|
||||||
|
* This is a conveniency for {@code future.map((T) null)} or {@code future.map((Void) null)}.<p>
|
||||||
|
*
|
||||||
|
* When this future succeeds, {@code null} will complete the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this future fails, the failure will be propagated to the returned future.
|
||||||
|
*
|
||||||
|
* @return the mapped future
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
default <V> Future<V> mapEmpty() {
|
||||||
|
return (Future<V>) AsyncResult.super.mapEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a {@code mapper} function on this future.<p>
|
||||||
|
*
|
||||||
|
* When this future fails, the {@code mapper} will be called with the completed value and this mapper
|
||||||
|
* returns a value. This value will complete the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* If the {@code mapper} throws an exception, the returned future will be failed with this exception.<p>
|
||||||
|
*
|
||||||
|
* When this future succeeds, the result will be propagated to the returned future and the {@code mapper}
|
||||||
|
* will not be called.
|
||||||
|
*
|
||||||
|
* @param mapper the mapper function
|
||||||
|
* @return the mapped future
|
||||||
|
*/
|
||||||
|
Future<T> otherwise(Function<Throwable, T> mapper);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the failure of a future to a specific {@code value}.<p>
|
||||||
|
*
|
||||||
|
* When this future fails, this {@code value} will complete the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this future succeeds, the result will be propagated to the returned future.
|
||||||
|
*
|
||||||
|
* @param value the value that eventually completes the mapped future
|
||||||
|
* @return the mapped future
|
||||||
|
*/
|
||||||
|
Future<T> otherwise(T value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the failure of a future to {@code null}.<p>
|
||||||
|
*
|
||||||
|
* This is a convenience for {@code future.otherwise((T) null)}.<p>
|
||||||
|
*
|
||||||
|
* When this future fails, the {@code null} value will complete the future returned by this method call.<p>
|
||||||
|
*
|
||||||
|
* When this future succeeds, the result will be propagated to the returned future.
|
||||||
|
*
|
||||||
|
* @return the mapped future
|
||||||
|
*/
|
||||||
|
default Future<T> otherwiseEmpty() {
|
||||||
|
return (Future<T>) AsyncResult.super.otherwiseEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the given {@code handler} upon completion.
|
||||||
|
* <p>
|
||||||
|
* If the {@code handler} throws an exception, the returned future will be failed with this exception.
|
||||||
|
*
|
||||||
|
* @param handler invoked upon completion of this future
|
||||||
|
* @return a future completed after the {@code handler} has been invoked
|
||||||
|
*/
|
||||||
|
default Future<T> andThen(Handler<AsyncResult<T>> handler) {
|
||||||
|
return transform(ar -> {
|
||||||
|
handler.handle(ar);
|
||||||
|
return (Future<T>) ar;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridges this Vert.x future to a {@link CompletionStage} instance.
|
||||||
|
* <p>
|
||||||
|
* The {@link CompletionStage} handling methods will be called from the thread that resolves this future.
|
||||||
|
*
|
||||||
|
* @return a {@link CompletionStage} that completes when this future resolves
|
||||||
|
*/
|
||||||
|
default CompletionStage<T> toCompletionStage() {
|
||||||
|
CompletableFuture<T> completableFuture = new CompletableFuture<>();
|
||||||
|
onComplete(ar -> {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
completableFuture.complete(ar.result());
|
||||||
|
} else {
|
||||||
|
completableFuture.completeExceptionally(ar.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return completableFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridges a {@link CompletionStage} object to a Vert.x future instance.
|
||||||
|
* <p>
|
||||||
|
* The Vert.x future handling methods will be called from the thread that completes {@code completionStage}.
|
||||||
|
*
|
||||||
|
* @param completionStage a completion stage
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return a Vert.x future that resolves when {@code completionStage} resolves
|
||||||
|
*/
|
||||||
|
static <T> Future<T> fromCompletionStage(CompletionStage<T> completionStage) {
|
||||||
|
Promise<T> promise = Promise.promise();
|
||||||
|
completionStage.whenComplete((value, err) -> {
|
||||||
|
if (err != null) {
|
||||||
|
promise.fail(err);
|
||||||
|
} else {
|
||||||
|
promise.complete(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridges a {@link CompletionStage} object to a Vert.x future instance.
|
||||||
|
* <p>
|
||||||
|
* The Vert.x future handling methods will be called on the provided {@code context}.
|
||||||
|
*
|
||||||
|
* @param completionStage a completion stage
|
||||||
|
* @param context a Vert.x context to dispatch to
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return a Vert.x future that resolves when {@code completionStage} resolves
|
||||||
|
*/
|
||||||
|
static <T> Future<T> fromCompletionStage(CompletionStage<T> completionStage, Context context) {
|
||||||
|
Promise<T> promise = ((ContextInternal) context).promise();
|
||||||
|
completionStage.whenComplete((value, err) -> {
|
||||||
|
if (err != null) {
|
||||||
|
promise.fail(err);
|
||||||
|
} else {
|
||||||
|
promise.complete(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
18
src/main/java/org/xbib/event/async/Handler.java
Normal file
18
src/main/java/org/xbib/event/async/Handler.java
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic event handler.
|
||||||
|
* <p>
|
||||||
|
* This interface is used heavily throughout Vert.x as a handler for all types of asynchronous occurrences.
|
||||||
|
* <p>
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Handler<E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Something has happened, so handle it.
|
||||||
|
*
|
||||||
|
* @param event the event to handle
|
||||||
|
*/
|
||||||
|
void handle(E event);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
public class NoStackTraceThrowable extends Throwable {
|
||||||
|
|
||||||
|
public NoStackTraceThrowable(String message) {
|
||||||
|
super(message, null, false, false);
|
||||||
|
}
|
||||||
|
}
|
130
src/main/java/org/xbib/event/async/Promise.java
Normal file
130
src/main/java/org/xbib/event/async/Promise.java
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.future.PromiseImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the writable side of an action that may, or may not, have occurred yet.
|
||||||
|
* <p>
|
||||||
|
* The {@link #future()} method returns the {@link Future} associated with a promise, the future
|
||||||
|
* can be used for getting notified of the promise completion and retrieve its value.
|
||||||
|
* <p>
|
||||||
|
* A promise extends {@code Handler<AsyncResult<T>>} so it can be used as a callback.
|
||||||
|
*/
|
||||||
|
public interface Promise<T> extends Handler<AsyncResult<T>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a promise that hasn't completed yet
|
||||||
|
*
|
||||||
|
* @param <T> the result type
|
||||||
|
* @return the promise
|
||||||
|
*/
|
||||||
|
static <T> Promise<T> promise() {
|
||||||
|
return new PromiseImpl<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Succeed or fail this promise with the {@link AsyncResult} event.
|
||||||
|
*
|
||||||
|
* @param asyncResult the async result to handle
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
default void handle(AsyncResult<T> asyncResult) {
|
||||||
|
if (asyncResult.succeeded()) {
|
||||||
|
complete(asyncResult.result());
|
||||||
|
} else {
|
||||||
|
fail(asyncResult.cause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the result. Any handler will be called, if there is one, and the promise will be marked as completed.
|
||||||
|
* <p/>
|
||||||
|
* Any handler set on the associated promise will be called.
|
||||||
|
*
|
||||||
|
* @param result the result
|
||||||
|
* @throws IllegalStateException when the promise is already completed
|
||||||
|
*/
|
||||||
|
default void complete(T result) {
|
||||||
|
if (!tryComplete(result)) {
|
||||||
|
throw new IllegalStateException("Result is already complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@code complete(null)}
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException when the promise is already completed
|
||||||
|
*/
|
||||||
|
default void complete() {
|
||||||
|
if (!tryComplete()) {
|
||||||
|
throw new IllegalStateException("Result is already complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the failure. Any handler will be called, if there is one, and the future will be marked as completed.
|
||||||
|
*
|
||||||
|
* @param cause the failure cause
|
||||||
|
* @throws IllegalStateException when the promise is already completed
|
||||||
|
*/
|
||||||
|
default void fail(Throwable cause) {
|
||||||
|
if (!tryFail(cause)) {
|
||||||
|
throw new IllegalStateException("Result is already complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@link #fail(Throwable)} with the {@code message}.
|
||||||
|
*
|
||||||
|
* @param message the failure message
|
||||||
|
* @throws IllegalStateException when the promise is already completed
|
||||||
|
*/
|
||||||
|
default void fail(String message) {
|
||||||
|
if (!tryFail(message)) {
|
||||||
|
throw new IllegalStateException("Result is already complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #complete(Object)} but returns {@code false} when the promise is already completed instead of throwing
|
||||||
|
* an {@link IllegalStateException}, it returns {@code true} otherwise.
|
||||||
|
*
|
||||||
|
* @param result the result
|
||||||
|
* @return {@code false} when the future is already completed
|
||||||
|
*/
|
||||||
|
boolean tryComplete(T result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@code tryComplete(null)}.
|
||||||
|
*
|
||||||
|
* @return {@code false} when the future is already completed
|
||||||
|
*/
|
||||||
|
default boolean tryComplete() {
|
||||||
|
return tryComplete(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #fail(Throwable)} but returns {@code false} when the promise is already completed instead of throwing
|
||||||
|
* an {@link IllegalStateException}, it returns {@code true} otherwise.
|
||||||
|
*
|
||||||
|
* @param cause the failure cause
|
||||||
|
* @return {@code false} when the future is already completed
|
||||||
|
*/
|
||||||
|
boolean tryFail(Throwable cause);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@link #fail(Throwable)} with the {@code message}.
|
||||||
|
*
|
||||||
|
* @param message the failure message
|
||||||
|
* @return false when the future is already completed
|
||||||
|
*/
|
||||||
|
default boolean tryFail(String message) {
|
||||||
|
return tryFail(new NoStackTraceThrowable(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link Future} associated with this promise, it can be used to be aware of the promise completion
|
||||||
|
*/
|
||||||
|
Future<T> future();
|
||||||
|
|
||||||
|
}
|
40
src/main/java/org/xbib/event/async/TimeoutStream.java
Normal file
40
src/main/java/org/xbib/event/async/TimeoutStream.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
import org.xbib.event.async.streams.ReadStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A timeout stream is triggered by a timer, the {@link Handler} will be call when the timer is fired,
|
||||||
|
* it can be once or several times depending on the nature of the timer related to this stream. The
|
||||||
|
* {@link ReadStream#endHandler(Handler)} will be called after the timer handler has been called.
|
||||||
|
* <p>
|
||||||
|
* Pausing the timer inhibits the timer shots until the stream is resumed. Setting a null handler callback cancels
|
||||||
|
* the timer.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface TimeoutStream extends ReadStream<Long> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TimeoutStream exceptionHandler(Handler<Throwable> handler);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TimeoutStream handler(Handler<Long> handler);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TimeoutStream pause();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TimeoutStream resume();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TimeoutStream fetch(long amount);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TimeoutStream endHandler(Handler<Void> endHandler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the timeout. Note this has the same effect as calling {@link #handler(Handler)} with a null
|
||||||
|
* argument.
|
||||||
|
*/
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
}
|
47
src/main/java/org/xbib/event/async/WorkerExecutor.java
Normal file
47
src/main/java/org/xbib/event/async/WorkerExecutor.java
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package org.xbib.event.async;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An executor for executing blocking code.<p>
|
||||||
|
*
|
||||||
|
* It provides the same <code>executeBlocking</code> operation than {@link Context} and
|
||||||
|
* {@link Async} but on a separate worker pool.<p>
|
||||||
|
*/
|
||||||
|
public interface WorkerExecutor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely execute some blocking code.
|
||||||
|
* <p>
|
||||||
|
* Executes the blocking code in the handler {@code blockingCodeHandler} using a thread from the worker pool.
|
||||||
|
* <p>
|
||||||
|
* When the code is complete the handler {@code resultHandler} will be called with the result on the original context
|
||||||
|
* (i.e. on the original event loop of the caller).
|
||||||
|
* <p>
|
||||||
|
* A {@code Future} instance is passed into {@code blockingCodeHandler}. When the blocking code successfully completes,
|
||||||
|
* the handler should call the {@link Promise#complete} or {@link Promise#complete(Object)} method, or the {@link Promise#fail}
|
||||||
|
* method if it failed.
|
||||||
|
* <p>
|
||||||
|
* In the {@code blockingCodeHandler} the current context remains the original context and therefore any task
|
||||||
|
* scheduled in the {@code blockingCodeHandler} will be executed on the this context and not on the worker thread.
|
||||||
|
*
|
||||||
|
* @param blockingCodeHandler handler representing the blocking code to run
|
||||||
|
* @param ordered if true then if executeBlocking is called several times on the same context, the executions
|
||||||
|
* for that context will be executed serially, not in parallel. if false then they will be no ordering
|
||||||
|
* guarantees
|
||||||
|
* @param <T> the type of the result
|
||||||
|
* @return a future notified with the result
|
||||||
|
*/
|
||||||
|
<T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler, boolean ordered);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #executeBlocking(Handler, boolean)} called with ordered = true.
|
||||||
|
*/
|
||||||
|
default <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler) {
|
||||||
|
return executeBlocking(blockingCodeHandler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the executor.
|
||||||
|
*/
|
||||||
|
Future<Void> close();
|
||||||
|
|
||||||
|
}
|
110
src/main/java/org/xbib/event/async/impl/AsyncBuilder.java
Normal file
110
src/main/java/org/xbib/event/async/impl/AsyncBuilder.java
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Async;
|
||||||
|
import org.xbib.event.async.AsyncOptions;
|
||||||
|
import org.xbib.event.async.spi.AsyncThreadFactory;
|
||||||
|
import org.xbib.event.async.spi.ExecutorServiceFactory;
|
||||||
|
import org.xbib.event.loop.EventLoopGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async builder for creating instances with SPI overrides.
|
||||||
|
*/
|
||||||
|
public class AsyncBuilder {
|
||||||
|
|
||||||
|
private final AsyncOptions options;
|
||||||
|
private AsyncThreadFactory threadFactory;
|
||||||
|
private ExecutorServiceFactory executorServiceFactory;
|
||||||
|
|
||||||
|
private EventLoopGroup eventLoopGroup;
|
||||||
|
|
||||||
|
public AsyncBuilder() {
|
||||||
|
this(new AsyncOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncBuilder(AsyncOptions options) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the options
|
||||||
|
*/
|
||||||
|
public AsyncOptions options() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@code AsyncThreadFactory} to use
|
||||||
|
*/
|
||||||
|
public AsyncThreadFactory threadFactory() {
|
||||||
|
return threadFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@code AsyncThreadFactory} instance to use.
|
||||||
|
* @param factory the metrics
|
||||||
|
* @return this builder instance
|
||||||
|
*/
|
||||||
|
public AsyncBuilder threadFactory(AsyncThreadFactory factory) {
|
||||||
|
this.threadFactory = factory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@code ExecutorServiceFactory} to use
|
||||||
|
*/
|
||||||
|
public ExecutorServiceFactory executorServiceFactory() {
|
||||||
|
return executorServiceFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@code ExecutorServiceFactory} instance to use.
|
||||||
|
* @param factory the factory
|
||||||
|
* @return this builder instance
|
||||||
|
*/
|
||||||
|
public AsyncBuilder executorServiceFactory(ExecutorServiceFactory factory) {
|
||||||
|
this.executorServiceFactory = factory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventLoopGroup eventLoopGroup() {
|
||||||
|
return eventLoopGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncBuilder eventLoopGroup(EventLoopGroup eventLoopGroup) {
|
||||||
|
this.eventLoopGroup = eventLoopGroup;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build and return the instance
|
||||||
|
*/
|
||||||
|
public Async newInstance() {
|
||||||
|
AsyncImpl async = new AsyncImpl(options, threadFactory, executorServiceFactory, eventLoopGroup);
|
||||||
|
async.init();
|
||||||
|
return async;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the service providers.
|
||||||
|
* @return this builder instance
|
||||||
|
*/
|
||||||
|
public AsyncBuilder init() {
|
||||||
|
initThreadFactory();
|
||||||
|
initExecutorServiceFactory();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initThreadFactory() {
|
||||||
|
if (threadFactory != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
threadFactory = AsyncThreadFactory.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initExecutorServiceFactory() {
|
||||||
|
if (executorServiceFactory != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
executorServiceFactory = ExecutorServiceFactory.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
558
src/main/java/org/xbib/event/async/impl/AsyncImpl.java
Normal file
558
src/main/java/org/xbib/event/async/impl/AsyncImpl.java
Normal file
|
@ -0,0 +1,558 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Async;
|
||||||
|
import org.xbib.event.async.AsyncOptions;
|
||||||
|
import org.xbib.event.async.Closeable;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.Promise;
|
||||||
|
import org.xbib.event.async.TimeoutStream;
|
||||||
|
import org.xbib.event.async.impl.future.PromiseInternal;
|
||||||
|
import org.xbib.event.async.spi.AsyncThreadFactory;
|
||||||
|
import org.xbib.event.async.spi.ExecutorServiceFactory;
|
||||||
|
import org.xbib.event.loop.EventLoop;
|
||||||
|
import org.xbib.event.loop.EventLoopGroup;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class AsyncImpl implements AsyncInternal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context dispatch info for context running with external threads.
|
||||||
|
*/
|
||||||
|
static final ThreadLocal<ContextDispatch> nonContextDispatch = new ThreadLocal<>();
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(AsyncImpl.class.getName());
|
||||||
|
|
||||||
|
private final ConcurrentMap<Long, InternalTimerHandler> timeouts = new ConcurrentHashMap<>();
|
||||||
|
private final AtomicLong timeoutCounter = new AtomicLong(0);
|
||||||
|
final WorkerPool workerPool;
|
||||||
|
final WorkerPool internalWorkerPool;
|
||||||
|
|
||||||
|
private final EventLoopGroup eventLoopGroup;
|
||||||
|
private boolean closed;
|
||||||
|
private volatile Handler<Throwable> exceptionHandler;
|
||||||
|
private final CloseFuture closeFuture;
|
||||||
|
private final ThreadLocal<WeakReference<ContextInternal>> stickyContext = new ThreadLocal<>();
|
||||||
|
private final boolean disableTCCL;
|
||||||
|
|
||||||
|
AsyncImpl(AsyncOptions options,
|
||||||
|
AsyncThreadFactory threadFactory,
|
||||||
|
ExecutorServiceFactory executorServiceFactory,
|
||||||
|
EventLoopGroup eventLoopGroup) {
|
||||||
|
this.eventLoopGroup = eventLoopGroup;
|
||||||
|
if (Async.currentContext() != null) {
|
||||||
|
log.log(Level.WARNING, "You're already on a context, are you sure you want to create a new instance?");
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean useDaemonThread = options.getUseDaemonThread();
|
||||||
|
int workerPoolSize = options.getWorkerPoolSize();
|
||||||
|
int internalBlockingPoolSize = options.getInternalBlockingPoolSize();
|
||||||
|
long maxEventLoopExecuteTime = options.getMaxEventLoopExecuteTime();
|
||||||
|
TimeUnit maxEventLoopExecuteTimeUnit = options.getMaxEventLoopExecuteTimeUnit();
|
||||||
|
TimeUnit maxWorkerExecuteTimeUnit = options.getMaxWorkerExecuteTimeUnit();
|
||||||
|
long maxWorkerExecuteTime = options.getMaxWorkerExecuteTime();
|
||||||
|
|
||||||
|
ThreadFactory workerThreadFactory = createThreadFactory(threadFactory, useDaemonThread, maxWorkerExecuteTime, maxWorkerExecuteTimeUnit, "async-worker-thread-", true);
|
||||||
|
ExecutorService workerExec = executorServiceFactory.createExecutor(workerThreadFactory, workerPoolSize, workerPoolSize);
|
||||||
|
ThreadFactory internalWorkerThreadFactory = createThreadFactory(threadFactory, useDaemonThread, maxWorkerExecuteTime, maxWorkerExecuteTimeUnit, "async-internal-blocking-", true);
|
||||||
|
ExecutorService internalWorkerExec = executorServiceFactory.createExecutor(internalWorkerThreadFactory, internalBlockingPoolSize, internalBlockingPoolSize);
|
||||||
|
|
||||||
|
closeFuture = new CloseFuture(log);
|
||||||
|
|
||||||
|
internalWorkerPool = new WorkerPool(internalWorkerExec);
|
||||||
|
workerPool = new WorkerPool(workerExec);
|
||||||
|
int defaultWorkerPoolSize = options.getWorkerPoolSize();
|
||||||
|
disableTCCL = options.getDisableTCCL();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimeoutStream periodicStream(long initialDelay, long delay) {
|
||||||
|
return new TimeoutStreamImpl(initialDelay, delay, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long setTimer(long delay, Handler<Long> handler) {
|
||||||
|
ContextInternal ctx = getOrCreateContext();
|
||||||
|
return scheduleTimeout(ctx, false, delay, TimeUnit.MILLISECONDS, false, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimeoutStream timerStream(long delay) {
|
||||||
|
return new TimeoutStreamImpl(delay, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long setPeriodic(long initialDelay, long delay, Handler<Long> handler) {
|
||||||
|
ContextInternal ctx = getOrCreateContext();
|
||||||
|
return scheduleTimeout(ctx, true, initialDelay, delay, TimeUnit.MILLISECONDS, false, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> PromiseInternal<T> promise() {
|
||||||
|
ContextInternal context = getOrCreateContext();
|
||||||
|
return context.promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> PromiseInternal<T> promise(Promise<T> p) {
|
||||||
|
if (p instanceof PromiseInternal) {
|
||||||
|
PromiseInternal<T> promise = (PromiseInternal<T>) p;
|
||||||
|
if (promise.context() != null) {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PromiseInternal<T> promise = promise();
|
||||||
|
promise.future().onComplete(p);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runOnContext(Handler<Void> task) {
|
||||||
|
ContextInternal context = getOrCreateContext();
|
||||||
|
context.runOnContext(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The background pool is used for making blocking calls to legacy synchronous APIs
|
||||||
|
public WorkerPool getWorkerPool() {
|
||||||
|
return workerPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorkerPool getInternalWorkerPool() {
|
||||||
|
return internalWorkerPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContextInternal getOrCreateContext() {
|
||||||
|
ContextInternal ctx = getContext();
|
||||||
|
if (ctx == null) {
|
||||||
|
ctx = createEventLoopContext();
|
||||||
|
stickyContext.set(new WeakReference<>(ctx));
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean cancelTimer(long id) {
|
||||||
|
InternalTimerHandler handler = timeouts.get(id);
|
||||||
|
if (handler != null) {
|
||||||
|
return handler.cancel();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventLoopContext createEventLoopContext() {
|
||||||
|
return createEventLoopContext(closeFuture, null, Thread.currentThread().getContextClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventLoopContext createEventLoopContext(CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl) {
|
||||||
|
return new EventLoopContext(this, eventLoopGroup.next(), internalWorkerPool, workerPool != null ? workerPool : this.workerPool, closeFuture, disableTCCL ? null : tccl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long scheduleTimeout(ContextInternal context,
|
||||||
|
boolean periodic,
|
||||||
|
long initialDelay,
|
||||||
|
long delay,
|
||||||
|
TimeUnit timeUnit,
|
||||||
|
boolean addCloseHook,
|
||||||
|
Handler<Long> handler) {
|
||||||
|
if (delay < 1) {
|
||||||
|
throw new IllegalArgumentException("Cannot schedule a timer with delay < 1 ms");
|
||||||
|
}
|
||||||
|
if (initialDelay < 0) {
|
||||||
|
throw new IllegalArgumentException("Cannot schedule a timer with initialDelay < 0");
|
||||||
|
}
|
||||||
|
long timerId = timeoutCounter.getAndIncrement();
|
||||||
|
InternalTimerHandler task = new InternalTimerHandler(timerId, handler, periodic, context);
|
||||||
|
timeouts.put(timerId, task);
|
||||||
|
if (addCloseHook) {
|
||||||
|
context.addCloseHook(task);
|
||||||
|
}
|
||||||
|
EventLoop el = context.eventLoop();
|
||||||
|
if (periodic) {
|
||||||
|
task.future = el.scheduleAtFixedRate(task, initialDelay, delay, timeUnit);
|
||||||
|
} else {
|
||||||
|
task.future = el.schedule(task, delay, timeUnit);
|
||||||
|
}
|
||||||
|
return task.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long scheduleTimeout(ContextInternal context,
|
||||||
|
boolean periodic,
|
||||||
|
long delay,
|
||||||
|
TimeUnit timeUnit,
|
||||||
|
boolean addCloseHook,
|
||||||
|
Handler<Long> handler) {
|
||||||
|
return scheduleTimeout(context, periodic, delay, delay, timeUnit, addCloseHook, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextInternal getContext() {
|
||||||
|
ContextInternal context = ContextInternal.current();
|
||||||
|
if (context != null && context.owner() == this) {
|
||||||
|
return context;
|
||||||
|
} else {
|
||||||
|
WeakReference<ContextInternal> ref = stickyContext.get();
|
||||||
|
return ref != null ? ref.get() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Future<Void> close() {
|
||||||
|
// Create this promise purposely without a context because the close operation will close thread pools
|
||||||
|
if (closed) {
|
||||||
|
// Just call the handler directly since pools shutdown
|
||||||
|
return Future.succeededFuture();
|
||||||
|
}
|
||||||
|
closed = true;
|
||||||
|
Future<Void> fut = closeFuture
|
||||||
|
.close();
|
||||||
|
Future<Void> val = fut;
|
||||||
|
Promise<Void> p = Promise.promise();
|
||||||
|
val.onComplete(ar -> {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
p.complete();
|
||||||
|
} else {
|
||||||
|
p.fail(ar.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return p.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timers are stored in the {@link #timeouts} map at creation time.
|
||||||
|
* <p/>
|
||||||
|
* Timers are removed from the {@link #timeouts} map when they are cancelled or are fired. The thread
|
||||||
|
* removing the timer successfully owns the timer termination (i.e cancel or timer) to avoid race conditions
|
||||||
|
* between timeout and cancellation.
|
||||||
|
* <p/>
|
||||||
|
* This class does not rely on the internal {@link #future} for the termination to handle the worker case
|
||||||
|
* since the actual timer {@link #handler} execution is scheduled when the {@link #future} executes.
|
||||||
|
*/
|
||||||
|
class InternalTimerHandler implements Handler<Void>, Closeable, Runnable {
|
||||||
|
|
||||||
|
private final Handler<Long> handler;
|
||||||
|
private final boolean periodic;
|
||||||
|
private final long id;
|
||||||
|
private final ContextInternal context;
|
||||||
|
private final AtomicBoolean disposed = new AtomicBoolean();
|
||||||
|
private volatile java.util.concurrent.Future<?> future;
|
||||||
|
|
||||||
|
InternalTimerHandler(long id, Handler<Long> runnable, boolean periodic, ContextInternal context) {
|
||||||
|
this.context = context;
|
||||||
|
this.id = id;
|
||||||
|
this.handler = runnable;
|
||||||
|
this.periodic = periodic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
context.emit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(Void v) {
|
||||||
|
if (periodic) {
|
||||||
|
if (!disposed.get()) {
|
||||||
|
handler.handle(id);
|
||||||
|
}
|
||||||
|
} else if (disposed.compareAndSet(false, true)) {
|
||||||
|
timeouts.remove(id);
|
||||||
|
try {
|
||||||
|
handler.handle(id);
|
||||||
|
} finally {
|
||||||
|
// Clean up after it's fired
|
||||||
|
context.removeCloseHook(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean cancel() {
|
||||||
|
boolean cancelled = tryCancel();
|
||||||
|
if (cancelled) {
|
||||||
|
context.removeCloseHook(this);
|
||||||
|
}
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryCancel() {
|
||||||
|
if (disposed.compareAndSet(false, true)) {
|
||||||
|
timeouts.remove(id);
|
||||||
|
future.cancel(false);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called via Context close hook when Verticle is undeployed
|
||||||
|
public void close(Promise<Void> completion) {
|
||||||
|
tryCancel();
|
||||||
|
completion.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ThreadFactory createThreadFactory(AsyncThreadFactory threadFactory, Boolean useDaemonThread, long maxExecuteTime, TimeUnit maxExecuteTimeUnit, String prefix, boolean worker) {
|
||||||
|
AtomicInteger threadCount = new AtomicInteger(0);
|
||||||
|
return runnable -> {
|
||||||
|
AsyncThread thread = threadFactory.newVertxThread(runnable, prefix + threadCount.getAndIncrement(), worker, maxExecuteTime, maxExecuteTimeUnit);
|
||||||
|
if (useDaemonThread != null && thread.isDaemon() != useDaemonThread) {
|
||||||
|
thread.setDaemon(useDaemonThread);
|
||||||
|
}
|
||||||
|
return thread;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Async exceptionHandler(Handler<Throwable> handler) {
|
||||||
|
exceptionHandler = handler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Handler<Throwable> exceptionHandler() {
|
||||||
|
return exceptionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CloseFuture closeFuture() {
|
||||||
|
return closeFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CloseFuture resolveCloseFuture() {
|
||||||
|
ContextInternal context = getContext();
|
||||||
|
return context != null ? context.closeFuture() : closeFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the {@code task} disabling the thread-local association for the duration
|
||||||
|
* of the execution. {@link Async#currentContext()} will return {@code null},
|
||||||
|
* @param task the task to execute
|
||||||
|
* @throws IllegalStateException if the current thread is not a Async thread
|
||||||
|
*/
|
||||||
|
void executeIsolated(Handler<Void> task) {
|
||||||
|
if (Thread.currentThread() instanceof AsyncThread) {
|
||||||
|
ContextInternal prev = beginDispatch(null);
|
||||||
|
try {
|
||||||
|
task.handle(null);
|
||||||
|
} finally {
|
||||||
|
endDispatch(prev);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
task.handle(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ContextDispatch {
|
||||||
|
ContextInternal context;
|
||||||
|
ClassLoader topLevelTCCL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin the emission of a context event.
|
||||||
|
* <p>
|
||||||
|
* This is a low level interface that should not be used, instead {@link ContextInternal#dispatch(Object, Handler)}
|
||||||
|
* shall be used.
|
||||||
|
*
|
||||||
|
* @param context the context on which the event is emitted on
|
||||||
|
* @return the current context that shall be restored
|
||||||
|
*/
|
||||||
|
ContextInternal beginDispatch(ContextInternal context) {
|
||||||
|
Thread thread = Thread.currentThread();
|
||||||
|
ContextInternal prev;
|
||||||
|
if (thread instanceof AsyncThread) {
|
||||||
|
AsyncThread asyncThread = (AsyncThread) thread;
|
||||||
|
prev = asyncThread.context;
|
||||||
|
asyncThread.executeStart();
|
||||||
|
asyncThread.context = context;
|
||||||
|
if (!disableTCCL) {
|
||||||
|
if (prev == null) {
|
||||||
|
asyncThread.topLevelTCCL = Thread.currentThread().getContextClassLoader();
|
||||||
|
}
|
||||||
|
if (context != null) {
|
||||||
|
thread.setContextClassLoader(context.classLoader());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prev = beginDispatch2(thread, context);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContextInternal beginDispatch2(Thread thread, ContextInternal context) {
|
||||||
|
ContextDispatch current = nonContextDispatch.get();
|
||||||
|
ContextInternal prev;
|
||||||
|
if (current != null) {
|
||||||
|
prev = current.context;
|
||||||
|
} else {
|
||||||
|
current = new ContextDispatch();
|
||||||
|
nonContextDispatch.set(current);
|
||||||
|
prev = null;
|
||||||
|
}
|
||||||
|
current.context = context;
|
||||||
|
if (!disableTCCL) {
|
||||||
|
if (prev == null) {
|
||||||
|
current.topLevelTCCL = Thread.currentThread().getContextClassLoader();
|
||||||
|
}
|
||||||
|
thread.setContextClassLoader(context.classLoader());
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End the emission of a context task.
|
||||||
|
* <p>
|
||||||
|
* This is a low level interface that should not be used, instead {@link ContextInternal#dispatch(Object, Handler)}
|
||||||
|
* shall be used.
|
||||||
|
*
|
||||||
|
* @param prev the previous context thread to restore, might be {@code null}
|
||||||
|
*/
|
||||||
|
void endDispatch(ContextInternal prev) {
|
||||||
|
Thread thread = Thread.currentThread();
|
||||||
|
if (thread instanceof AsyncThread) {
|
||||||
|
AsyncThread asyncThread = (AsyncThread) thread;
|
||||||
|
asyncThread.context = prev;
|
||||||
|
if (!disableTCCL) {
|
||||||
|
ClassLoader tccl;
|
||||||
|
if (prev == null) {
|
||||||
|
tccl = asyncThread.topLevelTCCL;
|
||||||
|
asyncThread.topLevelTCCL = null;
|
||||||
|
} else {
|
||||||
|
tccl = prev.classLoader();
|
||||||
|
}
|
||||||
|
Thread.currentThread().setContextClassLoader(tccl);
|
||||||
|
}
|
||||||
|
asyncThread.executeEnd();
|
||||||
|
} else {
|
||||||
|
endDispatch2(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endDispatch2(ContextInternal prev) {
|
||||||
|
ClassLoader tccl;
|
||||||
|
ContextDispatch current = nonContextDispatch.get();
|
||||||
|
if (prev != null) {
|
||||||
|
current.context = prev;
|
||||||
|
tccl = prev.classLoader();
|
||||||
|
} else {
|
||||||
|
nonContextDispatch.remove();
|
||||||
|
tccl = current.topLevelTCCL;
|
||||||
|
}
|
||||||
|
if (!disableTCCL) {
|
||||||
|
Thread.currentThread().setContextClassLoader(tccl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* This class is optimised for performance when used on the same event loop that is was passed to the handler with.
|
||||||
|
* However it can be used safely from other threads.
|
||||||
|
*
|
||||||
|
* The internal state is protected using the synchronized keyword. If always used on the same event loop, then
|
||||||
|
* we benefit from biased locking which makes the overhead of synchronized near zero.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private class TimeoutStreamImpl implements TimeoutStream, Handler<Long> {
|
||||||
|
|
||||||
|
private final long initialDelay;
|
||||||
|
private final long delay;
|
||||||
|
private final boolean periodic;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Handler<Long> handler;
|
||||||
|
private Handler<Void> endHandler;
|
||||||
|
private long demand;
|
||||||
|
|
||||||
|
public TimeoutStreamImpl(long delay, boolean periodic) {
|
||||||
|
this(delay, delay, periodic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeoutStreamImpl(long initialDelay, long delay, boolean periodic) {
|
||||||
|
this.initialDelay = initialDelay;
|
||||||
|
this.delay = delay;
|
||||||
|
this.periodic = periodic;
|
||||||
|
this.demand = Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void handle(Long event) {
|
||||||
|
try {
|
||||||
|
if (demand > 0) {
|
||||||
|
demand--;
|
||||||
|
handler.handle(event);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!periodic && endHandler != null) {
|
||||||
|
endHandler.handle(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized TimeoutStream fetch(long amount) {
|
||||||
|
demand += amount;
|
||||||
|
if (demand < 0) {
|
||||||
|
demand = Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimeoutStream exceptionHandler(Handler<Throwable> handler) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
if (id != null) {
|
||||||
|
AsyncImpl.this.cancelTimer(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized TimeoutStream handler(Handler<Long> handler) {
|
||||||
|
if (handler != null) {
|
||||||
|
if (id != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
ContextInternal ctx = getOrCreateContext();
|
||||||
|
this.handler = handler;
|
||||||
|
this.id = scheduleTimeout(ctx, periodic, initialDelay, delay, TimeUnit.MILLISECONDS, false, this);
|
||||||
|
} else {
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized TimeoutStream pause() {
|
||||||
|
demand = 0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized TimeoutStream resume() {
|
||||||
|
demand = Long.MAX_VALUE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized TimeoutStream endHandler(Handler<Void> endHandler) {
|
||||||
|
this.endHandler = endHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
src/main/java/org/xbib/event/async/impl/AsyncInternal.java
Normal file
52
src/main/java/org/xbib/event/async/impl/AsyncInternal.java
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Async;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.Promise;
|
||||||
|
import org.xbib.event.async.impl.future.PromiseInternal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface provides services for internal use only.
|
||||||
|
* It is not part of the public API and should not be used by
|
||||||
|
* developers.
|
||||||
|
*/
|
||||||
|
public interface AsyncInternal extends Async {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a promise associated with the context returned by {@link #getOrCreateContext()}.
|
||||||
|
*/
|
||||||
|
<T> PromiseInternal<T> promise();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a promise associated with the context returned by {@link #getOrCreateContext()} or the {@code handler}
|
||||||
|
* if that handler is already an instance of {@code PromiseInternal}
|
||||||
|
*/
|
||||||
|
<T> PromiseInternal<T> promise(Promise<T> promise);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ContextInternal getOrCreateContext();
|
||||||
|
|
||||||
|
WorkerPool getWorkerPool();
|
||||||
|
|
||||||
|
WorkerPool getInternalWorkerPool();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current context
|
||||||
|
* @return the context
|
||||||
|
*/
|
||||||
|
ContextInternal getContext();
|
||||||
|
|
||||||
|
default <T> Future<T> executeBlockingInternal(Handler<Promise<T>> blockingCodeHandler) {
|
||||||
|
ContextInternal context = getOrCreateContext();
|
||||||
|
return context.executeBlockingInternal(blockingCodeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> Future<T> executeBlockingInternal(Handler<Promise<T>> blockingCodeHandler, boolean ordered) {
|
||||||
|
ContextInternal context = getOrCreateContext();
|
||||||
|
return context.executeBlockingInternal(blockingCodeHandler, ordered);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseFuture closeFuture();
|
||||||
|
|
||||||
|
}
|
56
src/main/java/org/xbib/event/async/impl/AsyncThread.java
Normal file
56
src/main/java/org/xbib/event/async/impl/AsyncThread.java
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.thread.FastThreadLocalThread;
|
||||||
|
import org.xbib.event.thread.ThreadInfo;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class AsyncThread extends FastThreadLocalThread {
|
||||||
|
|
||||||
|
private final boolean worker;
|
||||||
|
final ThreadInfo info;
|
||||||
|
ContextInternal context;
|
||||||
|
ClassLoader topLevelTCCL;
|
||||||
|
|
||||||
|
public AsyncThread(Runnable target, String name, boolean worker, long maxExecTime, TimeUnit maxExecTimeUnit) {
|
||||||
|
super(target, name);
|
||||||
|
this.worker = worker;
|
||||||
|
this.info = new ThreadInfo(maxExecTimeUnit, maxExecTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current context of this thread, this method must be called from the current thread
|
||||||
|
*/
|
||||||
|
ContextInternal context() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeStart() {
|
||||||
|
if (context == null) {
|
||||||
|
info.startTime = System.nanoTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeEnd() {
|
||||||
|
if (context == null) {
|
||||||
|
info.startTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long startTime() {
|
||||||
|
return info.startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWorker() {
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long maxExecTime() {
|
||||||
|
return info.maxExecTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeUnit maxExecTimeUnit() {
|
||||||
|
return info.maxExecTimeUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
176
src/main/java/org/xbib/event/async/impl/CloseFuture.java
Normal file
176
src/main/java/org/xbib/event/async/impl/CloseFuture.java
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Closeable;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Promise;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A close future object is a state machine managing the closing sequence of a resource. A close future can be closed
|
||||||
|
* explicitly with the {@link #close()} method or when the future is unreachable in order to release the resource.
|
||||||
|
*
|
||||||
|
* <p> A closed future holds a set of nested {@link Closeable} that are processed when the future is closed. When a close
|
||||||
|
* future is closed, nested {@link Closeable} will be closed and the close future will notify the completion of the close
|
||||||
|
* sequence after the nested closeables are closed.
|
||||||
|
*/
|
||||||
|
public class CloseFuture extends NestedCloseable implements Closeable {
|
||||||
|
|
||||||
|
private final Logger log;
|
||||||
|
private final Promise<Void> promise = Promise.promise();
|
||||||
|
private boolean closed;
|
||||||
|
private Map<Closeable, CloseFuture> children;
|
||||||
|
|
||||||
|
public CloseFuture() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloseFuture(Logger log) {
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a {@code child} closeable, notified when this instance is closed.
|
||||||
|
*
|
||||||
|
* @param child the child closeable to add
|
||||||
|
*/
|
||||||
|
public synchronized void add(Closeable child) {
|
||||||
|
if (closed) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (child instanceof NestedCloseable) {
|
||||||
|
NestedCloseable base = (NestedCloseable) child;
|
||||||
|
synchronized (base) {
|
||||||
|
if (base.owner != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
base.owner = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (children == null) {
|
||||||
|
children = new HashMap<>();
|
||||||
|
}
|
||||||
|
children.put(child, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an existing {@code nested} closeable.
|
||||||
|
*
|
||||||
|
* @param nested the closeable to remove
|
||||||
|
*/
|
||||||
|
public boolean remove(Closeable nested) {
|
||||||
|
if (nested instanceof NestedCloseable) {
|
||||||
|
NestedCloseable base = (NestedCloseable) nested;
|
||||||
|
synchronized (base) {
|
||||||
|
if (base.owner == this) {
|
||||||
|
base.owner = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (children != null) {
|
||||||
|
return children.remove(nested) != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the future is closed.
|
||||||
|
*/
|
||||||
|
public synchronized boolean isClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the future completed after completion of all close hooks.
|
||||||
|
*/
|
||||||
|
public Future<Void> future() {
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run all close hooks, after completion of all hooks, the future is closed.
|
||||||
|
*
|
||||||
|
* @return the future completed after completion of all close hooks
|
||||||
|
*/
|
||||||
|
public Future<Void> close() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (closed) {
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
cascadeClose();
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cascadeClose() {
|
||||||
|
List<Closeable> toClose = Collections.emptyList();
|
||||||
|
synchronized (this) {
|
||||||
|
if (children != null) {
|
||||||
|
toClose = new ArrayList<>(children.keySet());
|
||||||
|
}
|
||||||
|
children = null;
|
||||||
|
}
|
||||||
|
// We want an immutable version of the list holding strong references to avoid racing against finalization
|
||||||
|
int num = toClose.size();
|
||||||
|
if (num > 0) {
|
||||||
|
AtomicInteger count = new AtomicInteger();
|
||||||
|
for (Closeable hook : toClose) {
|
||||||
|
// Clear the reference before notifying to avoid a callback to this
|
||||||
|
if (hook instanceof NestedCloseable) {
|
||||||
|
NestedCloseable base = (NestedCloseable) hook;
|
||||||
|
synchronized (base) {
|
||||||
|
base.owner = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Promise<Void> p = Promise.promise();
|
||||||
|
p.future().onComplete(ar -> {
|
||||||
|
if (count.incrementAndGet() == num) {
|
||||||
|
unregisterFromOwner();
|
||||||
|
promise.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
hook.close(p);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (log != null) {
|
||||||
|
log.log(Level.WARNING, "Failed to run close hook", t);
|
||||||
|
}
|
||||||
|
p.tryFail(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unregisterFromOwner();
|
||||||
|
promise.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterFromOwner() {
|
||||||
|
CloseFuture owner;
|
||||||
|
synchronized (this) {
|
||||||
|
owner = super.owner;
|
||||||
|
}
|
||||||
|
if (owner != null) {
|
||||||
|
owner.remove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the close hooks, this method should not be called directly instead it should be called when
|
||||||
|
* this close future is added to another close future.
|
||||||
|
*
|
||||||
|
* @param promise called when all hooks have been executed
|
||||||
|
*/
|
||||||
|
public void close(Promise<Void> promise) {
|
||||||
|
close().onComplete(promise);
|
||||||
|
}
|
||||||
|
}
|
189
src/main/java/org/xbib/event/async/impl/ContextBase.java
Normal file
189
src/main/java/org/xbib/event/async/impl/ContextBase.java
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Context;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.Promise;
|
||||||
|
import org.xbib.event.loop.EventLoop;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for {@link Context} implementations.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class ContextBase implements ContextInternal {
|
||||||
|
|
||||||
|
private final AsyncInternal owner;
|
||||||
|
private final CloseFuture closeFuture;
|
||||||
|
private final ClassLoader tccl;
|
||||||
|
private final EventLoop eventLoop;
|
||||||
|
private ConcurrentMap<Object, Object> data;
|
||||||
|
private ConcurrentMap<Object, Object> localData;
|
||||||
|
private volatile Handler<Throwable> exceptionHandler;
|
||||||
|
final TaskQueue internalOrderedTasks;
|
||||||
|
final WorkerPool internalWorkerPool;
|
||||||
|
final WorkerPool workerPool;
|
||||||
|
final TaskQueue orderedTasks;
|
||||||
|
|
||||||
|
protected ContextBase(AsyncInternal asyncInternal,
|
||||||
|
EventLoop eventLoop,
|
||||||
|
WorkerPool internalWorkerPool,
|
||||||
|
WorkerPool workerPool,
|
||||||
|
CloseFuture closeFuture,
|
||||||
|
ClassLoader tccl) {
|
||||||
|
this.eventLoop = eventLoop;
|
||||||
|
this.tccl = tccl;
|
||||||
|
this.owner = asyncInternal;
|
||||||
|
this.workerPool = workerPool;
|
||||||
|
this.closeFuture = closeFuture;
|
||||||
|
this.internalWorkerPool = internalWorkerPool;
|
||||||
|
this.orderedTasks = new TaskQueue();
|
||||||
|
this.internalOrderedTasks = new TaskQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CloseFuture closeFuture() {
|
||||||
|
return closeFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventLoop eventLoop() {
|
||||||
|
return eventLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncInternal owner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> executeBlockingInternal(Handler<Promise<T>> action) {
|
||||||
|
return executeBlocking(this, action, internalWorkerPool, internalOrderedTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> executeBlockingInternal(Handler<Promise<T>> action, boolean ordered) {
|
||||||
|
return executeBlocking(this, action, internalWorkerPool, ordered ? internalOrderedTasks : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler, boolean ordered) {
|
||||||
|
return executeBlocking(this, blockingCodeHandler, workerPool, ordered ? orderedTasks : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler, TaskQueue queue) {
|
||||||
|
return executeBlocking(this, blockingCodeHandler, workerPool, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Future<T> executeBlocking(ContextInternal context, Handler<Promise<T>> blockingCodeHandler,
|
||||||
|
WorkerPool workerPool, TaskQueue queue) {
|
||||||
|
Promise<T> promise = context.promise();
|
||||||
|
Future<T> fut = promise.future();
|
||||||
|
try {
|
||||||
|
Runnable command = () -> {
|
||||||
|
Object execMetric = null;
|
||||||
|
context.dispatch(promise, f -> {
|
||||||
|
try {
|
||||||
|
blockingCodeHandler.handle(promise);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
promise.tryFail(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Executor exec = workerPool.executor();
|
||||||
|
if (queue != null) {
|
||||||
|
queue.execute(command, exec);
|
||||||
|
} else {
|
||||||
|
exec.execute(command);
|
||||||
|
}
|
||||||
|
} catch (RejectedExecutionException e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return fut;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClassLoader classLoader() {
|
||||||
|
return tccl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorkerPool workerPool() {
|
||||||
|
return workerPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized ConcurrentMap<Object, Object> contextData() {
|
||||||
|
if (data == null) {
|
||||||
|
data = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized ConcurrentMap<Object, Object> localContextData() {
|
||||||
|
if (localData == null) {
|
||||||
|
localData = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
return localData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reportException(Throwable t) {
|
||||||
|
Handler<Throwable> handler = exceptionHandler;
|
||||||
|
if (handler == null) {
|
||||||
|
handler = owner.exceptionHandler();
|
||||||
|
}
|
||||||
|
if (handler != null) {
|
||||||
|
handler.handle(t);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unhandled exception", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context exceptionHandler(Handler<Throwable> handler) {
|
||||||
|
exceptionHandler = handler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Handler<Throwable> exceptionHandler() {
|
||||||
|
return exceptionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void runOnContext(Handler<Void> action) {
|
||||||
|
runOnContext(this, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void runOnContext(ContextInternal ctx, Handler<Void> action);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable task) {
|
||||||
|
execute(this, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract <T> void execute(ContextInternal ctx, Runnable task);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final <T> void execute(T argument, Handler<T> task) {
|
||||||
|
execute(this, argument, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract <T> void execute(ContextInternal ctx, T argument, Handler<T> task);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void emit(T argument, Handler<T> task) {
|
||||||
|
emit(this, argument, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract <T> void emit(ContextInternal ctx, T argument, Handler<T> task);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContextInternal duplicate() {
|
||||||
|
return new DuplicatedContext(this);
|
||||||
|
}
|
||||||
|
}
|
392
src/main/java/org/xbib/event/async/impl/ContextInternal.java
Normal file
392
src/main/java/org/xbib/event/async/impl/ContextInternal.java
Normal file
|
@ -0,0 +1,392 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.*;
|
||||||
|
import org.xbib.event.async.Context;
|
||||||
|
import org.xbib.event.async.impl.future.FailedFuture;
|
||||||
|
import org.xbib.event.async.impl.future.PromiseImpl;
|
||||||
|
import org.xbib.event.async.impl.future.PromiseInternal;
|
||||||
|
import org.xbib.event.async.impl.future.SucceededFuture;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface provides an api for internal use only
|
||||||
|
* It is not part of the public API and should not be used by
|
||||||
|
* developers.
|
||||||
|
*/
|
||||||
|
public interface ContextInternal extends Context {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current context
|
||||||
|
*/
|
||||||
|
static ContextInternal current() {
|
||||||
|
Thread thread = Thread.currentThread();
|
||||||
|
if (thread instanceof AsyncThread) {
|
||||||
|
return ((AsyncThread) thread).context();
|
||||||
|
} else {
|
||||||
|
AsyncImpl.ContextDispatch current = AsyncImpl.nonContextDispatch.get();
|
||||||
|
if (current != null) {
|
||||||
|
return current.context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void runOnContext(Handler<Void> action) {
|
||||||
|
executor().execute(() -> dispatch(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an executor that schedule a task on this context, the thread executing the task will not be associated with this context
|
||||||
|
*/
|
||||||
|
Executor executor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@link Promise} associated with this context
|
||||||
|
*/
|
||||||
|
default <T> PromiseInternal<T> promise() {
|
||||||
|
return new PromiseImpl<>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@link Promise} associated with this context or the {@code handler}
|
||||||
|
* if that handler is already an instance of {@code PromiseInternal}
|
||||||
|
*/
|
||||||
|
default <T> PromiseInternal<T> promise(Promise<T> p) {
|
||||||
|
if (p instanceof PromiseInternal) {
|
||||||
|
PromiseInternal<T> promise = (PromiseInternal<T>) p;
|
||||||
|
if (promise.context() != null) {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PromiseInternal<T> promise = promise();
|
||||||
|
promise.future().onComplete(p);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an empty succeeded {@link Future} associated with this context
|
||||||
|
*/
|
||||||
|
default <T> Future<T> succeededFuture() {
|
||||||
|
return new SucceededFuture<>(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a succeeded {@link Future} of the {@code result} associated with this context
|
||||||
|
*/
|
||||||
|
default <T> Future<T> succeededFuture(T result) {
|
||||||
|
return new SucceededFuture<>(this, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@link Future} failed with the {@code failure} associated with this context
|
||||||
|
*/
|
||||||
|
default <T> Future<T> failedFuture(Throwable failure) {
|
||||||
|
return new FailedFuture<>(this, failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@link Future} failed with the {@code message} associated with this context
|
||||||
|
*/
|
||||||
|
default <T> Future<T> failedFuture(String message) {
|
||||||
|
return new FailedFuture<>(this, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #executeBlocking(Handler, boolean)} but uses the {@code queue} to order the tasks instead
|
||||||
|
* of the internal queue of this context.
|
||||||
|
*/
|
||||||
|
<T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler, TaskQueue queue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an internal task on the internal blocking ordered executor.
|
||||||
|
*/
|
||||||
|
<T> Future<T> executeBlockingInternal(Handler<Promise<T>> action);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an internal task on the internal blocking ordered executor.
|
||||||
|
*/
|
||||||
|
<T> Future<T> executeBlockingInternal(Handler<Promise<T>> action, boolean ordered);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the deployment associated with this context or {@code null}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
AsyncInternal owner();
|
||||||
|
|
||||||
|
boolean inThread();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit the given {@code argument} event to the {@code task} and switch on this context if necessary, this also associates the
|
||||||
|
* current thread with the current context so {@link Async#currentContext()} returns this context.
|
||||||
|
* <br/>
|
||||||
|
* Any exception thrown from the {@literal task} will be reported on this context.
|
||||||
|
* <br/>
|
||||||
|
* Calling this method is equivalent to {@code execute(v -> dispatch(argument, task))}
|
||||||
|
*
|
||||||
|
* @param argument the {@code task} argument
|
||||||
|
* @param task the handler to execute with the {@code event} argument
|
||||||
|
*/
|
||||||
|
<T> void emit(T argument, Handler<T> task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #emit(Object, Handler)
|
||||||
|
*/
|
||||||
|
default void emit(Handler<Void> task) {
|
||||||
|
emit(null, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #execute(Object, Handler)
|
||||||
|
*/
|
||||||
|
default void execute(Handler<Void> task) {
|
||||||
|
execute(null, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the {@code task} on this context, it will be executed according to the
|
||||||
|
* context concurrency model.
|
||||||
|
*
|
||||||
|
* @param task the task to execute
|
||||||
|
*/
|
||||||
|
void execute(Runnable task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a {@code task} on this context, the task will be executed according to the
|
||||||
|
* context concurrency model.
|
||||||
|
*
|
||||||
|
* @param argument the {@code task} argument
|
||||||
|
* @param task the task to execute
|
||||||
|
*/
|
||||||
|
<T> void execute(T argument, Handler<T> task);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the current thread is running on this context
|
||||||
|
*/
|
||||||
|
default boolean isRunningOnContext() {
|
||||||
|
return current() == this && inThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #dispatch(Handler)
|
||||||
|
*/
|
||||||
|
default void dispatch(Runnable handler) {
|
||||||
|
ContextInternal prev = beginDispatch();
|
||||||
|
try {
|
||||||
|
handler.run();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
reportException(t);
|
||||||
|
} finally {
|
||||||
|
endDispatch(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #dispatch(Object, Handler)
|
||||||
|
*/
|
||||||
|
default void dispatch(Handler<Void> handler) {
|
||||||
|
dispatch(null, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch an {@code event} to the {@code handler} on this context.
|
||||||
|
* <p>
|
||||||
|
* The handler is executed directly by the caller thread which must be a context thread.
|
||||||
|
* <p>
|
||||||
|
* The handler execution is monitored by the blocked thread checker.
|
||||||
|
* <p>
|
||||||
|
* This context is thread-local associated during the task execution.
|
||||||
|
*
|
||||||
|
* @param event the event for the {@code handler}
|
||||||
|
* @param handler the handler to execute with the {@code event}
|
||||||
|
*/
|
||||||
|
default <E> void dispatch(E event, Handler<E> handler) {
|
||||||
|
ContextInternal prev = beginDispatch();
|
||||||
|
try {
|
||||||
|
handler.handle(event);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
reportException(t);
|
||||||
|
} finally {
|
||||||
|
endDispatch(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin the execution of a task on this context.
|
||||||
|
* <p>
|
||||||
|
* The task execution is monitored by the blocked thread checker.
|
||||||
|
* <p>
|
||||||
|
* This context is thread-local associated during the task execution.
|
||||||
|
* <p>
|
||||||
|
* You should not use this API directly, instead you should use {@link #dispatch(Object, Handler)}
|
||||||
|
*
|
||||||
|
* @return the previous context that shall be restored after or {@code null} if there is none
|
||||||
|
* @throws IllegalStateException when the current thread of execution cannot execute this task
|
||||||
|
*/
|
||||||
|
default ContextInternal beginDispatch() {
|
||||||
|
AsyncImpl async = (AsyncImpl) owner();
|
||||||
|
return async.beginDispatch(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End the execution of a task on this context, see {@link #beginDispatch()}
|
||||||
|
* <p>
|
||||||
|
* You should not use this API directly, instead you should use {@link #dispatch(Object, Handler)}
|
||||||
|
*
|
||||||
|
* @param previous the previous context to restore or {@code null} if there is none
|
||||||
|
* @throws IllegalStateException when the current thread of execution cannot execute this task
|
||||||
|
*/
|
||||||
|
default void endDispatch(ContextInternal previous) {
|
||||||
|
AsyncImpl async = (AsyncImpl) owner();
|
||||||
|
async.endDispatch(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an exception to this context synchronously.
|
||||||
|
* <p>
|
||||||
|
* The exception handler will be called when there is one, otherwise the exception will be logged.
|
||||||
|
*
|
||||||
|
* @param t the exception to report
|
||||||
|
*/
|
||||||
|
void reportException(Throwable t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link ConcurrentMap} used to store context data
|
||||||
|
* @see Context#get(Object)
|
||||||
|
* @see Context#put(Object, Object)
|
||||||
|
*/
|
||||||
|
ConcurrentMap<Object, Object> contextData();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
default <T> T get(Object key) {
|
||||||
|
return (T) contextData().get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void put(Object key, Object value) {
|
||||||
|
contextData().put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean remove(Object key) {
|
||||||
|
return contextData().remove(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link ConcurrentMap} used to store local context data
|
||||||
|
*/
|
||||||
|
ConcurrentMap<Object, Object> localContextData();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
default <T> T getLocal(Object key) {
|
||||||
|
return (T) localContextData().get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void putLocal(Object key, Object value) {
|
||||||
|
localContextData().put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean removeLocal(Object key) {
|
||||||
|
return localContextData().remove(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the classloader associated with this context
|
||||||
|
*/
|
||||||
|
ClassLoader classLoader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the context worker pool
|
||||||
|
*/
|
||||||
|
WorkerPool workerPool();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a context sharing with this context
|
||||||
|
* <ul>
|
||||||
|
* <li>the same concurrency</li>
|
||||||
|
* <li>the same exception handler</li>
|
||||||
|
* <li>the same context data</li>
|
||||||
|
* <li>the same deployment</li>
|
||||||
|
* <li>the same config</li>
|
||||||
|
* <li>the same classloader</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* The duplicate context has its own
|
||||||
|
* <ul>
|
||||||
|
* <li>local context data</li>
|
||||||
|
* <li>worker task queue</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return a duplicate of this context
|
||||||
|
*/
|
||||||
|
ContextInternal duplicate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link Async#setPeriodic(long, Handler)} except the periodic timer will fire on this context and the
|
||||||
|
* timer will not be associated with the context close hook.
|
||||||
|
*/
|
||||||
|
default long setPeriodic(long delay, Handler<Long> handler) {
|
||||||
|
AsyncImpl owner = (AsyncImpl) owner();
|
||||||
|
return owner.scheduleTimeout(this, true, delay, TimeUnit.MILLISECONDS, false, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link Async#setTimer(long, Handler)} except the timer will fire on this context and the timer
|
||||||
|
* will not be associated with the context close hook.
|
||||||
|
*/
|
||||||
|
default long setTimer(long delay, Handler<Long> handler) {
|
||||||
|
AsyncImpl owner = (AsyncImpl) owner();
|
||||||
|
return owner.scheduleTimeout(this, false, delay, TimeUnit.MILLISECONDS, false, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseFuture closeFuture();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a close hook.
|
||||||
|
*
|
||||||
|
* <p> The {@code hook} will be called when the associated resource needs to be released.
|
||||||
|
*
|
||||||
|
* @param hook the close hook
|
||||||
|
*/
|
||||||
|
default void addCloseHook(Closeable hook) {
|
||||||
|
closeFuture().add(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a close hook.
|
||||||
|
*
|
||||||
|
* <p> This is called when the resource is released explicitly and does not need anymore a managed close.
|
||||||
|
*
|
||||||
|
* @param hook the close hook
|
||||||
|
*/
|
||||||
|
default void removeCloseHook(Closeable hook) {
|
||||||
|
closeFuture().remove(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original context, a duplicate context returns the wrapped context otherwise this instance is returned.
|
||||||
|
*
|
||||||
|
* @return the wrapped context
|
||||||
|
*/
|
||||||
|
default ContextInternal unwrap() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this context is a duplicated context.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this context is a duplicated context, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
default boolean isDuplicate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
161
src/main/java/org/xbib/event/async/impl/DuplicatedContext.java
Normal file
161
src/main/java/org/xbib/event/async/impl/DuplicatedContext.java
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Context;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.Promise;
|
||||||
|
import org.xbib.event.loop.EventLoop;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context that forwards most operations to a delegate. This context
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>maintains its own ordered task queue, ordered execute blocking are ordered on this
|
||||||
|
* context instead of the delegate.</li>
|
||||||
|
* <li>maintains its own local data instead of the delegate.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
class DuplicatedContext implements ContextInternal {
|
||||||
|
|
||||||
|
protected final ContextBase delegate;
|
||||||
|
private ConcurrentMap<Object, Object> localData;
|
||||||
|
|
||||||
|
DuplicatedContext(ContextBase delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inThread() {
|
||||||
|
return delegate.inThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final CloseFuture closeFuture() {
|
||||||
|
return delegate.closeFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Context exceptionHandler(Handler<Throwable> handler) {
|
||||||
|
delegate.exceptionHandler(handler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Executor executor() {
|
||||||
|
return delegate.executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Handler<Throwable> exceptionHandler() {
|
||||||
|
return delegate.exceptionHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final EventLoop eventLoop() {
|
||||||
|
return delegate.eventLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final AsyncInternal owner() {
|
||||||
|
return delegate.owner();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ClassLoader classLoader() {
|
||||||
|
return delegate.classLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorkerPool workerPool() {
|
||||||
|
return delegate.workerPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void reportException(Throwable t) {
|
||||||
|
delegate.reportException(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ConcurrentMap<Object, Object> contextData() {
|
||||||
|
return delegate.contextData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ConcurrentMap<Object, Object> localContextData() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (localData == null) {
|
||||||
|
localData = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
return localData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final <T> Future<T> executeBlockingInternal(Handler<Promise<T>> action) {
|
||||||
|
return ContextBase.executeBlocking(this, action, delegate.internalWorkerPool, delegate.internalOrderedTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final <T> Future<T> executeBlockingInternal(Handler<Promise<T>> action, boolean ordered) {
|
||||||
|
return ContextBase.executeBlocking(this, action, delegate.internalWorkerPool, ordered ? delegate.internalOrderedTasks : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final <T> Future<T> executeBlocking(Handler<Promise<T>> action, boolean ordered) {
|
||||||
|
return ContextBase.executeBlocking(this, action, delegate.workerPool, ordered ? delegate.orderedTasks : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler, TaskQueue queue) {
|
||||||
|
return ContextBase.executeBlocking(this, blockingCodeHandler, delegate.workerPool, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void runOnContext(Handler<Void> action) {
|
||||||
|
delegate.runOnContext(this, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final <T> void execute(T argument, Handler<T> task) {
|
||||||
|
delegate.execute(this, argument, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void emit(T argument, Handler<T> task) {
|
||||||
|
delegate.emit(this, argument, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable task) {
|
||||||
|
delegate.execute(this, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEventLoopContext() {
|
||||||
|
return delegate.isEventLoopContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWorkerContext() {
|
||||||
|
return delegate.isWorkerContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContextInternal duplicate() {
|
||||||
|
return new DuplicatedContext(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContextInternal unwrap() {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDuplicate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.loop.EventLoop;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
|
public class EventLoopContext extends ContextBase {
|
||||||
|
|
||||||
|
EventLoopContext(AsyncInternal asyncInternal,
|
||||||
|
EventLoop eventLoop,
|
||||||
|
WorkerPool internalBlockingPool,
|
||||||
|
WorkerPool workerPool,
|
||||||
|
CloseFuture closeFuture,
|
||||||
|
ClassLoader tccl) {
|
||||||
|
super(asyncInternal, eventLoop, internalBlockingPool, workerPool, closeFuture, tccl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Executor executor() {
|
||||||
|
return eventLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runOnContext(ContextInternal ctx, Handler<Void> action) {
|
||||||
|
try {
|
||||||
|
eventLoop().execute(() -> ctx.dispatch(action));
|
||||||
|
} catch (RejectedExecutionException ignore) {
|
||||||
|
// Pool is already shut down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> void emit(ContextInternal ctx, T argument, Handler<T> task) {
|
||||||
|
EventLoop eventLoop = eventLoop();
|
||||||
|
if (eventLoop.inEventLoop()) {
|
||||||
|
ContextInternal prev = ctx.beginDispatch();
|
||||||
|
try {
|
||||||
|
task.handle(argument);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
reportException(t);
|
||||||
|
} finally {
|
||||||
|
ctx.endDispatch(prev);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eventLoop.execute(() -> emit(ctx, argument, task));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <ul>
|
||||||
|
* <li>When the current thread is event-loop thread of this context the implementation will execute the {@code task} directly</li>
|
||||||
|
* <li>Otherwise the task will be scheduled on the event-loop thread for execution</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected <T> void execute(ContextInternal ctx, T argument, Handler<T> task) {
|
||||||
|
EventLoop eventLoop = eventLoop();
|
||||||
|
if (eventLoop.inEventLoop()) {
|
||||||
|
task.handle(argument);
|
||||||
|
} else {
|
||||||
|
eventLoop.execute(() -> task.handle(argument));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> void execute(ContextInternal ctx, Runnable task) {
|
||||||
|
EventLoop eventLoop = eventLoop();
|
||||||
|
if (eventLoop.inEventLoop()) {
|
||||||
|
task.run();
|
||||||
|
} else {
|
||||||
|
eventLoop.execute(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEventLoopContext() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWorkerContext() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inThread() {
|
||||||
|
return eventLoop().inEventLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
src/main/java/org/xbib/event/async/impl/NestedCloseable.java
Normal file
10
src/main/java/org/xbib/event/async/impl/NestedCloseable.java
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps a reference to an owner close future so we can unregister from it when the closeable is closed.
|
||||||
|
*/
|
||||||
|
abstract class NestedCloseable {
|
||||||
|
|
||||||
|
CloseFuture owner;
|
||||||
|
|
||||||
|
}
|
88
src/main/java/org/xbib/event/async/impl/TaskQueue.java
Normal file
88
src/main/java/org/xbib/event/async/impl/TaskQueue.java
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task queue that always run all tasks in order. The executor to run the tasks is passed
|
||||||
|
* when the tasks are executed, this executor is not guaranteed to be used, as if several
|
||||||
|
* tasks are queued, the original thread will be used.
|
||||||
|
*
|
||||||
|
* More specifically, any call B to the {@link #execute(Runnable, Executor)} method that happens-after another call A to the
|
||||||
|
* same method, will result in B's task running after A's.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TaskQueue {
|
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(TaskQueue.class.getName());
|
||||||
|
|
||||||
|
private static class Task {
|
||||||
|
|
||||||
|
private final Runnable runnable;
|
||||||
|
private final Executor exec;
|
||||||
|
|
||||||
|
public Task(Runnable runnable, Executor exec) {
|
||||||
|
this.runnable = runnable;
|
||||||
|
this.exec = exec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @protectedby tasks
|
||||||
|
private final LinkedList<Task> tasks = new LinkedList<>();
|
||||||
|
|
||||||
|
// @protectedby tasks
|
||||||
|
private Executor current;
|
||||||
|
|
||||||
|
private final Runnable runner;
|
||||||
|
|
||||||
|
public TaskQueue() {
|
||||||
|
runner = this::run;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
for (; ; ) {
|
||||||
|
final Task task;
|
||||||
|
synchronized (tasks) {
|
||||||
|
task = tasks.poll();
|
||||||
|
if (task == null) {
|
||||||
|
current = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (task.exec != current) {
|
||||||
|
tasks.addFirst(task);
|
||||||
|
task.exec.execute(runner);
|
||||||
|
current = task.exec;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
task.runnable.run();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.log(Level.SEVERE, "Caught unexpected Throwable", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task.
|
||||||
|
*
|
||||||
|
* @param task the task to run.
|
||||||
|
*/
|
||||||
|
public void execute(Runnable task, Executor executor) {
|
||||||
|
synchronized (tasks) {
|
||||||
|
tasks.add(new Task(task, executor));
|
||||||
|
if (current == null) {
|
||||||
|
current = executor;
|
||||||
|
try {
|
||||||
|
executor.execute(runner);
|
||||||
|
} catch (RejectedExecutionException e) {
|
||||||
|
current = null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/main/java/org/xbib/event/async/impl/WorkerPool.java
Normal file
20
src/main/java/org/xbib/event/async/impl/WorkerPool.java
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.event.async.impl;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class WorkerPool {
|
||||||
|
|
||||||
|
private final ExecutorService pool;
|
||||||
|
|
||||||
|
public WorkerPool(ExecutorService pool) {
|
||||||
|
this.pool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExecutorService executor() {
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
pool.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.AsyncResult;
|
||||||
|
import org.xbib.event.async.CompositeFuture;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class CompositeFutureImpl extends FutureImpl<CompositeFuture> implements CompositeFuture {
|
||||||
|
|
||||||
|
public static CompositeFuture all(Future<?>... results) {
|
||||||
|
CompositeFutureImpl composite = new CompositeFutureImpl(results);
|
||||||
|
int len = results.length;
|
||||||
|
for (Future<?> result : results) {
|
||||||
|
result.onComplete(ar -> {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
synchronized (composite) {
|
||||||
|
if (composite.count == len || ++composite.count != len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composite.trySucceed();
|
||||||
|
} else {
|
||||||
|
synchronized (composite) {
|
||||||
|
if (composite.count == len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
composite.count = len;
|
||||||
|
}
|
||||||
|
composite.tryFail(ar.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (len == 0) {
|
||||||
|
composite.trySucceed();
|
||||||
|
}
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompositeFuture any(Future<?>... results) {
|
||||||
|
CompositeFutureImpl composite = new CompositeFutureImpl(results);
|
||||||
|
int len = results.length;
|
||||||
|
for (Future<?> result : results) {
|
||||||
|
result.onComplete(ar -> {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
synchronized (composite) {
|
||||||
|
if (composite.count == len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
composite.count = len;
|
||||||
|
}
|
||||||
|
composite.trySucceed();
|
||||||
|
} else {
|
||||||
|
synchronized (composite) {
|
||||||
|
if (composite.count == len || ++composite.count != len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composite.tryFail(ar.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (results.length == 0) {
|
||||||
|
composite.trySucceed();
|
||||||
|
}
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Function<CompositeFuture, Object> ALL = cf -> {
|
||||||
|
int size = cf.size();
|
||||||
|
for (int i = 0;i < size;i++) {
|
||||||
|
if (!cf.succeeded(i)) {
|
||||||
|
return cf.cause(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cf;
|
||||||
|
};
|
||||||
|
|
||||||
|
public static CompositeFuture join(Future<?>... results) {
|
||||||
|
return join(ALL, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompositeFuture join(Function<CompositeFuture, Object> pred, Future<?>... results) {
|
||||||
|
CompositeFutureImpl composite = new CompositeFutureImpl(results);
|
||||||
|
int len = results.length;
|
||||||
|
for (Future<?> result : results) {
|
||||||
|
result.onComplete(ar -> {
|
||||||
|
synchronized (composite) {
|
||||||
|
if (++composite.count < len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composite.complete(pred.apply(composite));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (len == 0) {
|
||||||
|
composite.trySucceed();
|
||||||
|
}
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Future[] results;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
private CompositeFutureImpl(Future<?>... results) {
|
||||||
|
this.results = results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable cause(int index) {
|
||||||
|
return future(index).cause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean succeeded(int index) {
|
||||||
|
return future(index).succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean failed(int index) {
|
||||||
|
return future(index).failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isComplete(int index) {
|
||||||
|
return future(index).isComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T resultAt(int index) {
|
||||||
|
return this.<T>future(index).result();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> Future<T> future(int index) {
|
||||||
|
if (index < 0 || index >= results.length) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
return (Future<T>) results[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return results.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trySucceed() {
|
||||||
|
tryComplete(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fail(Throwable t) {
|
||||||
|
complete(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void complete(Object result) {
|
||||||
|
if (result == this) {
|
||||||
|
tryComplete(this);
|
||||||
|
} else if (result instanceof Throwable) {
|
||||||
|
tryFail((Throwable) result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeFuture onComplete(Handler<AsyncResult<CompositeFuture>> handler) {
|
||||||
|
return (CompositeFuture)super.onComplete(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeFuture onSuccess(Handler<CompositeFuture> handler) {
|
||||||
|
return (CompositeFuture)super.onSuccess(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeFuture onFailure(Handler<Throwable> handler) {
|
||||||
|
return (CompositeFuture)super.onFailure(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void formatValue(Object value, StringBuilder sb) {
|
||||||
|
sb.append('(');
|
||||||
|
for (int i = 0;i < results.length;i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
sb.append(',');
|
||||||
|
}
|
||||||
|
sb.append(results[i]);
|
||||||
|
}
|
||||||
|
sb.append(')');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function compose transformation.
|
||||||
|
*/
|
||||||
|
class Composition<T, U> extends Operation<U> implements Listener<T> {
|
||||||
|
|
||||||
|
private final Function<T, Future<U>> successMapper;
|
||||||
|
private final Function<Throwable, Future<U>> failureMapper;
|
||||||
|
|
||||||
|
Composition(ContextInternal context, Function<T, Future<U>> successMapper, Function<Throwable, Future<U>> failureMapper) {
|
||||||
|
super(context);
|
||||||
|
this.successMapper = successMapper;
|
||||||
|
this.failureMapper = failureMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
FutureInternal<U> future;
|
||||||
|
try {
|
||||||
|
future = (FutureInternal<U>) successMapper.apply(value);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tryFail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
future.addListener(newListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
FutureInternal<U> future;
|
||||||
|
try {
|
||||||
|
future = (FutureInternal<U>) failureMapper.apply(failure);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tryFail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
future.addListener(newListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Listener<U> newListener() {
|
||||||
|
return new Listener<U>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(U value) {
|
||||||
|
tryComplete(value);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
tryFail(failure);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eventually operation.
|
||||||
|
*/
|
||||||
|
class Eventually<T, U> extends Operation<T> implements Listener<T> {
|
||||||
|
|
||||||
|
private final Function<Void, Future<U>> mapper;
|
||||||
|
|
||||||
|
Eventually(ContextInternal context, Function<Void, Future<U>> mapper) {
|
||||||
|
super(context);
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
FutureInternal<U> future;
|
||||||
|
try {
|
||||||
|
future = (FutureInternal<U>) mapper.apply(null);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tryFail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
future.addListener(new Listener<U>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(U ignore) {
|
||||||
|
tryComplete(value);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable ignore) {
|
||||||
|
tryComplete(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
FutureInternal<U> future;
|
||||||
|
try {
|
||||||
|
future = (FutureInternal<U>) mapper.apply(null);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tryFail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
future.addListener(new Listener<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(U ignore) {
|
||||||
|
tryFail(failure);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable ignore) {
|
||||||
|
tryFail(failure);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
127
src/main/java/org/xbib/event/async/impl/future/FailedFuture.java
Normal file
127
src/main/java/org/xbib/event/async/impl/future/FailedFuture.java
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.AsyncResult;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.NoStackTraceThrowable;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Failed future implementation.
|
||||||
|
*/
|
||||||
|
public final class FailedFuture<T> extends FutureBase<T> {
|
||||||
|
|
||||||
|
private final Throwable cause;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that has already failed
|
||||||
|
* @param t the throwable
|
||||||
|
*/
|
||||||
|
public FailedFuture(Throwable t) {
|
||||||
|
this(null, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that has already failed
|
||||||
|
* @param t the throwable
|
||||||
|
*/
|
||||||
|
public FailedFuture(ContextInternal context, Throwable t) {
|
||||||
|
super(context);
|
||||||
|
this.cause = t != null ? t : new NoStackTraceThrowable(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that has already failed
|
||||||
|
* @param failureMessage the failure message
|
||||||
|
*/
|
||||||
|
public FailedFuture(String failureMessage) {
|
||||||
|
this(null, failureMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that has already failed
|
||||||
|
* @param failureMessage the failure message
|
||||||
|
*/
|
||||||
|
public FailedFuture(ContextInternal context, String failureMessage) {
|
||||||
|
this(context, new NoStackTraceThrowable(failureMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isComplete() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onComplete(Handler<AsyncResult<T>> handler) {
|
||||||
|
if (handler instanceof Listener) {
|
||||||
|
emitFailure(cause, (Listener<T>) handler);
|
||||||
|
} else if (context != null) {
|
||||||
|
context.emit(this, handler);
|
||||||
|
} else {
|
||||||
|
handler.handle(this);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onSuccess(Handler<T> handler) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onFailure(Handler<Throwable> handler) {
|
||||||
|
if (context != null) {
|
||||||
|
context.emit(cause, handler);
|
||||||
|
} else {
|
||||||
|
handler.handle(cause);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(Listener<T> listener) {
|
||||||
|
emitFailure(cause, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T result() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable cause() {
|
||||||
|
return cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean succeeded() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean failed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> Future<U> map(Function<T, U> mapper) {
|
||||||
|
return (Future<U>) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> map(V value) {
|
||||||
|
return (Future<V>) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> otherwise(T value) {
|
||||||
|
return new SucceededFuture<>(context, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Future{cause=" + cause.getMessage() + "}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map value transformation.
|
||||||
|
*/
|
||||||
|
class FixedMapping<T, U> extends Operation<U> implements Listener<T> {
|
||||||
|
|
||||||
|
private final U value;
|
||||||
|
|
||||||
|
FixedMapping(ContextInternal context, U value) {
|
||||||
|
super(context);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
tryComplete(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
tryFail(failure);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Otherwise value transformation.
|
||||||
|
*/
|
||||||
|
class FixedOtherwise<T> extends Operation<T> implements Listener<T> {
|
||||||
|
|
||||||
|
private final T value;
|
||||||
|
|
||||||
|
FixedOtherwise(ContextInternal context, T value) {
|
||||||
|
super(context);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
tryComplete(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
tryComplete(value);
|
||||||
|
}
|
||||||
|
}
|
119
src/main/java/org/xbib/event/async/impl/future/FutureBase.java
Normal file
119
src/main/java/org/xbib/event/async/impl/future/FutureBase.java
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.AsyncResult;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Future base implementation.
|
||||||
|
*/
|
||||||
|
public abstract class FutureBase<T> implements FutureInternal<T> {
|
||||||
|
|
||||||
|
protected final ContextInternal context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that hasn't completed yet
|
||||||
|
*/
|
||||||
|
protected FutureBase() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that hasn't completed yet
|
||||||
|
*/
|
||||||
|
FutureBase(ContextInternal context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ContextInternal context() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void emitSuccess(T value, Listener<T> listener) {
|
||||||
|
if (context != null && !context.isRunningOnContext()) {
|
||||||
|
context.execute(() -> {
|
||||||
|
ContextInternal prev = context.beginDispatch();
|
||||||
|
try {
|
||||||
|
listener.onSuccess(value);
|
||||||
|
} finally {
|
||||||
|
context.endDispatch(prev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
listener.onSuccess(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void emitFailure(Throwable cause, Listener<T> listener) {
|
||||||
|
if (context != null && !context.isRunningOnContext()) {
|
||||||
|
context.execute(() -> {
|
||||||
|
ContextInternal prev = context.beginDispatch();
|
||||||
|
try {
|
||||||
|
listener.onFailure(cause);
|
||||||
|
} finally {
|
||||||
|
context.endDispatch(prev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
listener.onFailure(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> Future<U> compose(Function<T, Future<U>> successMapper, Function<Throwable, Future<U>> failureMapper) {
|
||||||
|
Objects.requireNonNull(successMapper, "No null success mapper accepted");
|
||||||
|
Objects.requireNonNull(failureMapper, "No null failure mapper accepted");
|
||||||
|
Composition<T, U> operation = new Composition<>(context, successMapper, failureMapper);
|
||||||
|
addListener(operation);
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> Future<U> transform(Function<AsyncResult<T>, Future<U>> mapper) {
|
||||||
|
Objects.requireNonNull(mapper, "No null mapper accepted");
|
||||||
|
Transformation<T, U> operation = new Transformation<>(context, this, mapper);
|
||||||
|
addListener(operation);
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> Future<T> eventually(Function<Void, Future<U>> mapper) {
|
||||||
|
Objects.requireNonNull(mapper, "No null mapper accepted");
|
||||||
|
Eventually<T, U> operation = new Eventually<>(context, mapper);
|
||||||
|
addListener(operation);
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> Future<U> map(Function<T, U> mapper) {
|
||||||
|
Objects.requireNonNull(mapper, "No null mapper accepted");
|
||||||
|
Mapping<T, U> operation = new Mapping<>(context, mapper);
|
||||||
|
addListener(operation);
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> map(V value) {
|
||||||
|
FixedMapping<T, V> transformation = new FixedMapping<>(context, value);
|
||||||
|
addListener(transformation);
|
||||||
|
return transformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> otherwise(Function<Throwable, T> mapper) {
|
||||||
|
Objects.requireNonNull(mapper, "No null mapper accepted");
|
||||||
|
Otherwise<T> transformation = new Otherwise<>(context, mapper);
|
||||||
|
addListener(transformation);
|
||||||
|
return transformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> otherwise(T value) {
|
||||||
|
FixedOtherwise<T> operation = new FixedOtherwise<>(context, value);
|
||||||
|
addListener(operation);
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
}
|
268
src/main/java/org/xbib/event/async/impl/future/FutureImpl.java
Normal file
268
src/main/java/org/xbib/event/async/impl/future/FutureImpl.java
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.AsyncResult;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.NoStackTraceThrowable;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Future implementation.
|
||||||
|
*/
|
||||||
|
class FutureImpl<T> extends FutureBase<T> {
|
||||||
|
|
||||||
|
private static final Object NULL_VALUE = new Object();
|
||||||
|
|
||||||
|
private Object value;
|
||||||
|
private Listener<T> listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that hasn't completed yet
|
||||||
|
*/
|
||||||
|
FutureImpl() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that hasn't completed yet
|
||||||
|
*/
|
||||||
|
FutureImpl(ContextInternal context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of the operation. This will be null if the operation failed.
|
||||||
|
*/
|
||||||
|
public synchronized T result() {
|
||||||
|
return value instanceof CauseHolder ? null : value == NULL_VALUE ? null : (T) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception describing failure. This will be null if the operation succeeded.
|
||||||
|
*/
|
||||||
|
public synchronized Throwable cause() {
|
||||||
|
return value instanceof CauseHolder ? ((CauseHolder)value).cause : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did it succeed?
|
||||||
|
*/
|
||||||
|
public synchronized boolean succeeded() {
|
||||||
|
return value != null && !(value instanceof CauseHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did it fail?
|
||||||
|
*/
|
||||||
|
public synchronized boolean failed() {
|
||||||
|
return value instanceof CauseHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has it completed?
|
||||||
|
*/
|
||||||
|
public synchronized boolean isComplete() {
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onSuccess(Handler<T> handler) {
|
||||||
|
Objects.requireNonNull(handler, "No null handler accepted");
|
||||||
|
addListener(new Listener<T>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
try {
|
||||||
|
handler.handle(value);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (context != null) {
|
||||||
|
context.reportException(t);
|
||||||
|
} else {
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onFailure(Handler<Throwable> handler) {
|
||||||
|
Objects.requireNonNull(handler, "No null handler accepted");
|
||||||
|
addListener(new Listener<T>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
try {
|
||||||
|
handler.handle(failure);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (context != null) {
|
||||||
|
context.reportException(t);
|
||||||
|
} else {
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onComplete(Handler<AsyncResult<T>> handler) {
|
||||||
|
Objects.requireNonNull(handler, "No null handler accepted");
|
||||||
|
Listener<T> listener;
|
||||||
|
if (handler instanceof Listener) {
|
||||||
|
listener = (Listener<T>) handler;
|
||||||
|
} else {
|
||||||
|
listener = new Listener<T>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
try {
|
||||||
|
handler.handle(FutureImpl.this);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (context != null) {
|
||||||
|
context.reportException(t);
|
||||||
|
} else {
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
try {
|
||||||
|
handler.handle(FutureImpl.this);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (context != null) {
|
||||||
|
context.reportException(t);
|
||||||
|
} else {
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
addListener(listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(Listener<T> listener) {
|
||||||
|
Object v;
|
||||||
|
synchronized (this) {
|
||||||
|
v = value;
|
||||||
|
if (v == null) {
|
||||||
|
if (this.listener == null) {
|
||||||
|
this.listener = listener;
|
||||||
|
} else {
|
||||||
|
ListenerArray<T> listeners;
|
||||||
|
if (this.listener instanceof FutureImpl.ListenerArray) {
|
||||||
|
listeners = (ListenerArray<T>) this.listener;
|
||||||
|
} else {
|
||||||
|
listeners = new ListenerArray<>();
|
||||||
|
listeners.add(this.listener);
|
||||||
|
this.listener = listeners;
|
||||||
|
}
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v instanceof CauseHolder) {
|
||||||
|
emitFailure(((CauseHolder)v).cause, listener);
|
||||||
|
} else {
|
||||||
|
if (v == NULL_VALUE) {
|
||||||
|
v = null;
|
||||||
|
}
|
||||||
|
emitSuccess((T) v, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryComplete(T result) {
|
||||||
|
Listener<T> l;
|
||||||
|
synchronized (this) {
|
||||||
|
if (value != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = result == null ? NULL_VALUE : result;
|
||||||
|
l = listener;
|
||||||
|
listener = null;
|
||||||
|
}
|
||||||
|
if (l != null) {
|
||||||
|
emitSuccess(result, l);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryFail(Throwable cause) {
|
||||||
|
if (cause == null) {
|
||||||
|
cause = new NoStackTraceThrowable(null);
|
||||||
|
}
|
||||||
|
Listener<T> l;
|
||||||
|
synchronized (this) {
|
||||||
|
if (value != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = new CauseHolder(cause);
|
||||||
|
l = listener;
|
||||||
|
listener = null;
|
||||||
|
}
|
||||||
|
if (l != null) {
|
||||||
|
emitFailure(cause, l);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (value instanceof CauseHolder) {
|
||||||
|
return "Future{cause=" + ((CauseHolder)value).cause.getMessage() + "}";
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
if (value == NULL_VALUE) {
|
||||||
|
return "Future{result=null}";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder("Future{result=");
|
||||||
|
formatValue(value, sb);
|
||||||
|
sb.append("}");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
return "Future{unresolved}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void formatValue(Object value, StringBuilder sb) {
|
||||||
|
sb.append(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ListenerArray<T> extends ArrayList<Listener<T>> implements Listener<T> {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
for (Listener<T> handler : this) {
|
||||||
|
handler.onSuccess(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
for (Listener<T> handler : this) {
|
||||||
|
handler.onFailure(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CauseHolder {
|
||||||
|
|
||||||
|
private final Throwable cause;
|
||||||
|
|
||||||
|
CauseHolder(Throwable cause) {
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose some of the future internal stuff.
|
||||||
|
*/
|
||||||
|
public interface FutureInternal<T> extends Future<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the context associated with this promise or {@code null} when there is none
|
||||||
|
*/
|
||||||
|
ContextInternal context();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener to the future result.
|
||||||
|
*
|
||||||
|
* @param listener the listener
|
||||||
|
*/
|
||||||
|
void addListener(Listener<T> listener);
|
||||||
|
|
||||||
|
}
|
21
src/main/java/org/xbib/event/async/impl/future/Listener.java
Normal file
21
src/main/java/org/xbib/event/async/impl/future/Listener.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal listener that signals success or failure when a future completes.
|
||||||
|
*/
|
||||||
|
public interface Listener<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal the success.
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
*/
|
||||||
|
void onSuccess(T value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal the failure
|
||||||
|
*
|
||||||
|
* @param failure the failure
|
||||||
|
*/
|
||||||
|
void onFailure(Throwable failure);
|
||||||
|
}
|
35
src/main/java/org/xbib/event/async/impl/future/Mapping.java
Normal file
35
src/main/java/org/xbib/event/async/impl/future/Mapping.java
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function map transformation.
|
||||||
|
*/
|
||||||
|
class Mapping<T, U> extends Operation<U> implements Listener<T> {
|
||||||
|
|
||||||
|
private final Function<T, U> successMapper;
|
||||||
|
|
||||||
|
Mapping(ContextInternal context, Function<T, U> successMapper) {
|
||||||
|
super(context);
|
||||||
|
this.successMapper = successMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
U result;
|
||||||
|
try {
|
||||||
|
result = successMapper.apply(value);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tryFail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tryComplete(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
tryFail(failure);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for transforming the completion of a future.
|
||||||
|
*/
|
||||||
|
abstract class Operation<T> extends FutureImpl<T> {
|
||||||
|
|
||||||
|
protected Operation(ContextInternal context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
class Otherwise<T> extends Operation<T> implements Listener<T> {
|
||||||
|
|
||||||
|
private final Function<Throwable, T> mapper;
|
||||||
|
|
||||||
|
Otherwise(ContextInternal context, Function<Throwable, T> mapper) {
|
||||||
|
super(context);
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
tryComplete(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
T result;
|
||||||
|
try {
|
||||||
|
result = mapper.apply(failure);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tryFail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tryComplete(result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.Future;
|
||||||
|
import org.xbib.event.async.AsyncResult;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise implementation.
|
||||||
|
*/
|
||||||
|
public final class PromiseImpl<T> extends FutureImpl<T> implements PromiseInternal<T>, Listener<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a promise that hasn't completed yet
|
||||||
|
*/
|
||||||
|
public PromiseImpl() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a promise that hasn't completed yet
|
||||||
|
*/
|
||||||
|
public PromiseImpl(ContextInternal context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(AsyncResult<T> ar) {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
onSuccess(ar.result());
|
||||||
|
} else {
|
||||||
|
onFailure(ar.cause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
tryComplete(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
tryFail(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.xbib.event.async.Future<T> future() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void operationComplete(Future<T> future) {
|
||||||
|
if (future.isSuccess()) {
|
||||||
|
complete(future.getNow());
|
||||||
|
} else {
|
||||||
|
fail(future.cause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.FutureListener;
|
||||||
|
import org.xbib.event.async.Promise;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
public interface PromiseInternal<T> extends Promise<T>, FutureListener<T>, FutureInternal<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the context associated with this promise or {@code null} when there is none
|
||||||
|
*/
|
||||||
|
ContextInternal context();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.AsyncResult;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Succeeded future implementation.
|
||||||
|
*/
|
||||||
|
public final class SucceededFuture<T> extends FutureBase<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stateless instance of empty results that can be shared safely.
|
||||||
|
*/
|
||||||
|
public static final SucceededFuture EMPTY = new SucceededFuture(null, null);
|
||||||
|
|
||||||
|
private final T result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that has already succeeded
|
||||||
|
* @param result the result
|
||||||
|
*/
|
||||||
|
public SucceededFuture(T result) {
|
||||||
|
this(null, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a future that has already succeeded
|
||||||
|
* @param context the context
|
||||||
|
* @param result the result
|
||||||
|
*/
|
||||||
|
public SucceededFuture(ContextInternal context, T result) {
|
||||||
|
super(context);
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isComplete() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onSuccess(Handler<T> handler) {
|
||||||
|
if (context != null) {
|
||||||
|
context.emit(result, handler);
|
||||||
|
} else {
|
||||||
|
handler.handle(result);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onFailure(Handler<Throwable> handler) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> onComplete(Handler<AsyncResult<T>> handler) {
|
||||||
|
if (handler instanceof Listener) {
|
||||||
|
emitSuccess(result ,(Listener<T>) handler);
|
||||||
|
} else if (context != null) {
|
||||||
|
context.emit(this, handler);
|
||||||
|
} else {
|
||||||
|
handler.handle(this);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(Listener<T> listener) {
|
||||||
|
emitSuccess(result ,listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T result() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable cause() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean succeeded() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean failed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> map(V value) {
|
||||||
|
return new SucceededFuture<>(context, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> otherwise(Function<Throwable, T> mapper) {
|
||||||
|
Objects.requireNonNull(mapper, "No null mapper accepted");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<T> otherwise(T value) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Future{result=" + result + "}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.xbib.event.async.impl.future;
|
||||||
|
|
||||||
|
import org.xbib.event.async.AsyncResult;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.impl.ContextInternal;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function compose transformation.
|
||||||
|
*/
|
||||||
|
class Transformation<T, U> extends Operation<U> implements Listener<T> {
|
||||||
|
|
||||||
|
private final Future<T> future;
|
||||||
|
private final Function<AsyncResult<T>, Future<U>> mapper;
|
||||||
|
|
||||||
|
Transformation(ContextInternal context, Future<T> future, Function<AsyncResult<T>, Future<U>> mapper) {
|
||||||
|
super(context);
|
||||||
|
this.future = future;
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T value) {
|
||||||
|
FutureInternal<U> future;
|
||||||
|
try {
|
||||||
|
future = (FutureInternal<U>) mapper.apply(this.future);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tryFail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
future.addListener(newListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
FutureInternal<U> future;
|
||||||
|
try {
|
||||||
|
future = (FutureInternal<U>) mapper.apply(this.future);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
tryFail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
future.addListener(newListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Listener<U> newListener() {
|
||||||
|
return new Listener<U>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(U value) {
|
||||||
|
tryComplete(value);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable failure) {
|
||||||
|
tryFail(failure);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.xbib.event.async.spi;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.AsyncBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for loading Vert.x SPI implementations.
|
||||||
|
*/
|
||||||
|
public interface AsyncServiceProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let the provider initialize the Vert.x builder.
|
||||||
|
*
|
||||||
|
* @param builder the builder
|
||||||
|
*/
|
||||||
|
void init(AsyncBuilder builder);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.xbib.event.async.spi;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.AsyncBuilder;
|
||||||
|
import org.xbib.event.async.impl.AsyncThread;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public interface AsyncThreadFactory extends AsyncServiceProvider {
|
||||||
|
|
||||||
|
AsyncThreadFactory INSTANCE = new AsyncThreadFactory() {
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void init(AsyncBuilder builder) {
|
||||||
|
if (builder.threadFactory() == null) {
|
||||||
|
builder.threadFactory(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default AsyncThread newVertxThread(Runnable target, String name, boolean worker, long maxExecTime, TimeUnit maxExecTimeUnit) {
|
||||||
|
return new AsyncThread(target, name, worker, maxExecTime, maxExecTimeUnit);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.xbib.event.async.spi;
|
||||||
|
|
||||||
|
import org.xbib.event.async.impl.AsyncBuilder;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface for a factory used to obtain an external
|
||||||
|
* {@code ExecutorService}.
|
||||||
|
*/
|
||||||
|
public interface ExecutorServiceFactory extends AsyncServiceProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default instance that delegates to {@link Executors#newFixedThreadPool(int, ThreadFactory)}
|
||||||
|
*/
|
||||||
|
ExecutorServiceFactory INSTANCE = (threadFactory, concurrency, maxConcurrency) ->
|
||||||
|
Executors.newFixedThreadPool(maxConcurrency, threadFactory);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void init(AsyncBuilder builder) {
|
||||||
|
if (builder.executorServiceFactory() == null) {
|
||||||
|
builder.executorServiceFactory(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an ExecutorService
|
||||||
|
*
|
||||||
|
* @param threadFactory A {@link ThreadFactory} which must be used by the
|
||||||
|
* created {@link ExecutorService} to create threads. Null
|
||||||
|
* indicates there is no requirement to use a specific
|
||||||
|
* factory.
|
||||||
|
* @param concurrency The target level of concurrency or 0 which indicates
|
||||||
|
* unspecified
|
||||||
|
* @param maxConcurrency A hard limit to the level of concurrency required,
|
||||||
|
* should be greater than {@code concurrency} or 0 which
|
||||||
|
* indicates unspecified.
|
||||||
|
*
|
||||||
|
* @return an {@link ExecutorService} that can be used to run tasks
|
||||||
|
*/
|
||||||
|
ExecutorService createExecutor(ThreadFactory threadFactory, Integer concurrency, Integer maxConcurrency);
|
||||||
|
|
||||||
|
}
|
68
src/main/java/org/xbib/event/async/streams/Pipe.java
Normal file
68
src/main/java/org/xbib/event/async/streams/Pipe.java
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package org.xbib.event.async.streams;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipe data from a {@link ReadStream} to a {@link WriteStream} and performs flow control where necessary to
|
||||||
|
* prevent the write stream buffer from getting overfull.
|
||||||
|
* <p>
|
||||||
|
* Instances of this class read items from a {@link ReadStream} and write them to a {@link WriteStream}. If data
|
||||||
|
* can be read faster than it can be written this could result in the write queue of the {@link WriteStream} growing
|
||||||
|
* without bound, eventually causing it to exhaust all available RAM.
|
||||||
|
* <p>
|
||||||
|
* To prevent this, after each write, instances of this class check whether the write queue of the {@link
|
||||||
|
* WriteStream} is full, and if so, the {@link ReadStream} is paused, and a {@code drainHandler} is set on the
|
||||||
|
* {@link WriteStream}.
|
||||||
|
* <p>
|
||||||
|
* When the {@link WriteStream} has processed half of its backlog, the {@code drainHandler} will be
|
||||||
|
* called, which results in the pump resuming the {@link ReadStream}.
|
||||||
|
* <p>
|
||||||
|
* This class can be used to pipe from any {@link ReadStream} to any {@link WriteStream}.
|
||||||
|
* <p>
|
||||||
|
*/
|
||||||
|
public interface Pipe<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to {@code true} to call {@link WriteStream#end()} when the source {@code ReadStream} fails, {@code false} otherwise.
|
||||||
|
*
|
||||||
|
* @param end {@code true} to end the stream on a source {@code ReadStream} failure
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
Pipe<T> endOnFailure(boolean end);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to {@code true} to call {@link WriteStream#end()} when the source {@code ReadStream} succeeds, {@code false} otherwise.
|
||||||
|
*
|
||||||
|
* @param end {@code true} to end the stream on a source {@code ReadStream} success
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
Pipe<T> endOnSuccess(boolean end);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to {@code true} to call {@link WriteStream#end()} when the source {@code ReadStream} completes, {@code false} otherwise.
|
||||||
|
* <p>
|
||||||
|
* Calling this overwrites {@link #endOnFailure} and {@link #endOnSuccess}.
|
||||||
|
*
|
||||||
|
* @param end {@code true} to end the stream on a source {@code ReadStream} completion
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
Pipe<T> endOnComplete(boolean end);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start to pipe the elements to the destination {@code WriteStream}.
|
||||||
|
* <p>
|
||||||
|
* When the operation fails with a write error, the source stream is resumed.
|
||||||
|
*
|
||||||
|
* @param dst the destination write stream
|
||||||
|
* @return a future notified when the pipe operation completes
|
||||||
|
*/
|
||||||
|
Future<Void> to(WriteStream<T> dst);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the pipe.
|
||||||
|
* <p>
|
||||||
|
* The streams handlers will be unset and the read stream resumed unless it is already ended.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
|
||||||
|
}
|
102
src/main/java/org/xbib/event/async/streams/ReadStream.java
Normal file
102
src/main/java/org/xbib/event/async/streams/ReadStream.java
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package org.xbib.event.async.streams;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.streams.impl.PipeImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a stream of items that can be read from.
|
||||||
|
* <p>
|
||||||
|
* Any class that implements this interface can be used by a {@link Pipe} to pipe data from it
|
||||||
|
* to a {@link WriteStream}.
|
||||||
|
* <p>
|
||||||
|
* <h3>Streaming mode</h3>
|
||||||
|
* The stream is either in <i>flowing</i> or <i>fetch</i> mode.
|
||||||
|
* <ul>
|
||||||
|
* <i>Initially the stream is in <i>flowing</i> mode.</i>
|
||||||
|
* <li>When the stream is in <i>flowing</i> mode, elements are delivered to the {@code handler}.</li>
|
||||||
|
* <li>When the stream is in <i>fetch</i> mode, only the number of requested elements will be delivered to the {@code handler}.</li>
|
||||||
|
* </ul>
|
||||||
|
* The mode can be changed with the {@link #pause()}, {@link #resume()} and {@link #fetch} methods:
|
||||||
|
* <ul>
|
||||||
|
* <li>Calling {@link #resume()} sets the <i>flowing</i> mode</li>
|
||||||
|
* <li>Calling {@link #pause()} sets the <i>fetch</i> mode and resets the demand to {@code 0}</li>
|
||||||
|
* <li>Calling {@link #fetch(long)} requests a specific amount of elements and adds it to the actual demand</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface ReadStream<T> extends StreamBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an exception handler on the read stream.
|
||||||
|
*
|
||||||
|
* @param handler the exception handler
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
ReadStream<T> exceptionHandler(Handler<Throwable> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a data handler. As data is read, the handler will be called with the data.
|
||||||
|
*
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
ReadStream<T> handler(Handler<T> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause the {@code ReadStream}, it sets the buffer in {@code fetch} mode and clears the actual demand.
|
||||||
|
* <p>
|
||||||
|
* While it's paused, no data will be sent to the data {@code handler}.
|
||||||
|
*
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
ReadStream<T> pause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume reading, and sets the buffer in {@code flowing} mode.
|
||||||
|
* <p/>
|
||||||
|
* If the {@code ReadStream} has been paused, reading will recommence on it.
|
||||||
|
*
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
ReadStream<T> resume();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the specified {@code amount} of elements. If the {@code ReadStream} has been paused, reading will
|
||||||
|
* recommence with the specified {@code amount} of items, otherwise the specified {@code amount} will
|
||||||
|
* be added to the current stream demand.
|
||||||
|
*
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
ReadStream<T> fetch(long amount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an end handler. Once the stream has ended, and there is no more data to be read, this handler will be called.
|
||||||
|
*
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
ReadStream<T> endHandler(Handler<Void> endHandler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause this stream and return a {@link Pipe} to transfer the elements of this stream to a destination {@link WriteStream}.
|
||||||
|
* <p/>
|
||||||
|
* The stream will be resumed when the pipe will be wired to a {@code WriteStream}.
|
||||||
|
*
|
||||||
|
* @return a pipe
|
||||||
|
*/
|
||||||
|
default Pipe<T> pipe() {
|
||||||
|
pause();
|
||||||
|
return new PipeImpl<>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipe this {@code ReadStream} to the {@code WriteStream}.
|
||||||
|
* <p>
|
||||||
|
* Elements emitted by this stream will be written to the write stream until this stream ends or fails.
|
||||||
|
*
|
||||||
|
* @param dst the destination write stream
|
||||||
|
* @return a future notified when the write stream will be ended with the outcome
|
||||||
|
*/
|
||||||
|
default Future<Void> pipeTo(WriteStream<T> dst) {
|
||||||
|
return new PipeImpl<>(this).to(dst);
|
||||||
|
}
|
||||||
|
}
|
17
src/main/java/org/xbib/event/async/streams/StreamBase.java
Normal file
17
src/main/java/org/xbib/event/async/streams/StreamBase.java
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package org.xbib.event.async.streams;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interface for a stream.
|
||||||
|
*/
|
||||||
|
public interface StreamBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an exception handler.
|
||||||
|
*
|
||||||
|
* @param handler the handler
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
StreamBase exceptionHandler(Handler<Throwable> handler);
|
||||||
|
}
|
95
src/main/java/org/xbib/event/async/streams/WriteStream.java
Normal file
95
src/main/java/org/xbib/event/async/streams/WriteStream.java
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package org.xbib.event.async.streams;
|
||||||
|
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Represents a stream of data that can be written to.
|
||||||
|
* <p>
|
||||||
|
* Any class that implements this interface can be used by a {@link Pipe} to pipe data from a {@code ReadStream}
|
||||||
|
* to it.
|
||||||
|
*/
|
||||||
|
public interface WriteStream<T> extends StreamBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an exception handler on the write stream.
|
||||||
|
*
|
||||||
|
* @param handler the exception handler
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
WriteStream<T> exceptionHandler(Handler<Throwable> handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write some data to the stream.
|
||||||
|
*
|
||||||
|
* <p> The data is usually put on an internal write queue, and the write actually happens
|
||||||
|
* asynchronously. To avoid running out of memory by putting too much on the write queue,
|
||||||
|
* check the {@link #writeQueueFull} method before writing. This is done automatically if
|
||||||
|
* using a {@link Pipe}.
|
||||||
|
*
|
||||||
|
* <p> When the {@code data} is moved from the queue to the actual medium, the returned
|
||||||
|
* {@link Future} will be completed with the write result, e.g the future is succeeded
|
||||||
|
* when a server HTTP response buffer is written to the socket and failed if the remote
|
||||||
|
* client has closed the socket while the data was still pending for write.
|
||||||
|
*
|
||||||
|
* @param data the data to write
|
||||||
|
* @return a future completed with the write result
|
||||||
|
*/
|
||||||
|
Future<Void> write(T data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends the stream.
|
||||||
|
* <p>
|
||||||
|
* Once the stream has ended, it cannot be used any more.
|
||||||
|
*
|
||||||
|
* @return a future completed with the result
|
||||||
|
*/
|
||||||
|
Future<Void> end();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #end()} but writes some data to the stream before ending.
|
||||||
|
*
|
||||||
|
* @implSpec The default default implementation calls sequentially {@link #write(Object)} then {@link #end()}
|
||||||
|
* @apiNote Implementations might want to perform a single operation
|
||||||
|
* @param data the data to write
|
||||||
|
* @return a future completed with the result
|
||||||
|
*/
|
||||||
|
default Future<Void> end(T data) {
|
||||||
|
return write(data).compose(v -> end());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the maximum size of the write queue to {@code maxSize}. You will still be able to write to the stream even
|
||||||
|
* if there is more than {@code maxSize} items in the write queue. This is used as an indicator by classes such as
|
||||||
|
* {@link Pipe} to provide flow control.
|
||||||
|
* <p/>
|
||||||
|
* The value is defined by the implementation of the stream.
|
||||||
|
*
|
||||||
|
* @param maxSize the max size of the write stream
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
WriteStream<T> setWriteQueueMaxSize(int maxSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will return {@code true} if there are more bytes in the write queue than the value set using {@link
|
||||||
|
* #setWriteQueueMaxSize}
|
||||||
|
*
|
||||||
|
* @return {@code true} if write queue is full
|
||||||
|
*/
|
||||||
|
boolean writeQueueFull();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a drain handler on the stream. If the write queue is full, then the handler will be called when the write
|
||||||
|
* queue is ready to accept buffers again. See {@link Pipe} for an example of this being used.
|
||||||
|
*
|
||||||
|
* <p> The stream implementation defines when the drain handler, for example it could be when the queue size has been
|
||||||
|
* reduced to {@code maxSize / 2}.
|
||||||
|
*
|
||||||
|
* @param handler the handler
|
||||||
|
* @return a reference to this, so the API can be used fluently
|
||||||
|
*/
|
||||||
|
WriteStream<T> drainHandler(Handler<Void> handler);
|
||||||
|
|
||||||
|
}
|
141
src/main/java/org/xbib/event/async/streams/impl/PipeImpl.java
Normal file
141
src/main/java/org/xbib/event/async/streams/impl/PipeImpl.java
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
package org.xbib.event.async.streams.impl;
|
||||||
|
|
||||||
|
import org.xbib.event.async.AsyncResult;
|
||||||
|
import org.xbib.event.async.Future;
|
||||||
|
import org.xbib.event.async.Handler;
|
||||||
|
import org.xbib.event.async.Promise;
|
||||||
|
import org.xbib.event.async.EventException;
|
||||||
|
import org.xbib.event.async.streams.Pipe;
|
||||||
|
import org.xbib.event.async.streams.ReadStream;
|
||||||
|
import org.xbib.event.async.streams.WriteStream;
|
||||||
|
|
||||||
|
public class PipeImpl<T> implements Pipe<T> {
|
||||||
|
|
||||||
|
private final Promise<Void> result;
|
||||||
|
private final ReadStream<T> src;
|
||||||
|
private boolean endOnSuccess = true;
|
||||||
|
private boolean endOnFailure = true;
|
||||||
|
private WriteStream<T> dst;
|
||||||
|
|
||||||
|
public PipeImpl(ReadStream<T> src) {
|
||||||
|
this.src = src;
|
||||||
|
this.result = Promise.promise();
|
||||||
|
|
||||||
|
// Set handlers now
|
||||||
|
src.endHandler(result::tryComplete);
|
||||||
|
src.exceptionHandler(result::tryFail);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Pipe<T> endOnFailure(boolean end) {
|
||||||
|
endOnFailure = end;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Pipe<T> endOnSuccess(boolean end) {
|
||||||
|
endOnSuccess = end;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Pipe<T> endOnComplete(boolean end) {
|
||||||
|
endOnSuccess = end;
|
||||||
|
endOnFailure = end;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWriteResult(AsyncResult<Void> ack) {
|
||||||
|
if (ack.failed()) {
|
||||||
|
result.tryFail(new WriteException(ack.cause()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<Void> to(WriteStream<T> ws) {
|
||||||
|
Promise<Void> promise = Promise.promise();
|
||||||
|
if (ws == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
synchronized (PipeImpl.this) {
|
||||||
|
if (dst != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
dst = ws;
|
||||||
|
}
|
||||||
|
Handler<Void> drainHandler = v -> src.resume();
|
||||||
|
src.handler(item -> {
|
||||||
|
ws.write(item).onComplete(this::handleWriteResult);
|
||||||
|
if (ws.writeQueueFull()) {
|
||||||
|
src.pause();
|
||||||
|
ws.drainHandler(drainHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
src.resume();
|
||||||
|
result.future().onComplete(ar -> {
|
||||||
|
try {
|
||||||
|
src.handler(null);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
src.exceptionHandler(null);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
src.endHandler(null);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
handleSuccess(promise);
|
||||||
|
} else {
|
||||||
|
Throwable err = ar.cause();
|
||||||
|
if (err instanceof WriteException) {
|
||||||
|
src.resume();
|
||||||
|
err = err.getCause();
|
||||||
|
}
|
||||||
|
handleFailure(err, promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSuccess(Promise<Void> promise) {
|
||||||
|
if (endOnSuccess) {
|
||||||
|
dst.end().onComplete(promise);
|
||||||
|
} else {
|
||||||
|
promise.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFailure(Throwable cause, Promise<Void> completionHandler) {
|
||||||
|
if (endOnFailure){
|
||||||
|
dst
|
||||||
|
.end()
|
||||||
|
.transform(ar -> Future.<Void>failedFuture(cause))
|
||||||
|
.onComplete(completionHandler);
|
||||||
|
} else {
|
||||||
|
completionHandler.fail(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
synchronized (this) {
|
||||||
|
src.exceptionHandler(null);
|
||||||
|
src.handler(null);
|
||||||
|
if (dst != null) {
|
||||||
|
dst.drainHandler(null);
|
||||||
|
dst.exceptionHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventException err = new EventException("Pipe closed", true);
|
||||||
|
if (result.tryFail(err)) {
|
||||||
|
src.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class WriteException extends EventException {
|
||||||
|
private WriteException(Throwable cause) {
|
||||||
|
super(cause, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.xbib.event.yield.AsyncQuery;
|
import org.xbib.event.yield.AsyncQuery;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
import org.xbib.event.yield.AsyncQuery;
|
import org.xbib.event.yield.AsyncQuery;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.event.async;
|
package org.xbib.event.io;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
181
src/main/java/org/xbib/event/loop/AbstractEventExecutor.java
Normal file
181
src/main/java/org/xbib/event/loop/AbstractEventExecutor.java
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import org.xbib.event.DefaultProgressivePromise;
|
||||||
|
import org.xbib.event.DefaultPromise;
|
||||||
|
import org.xbib.event.FailedFuture;
|
||||||
|
import org.xbib.event.Future;
|
||||||
|
import org.xbib.event.ProgressivePromise;
|
||||||
|
import org.xbib.event.Promise;
|
||||||
|
import org.xbib.event.PromiseTask;
|
||||||
|
import org.xbib.event.ScheduledFuture;
|
||||||
|
import org.xbib.event.SucceededFuture;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.AbstractExecutorService;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.RunnableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for {@link EventExecutor} implementations.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractEventExecutor extends AbstractExecutorService implements EventExecutor {
|
||||||
|
private static final Logger logger = Logger.getLogger(AbstractEventExecutor.class.getName());
|
||||||
|
|
||||||
|
static final long DEFAULT_SHUTDOWN_QUIET_PERIOD = 2;
|
||||||
|
static final long DEFAULT_SHUTDOWN_TIMEOUT = 15;
|
||||||
|
|
||||||
|
private final EventExecutorGroup parent;
|
||||||
|
private final Collection<EventExecutor> selfCollection = Collections.<EventExecutor>singleton(this);
|
||||||
|
|
||||||
|
protected AbstractEventExecutor() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractEventExecutor(EventExecutorGroup parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventExecutorGroup parent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventExecutor next() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inEventLoop() {
|
||||||
|
return inEventLoop(Thread.currentThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<EventExecutor> iterator() {
|
||||||
|
return selfCollection.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> shutdownGracefully() {
|
||||||
|
return shutdownGracefully(DEFAULT_SHUTDOWN_QUIET_PERIOD, DEFAULT_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated {@link #shutdownGracefully(long, long, TimeUnit)} or {@link #shutdownGracefully()} instead.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public abstract void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated {@link #shutdownGracefully(long, long, TimeUnit)} or {@link #shutdownGracefully()} instead.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public List<Runnable> shutdownNow() {
|
||||||
|
shutdown();
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Promise<V> newPromise() {
|
||||||
|
return new DefaultPromise<V>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> ProgressivePromise<V> newProgressivePromise() {
|
||||||
|
return new DefaultProgressivePromise<V>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> newSucceededFuture(V result) {
|
||||||
|
return new SucceededFuture<V>(this, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> newFailedFuture(Throwable cause) {
|
||||||
|
return new FailedFuture<V>(this, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> submit(Runnable task) {
|
||||||
|
return (Future<?>) super.submit(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> submit(Runnable task, T result) {
|
||||||
|
return (Future<T>) super.submit(task, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> submit(Callable<T> task) {
|
||||||
|
return (Future<T>) super.submit(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
|
||||||
|
return new PromiseTask<T>(this, runnable, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
|
||||||
|
return new PromiseTask<T>(this, callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> schedule(Runnable command, long delay,
|
||||||
|
TimeUnit unit) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to execute the given {@link Runnable} and just log if it throws a {@link Throwable}.
|
||||||
|
*/
|
||||||
|
protected static void safeExecute(Runnable task) {
|
||||||
|
try {
|
||||||
|
task.run();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.log(Level.WARNING, "A task raised an exception. Task: " + task, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #execute(Runnable)} but does not guarantee the task will be run until either
|
||||||
|
* a non-lazy task is executed or the executor is shut down.
|
||||||
|
*
|
||||||
|
* This is equivalent to submitting a {@link AbstractEventExecutor.LazyRunnable} to
|
||||||
|
* {@link #execute(Runnable)} but for an arbitrary {@link Runnable}.
|
||||||
|
*
|
||||||
|
* The default implementation just delegates to {@link #execute(Runnable)}.
|
||||||
|
*/
|
||||||
|
public void lazyExecute(Runnable task) {
|
||||||
|
execute(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for {@link Runnable} to indicate that it should be queued for execution
|
||||||
|
* but does not need to run immediately.
|
||||||
|
*/
|
||||||
|
public interface LazyRunnable extends Runnable { }
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import org.xbib.event.Future;
|
||||||
|
import org.xbib.event.ScheduledFuture;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for {@link EventExecutorGroup} implementations.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractEventExecutorGroup implements EventExecutorGroup {
|
||||||
|
@Override
|
||||||
|
public Future<?> submit(Runnable task) {
|
||||||
|
return next().submit(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> submit(Runnable task, T result) {
|
||||||
|
return next().submit(task, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> submit(Callable<T> task) {
|
||||||
|
return next().submit(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
|
||||||
|
return next().schedule(command, delay, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
|
||||||
|
return next().schedule(callable, delay, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
|
||||||
|
return next().scheduleAtFixedRate(command, initialDelay, period, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
|
||||||
|
return next().scheduleWithFixedDelay(command, initialDelay, delay, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> shutdownGracefully() {
|
||||||
|
return shutdownGracefully(2, 15, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
|
||||||
|
throws InterruptedException {
|
||||||
|
return next().invokeAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<java.util.concurrent.Future<T>> invokeAll(
|
||||||
|
Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
return next().invokeAll(tasks, timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
|
||||||
|
return next().invokeAny(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
return next().invokeAny(tasks, timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable command) {
|
||||||
|
next().execute(command);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import org.xbib.event.ScheduledFuture;
|
||||||
|
import org.xbib.event.ScheduledFutureTask;
|
||||||
|
import org.xbib.event.util.DefaultPriorityQueue;
|
||||||
|
import org.xbib.event.util.PriorityQueue;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.xbib.event.ScheduledFutureTask.deadlineNanos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for {@link EventExecutor}s that want to support scheduling.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractScheduledEventExecutor extends AbstractEventExecutor {
|
||||||
|
private static final Comparator<ScheduledFutureTask<?>> SCHEDULED_FUTURE_TASK_COMPARATOR =
|
||||||
|
new Comparator<ScheduledFutureTask<?>>() {
|
||||||
|
@Override
|
||||||
|
public int compare(ScheduledFutureTask<?> o1, ScheduledFutureTask<?> o2) {
|
||||||
|
return o1.compareTo(o2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Runnable WAKEUP_TASK = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() { } // Do nothing
|
||||||
|
};
|
||||||
|
|
||||||
|
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
|
||||||
|
|
||||||
|
long nextTaskId;
|
||||||
|
|
||||||
|
protected AbstractScheduledEventExecutor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractScheduledEventExecutor(EventExecutorGroup parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static long nanoTime() {
|
||||||
|
return ScheduledFutureTask.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an arbitrary deadline {@code deadlineNanos}, calculate the number of nano seconds from now
|
||||||
|
* {@code deadlineNanos} would expire.
|
||||||
|
* @param deadlineNanos An arbitrary deadline in nano seconds.
|
||||||
|
* @return the number of nano seconds from now {@code deadlineNanos} would expire.
|
||||||
|
*/
|
||||||
|
protected static long deadlineToDelayNanos(long deadlineNanos) {
|
||||||
|
return ScheduledFutureTask.deadlineToDelayNanos(deadlineNanos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial value used for delay and computations based upon a monatomic time source.
|
||||||
|
* @return initial value used for delay and computations based upon a monatomic time source.
|
||||||
|
*/
|
||||||
|
protected static long initialNanoTime() {
|
||||||
|
return ScheduledFutureTask.initialNanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
|
||||||
|
if (scheduledTaskQueue == null) {
|
||||||
|
scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(
|
||||||
|
SCHEDULED_FUTURE_TASK_COMPARATOR,
|
||||||
|
// Use same initial capacity as java.util.PriorityQueue
|
||||||
|
11);
|
||||||
|
}
|
||||||
|
return scheduledTaskQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNullOrEmpty(Queue<ScheduledFutureTask<?>> queue) {
|
||||||
|
return queue == null || queue.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel all scheduled tasks.
|
||||||
|
*
|
||||||
|
* This method MUST be called only when {@link #inEventLoop()} is {@code true}.
|
||||||
|
*/
|
||||||
|
protected void cancelScheduledTasks() {
|
||||||
|
assert inEventLoop();
|
||||||
|
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
|
||||||
|
if (isNullOrEmpty(scheduledTaskQueue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ScheduledFutureTask<?>[] scheduledTasks =
|
||||||
|
scheduledTaskQueue.toArray(new ScheduledFutureTask<?>[0]);
|
||||||
|
|
||||||
|
for (ScheduledFutureTask<?> task: scheduledTasks) {
|
||||||
|
task.cancelWithoutRemove(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduledTaskQueue.clearIgnoringIndexes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #pollScheduledTask(long)
|
||||||
|
*/
|
||||||
|
protected final Runnable pollScheduledTask() {
|
||||||
|
return pollScheduledTask(nanoTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link Runnable} which is ready to be executed with the given {@code nanoTime}.
|
||||||
|
* You should use {@link #nanoTime()} to retrieve the correct {@code nanoTime}.
|
||||||
|
*/
|
||||||
|
protected final Runnable pollScheduledTask(long nanoTime) {
|
||||||
|
assert inEventLoop();
|
||||||
|
|
||||||
|
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
|
||||||
|
if (scheduledTask == null || scheduledTask.deadlineNanos() - nanoTime > 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
scheduledTaskQueue.remove();
|
||||||
|
scheduledTask.setConsumed();
|
||||||
|
return scheduledTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the nanoseconds until the next scheduled task is ready to be run or {@code -1} if no task is scheduled.
|
||||||
|
*/
|
||||||
|
protected final long nextScheduledTaskNano() {
|
||||||
|
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
|
||||||
|
return scheduledTask != null ? scheduledTask.delayNanos() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the deadline (in nanoseconds) when the next scheduled task is ready to be run or {@code -1}
|
||||||
|
* if no task is scheduled.
|
||||||
|
*/
|
||||||
|
protected final long nextScheduledTaskDeadlineNanos() {
|
||||||
|
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
|
||||||
|
return scheduledTask != null ? scheduledTask.deadlineNanos() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ScheduledFutureTask<?> peekScheduledTask() {
|
||||||
|
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
|
||||||
|
return scheduledTaskQueue != null ? scheduledTaskQueue.peek() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if a scheduled task is ready for processing.
|
||||||
|
*/
|
||||||
|
protected final boolean hasScheduledTasks() {
|
||||||
|
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
|
||||||
|
return scheduledTask != null && scheduledTask.deadlineNanos() <= nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
|
||||||
|
Objects.requireNonNull(command, "command");
|
||||||
|
Objects.requireNonNull(unit, "unit");
|
||||||
|
if (delay < 0) {
|
||||||
|
delay = 0;
|
||||||
|
}
|
||||||
|
return schedule(new ScheduledFutureTask<Void>(
|
||||||
|
this,
|
||||||
|
command,
|
||||||
|
deadlineNanos(unit.toNanos(delay))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
|
||||||
|
Objects.requireNonNull(callable, "callable");
|
||||||
|
Objects.requireNonNull(unit, "unit");
|
||||||
|
if (delay < 0) {
|
||||||
|
delay = 0;
|
||||||
|
}
|
||||||
|
return schedule(new ScheduledFutureTask<V>(this, callable, deadlineNanos(unit.toNanos(delay))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
|
||||||
|
Objects.requireNonNull(command, "command");
|
||||||
|
Objects.requireNonNull(unit, "unit");
|
||||||
|
if (initialDelay < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("initialDelay: %d (expected: >= 0)", initialDelay));
|
||||||
|
}
|
||||||
|
if (period <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("period: %d (expected: > 0)", period));
|
||||||
|
}
|
||||||
|
return schedule(new ScheduledFutureTask<Void>(
|
||||||
|
this, command, deadlineNanos(unit.toNanos(initialDelay)), unit.toNanos(period)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
|
||||||
|
Objects.requireNonNull(command, "command");
|
||||||
|
Objects.requireNonNull(unit, "unit");
|
||||||
|
if (initialDelay < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("initialDelay: %d (expected: >= 0)", initialDelay));
|
||||||
|
}
|
||||||
|
if (delay <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("delay: %d (expected: > 0)", delay));
|
||||||
|
}
|
||||||
|
return schedule(new ScheduledFutureTask<Void>(
|
||||||
|
this, command, deadlineNanos(unit.toNanos(initialDelay)), -unit.toNanos(delay)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void scheduleFromEventLoop(final ScheduledFutureTask<?> task) {
|
||||||
|
// nextTaskId a long and so there is no chance it will overflow back to 0
|
||||||
|
scheduledTaskQueue().add(task.setId(++nextTaskId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
|
||||||
|
if (inEventLoop()) {
|
||||||
|
scheduleFromEventLoop(task);
|
||||||
|
} else {
|
||||||
|
final long deadlineNanos = task.deadlineNanos();
|
||||||
|
// task will add itself to scheduled task queue when run if not expired
|
||||||
|
if (beforeScheduledTaskSubmitted(deadlineNanos)) {
|
||||||
|
execute(task);
|
||||||
|
} else {
|
||||||
|
lazyExecute(task);
|
||||||
|
// Second hook after scheduling to facilitate race-avoidance
|
||||||
|
if (afterScheduledTaskSubmitted(deadlineNanos)) {
|
||||||
|
execute(WAKEUP_TASK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void removeScheduled(final ScheduledFutureTask<?> task) {
|
||||||
|
assert task.isCancelled();
|
||||||
|
if (inEventLoop()) {
|
||||||
|
scheduledTaskQueue().removeTyped(task);
|
||||||
|
} else {
|
||||||
|
// task will remove itself from scheduled task queue when it runs
|
||||||
|
lazyExecute(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from arbitrary non-{@link EventExecutor} threads prior to scheduled task submission.
|
||||||
|
* Returns {@code true} if the {@link EventExecutor} thread should be woken immediately to
|
||||||
|
* process the scheduled task (if not already awake).
|
||||||
|
* <p>
|
||||||
|
* If {@code false} is returned, {@link #afterScheduledTaskSubmitted(long)} will be called with
|
||||||
|
* the same value <i>after</i> the scheduled task is enqueued, providing another opportunity
|
||||||
|
* to wake the {@link EventExecutor} thread if required.
|
||||||
|
*
|
||||||
|
* @param deadlineNanos deadline of the to-be-scheduled task
|
||||||
|
* relative to {@link AbstractScheduledEventExecutor#nanoTime()}
|
||||||
|
* @return {@code true} if the {@link EventExecutor} thread should be woken, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
protected boolean beforeScheduledTaskSubmitted(long deadlineNanos) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #beforeScheduledTaskSubmitted(long)}. Called only after that method returns false.
|
||||||
|
*
|
||||||
|
* @param deadlineNanos relative to {@link AbstractScheduledEventExecutor#nanoTime()}
|
||||||
|
* @return {@code true} if the {@link EventExecutor} thread should be woken, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
protected boolean afterScheduledTaskSubmitted(long deadlineNanos) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link IllegalStateException} which is raised when a user performed a blocking operation
|
||||||
|
* when the user is in an event loop thread. If a blocking operation is performed in an event loop
|
||||||
|
* thread, the blocking operation will most likely enter a dead lock state, hence throwing this
|
||||||
|
* exception.
|
||||||
|
*/
|
||||||
|
public class BlockingOperationException extends IllegalStateException {
|
||||||
|
|
||||||
|
public BlockingOperationException() { }
|
||||||
|
|
||||||
|
public BlockingOperationException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockingOperationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockingOperationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation which uses simple round-robin to choose next {@link EventExecutor}.
|
||||||
|
*/
|
||||||
|
public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
|
||||||
|
|
||||||
|
public static final DefaultEventExecutorChooserFactory INSTANCE = new DefaultEventExecutorChooserFactory();
|
||||||
|
|
||||||
|
private DefaultEventExecutorChooserFactory() { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventExecutorChooser newChooser(EventExecutor[] executors) {
|
||||||
|
if (isPowerOfTwo(executors.length)) {
|
||||||
|
return new PowerOfTwoEventExecutorChooser(executors);
|
||||||
|
} else {
|
||||||
|
return new GenericEventExecutorChooser(executors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPowerOfTwo(int val) {
|
||||||
|
return (val & -val) == val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
|
||||||
|
private final AtomicInteger idx = new AtomicInteger();
|
||||||
|
private final EventExecutor[] executors;
|
||||||
|
|
||||||
|
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
|
||||||
|
this.executors = executors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventExecutor next() {
|
||||||
|
return executors[idx.getAndIncrement() & executors.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
|
||||||
|
// Use a 'long' counter to avoid non-round-robin behaviour at the 32-bit overflow boundary.
|
||||||
|
// The 64-bit long solves this by placing the overflow so far into the future that no system
|
||||||
|
// will encounter this in practice.
|
||||||
|
private final AtomicLong idx = new AtomicLong();
|
||||||
|
private final EventExecutor[] executors;
|
||||||
|
|
||||||
|
GenericEventExecutorChooser(EventExecutor[] executors) {
|
||||||
|
this.executors = executors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventExecutor next() {
|
||||||
|
return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/main/java/org/xbib/event/loop/EventExecutor.java
Normal file
63
src/main/java/org/xbib/event/loop/EventExecutor.java
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import org.xbib.event.Future;
|
||||||
|
import org.xbib.event.FutureListener;
|
||||||
|
import org.xbib.event.ProgressivePromise;
|
||||||
|
import org.xbib.event.Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link EventExecutor} is a special {@link EventExecutorGroup} which comes
|
||||||
|
* with some handy methods to see if a {@link Thread} is executed in a event loop.
|
||||||
|
* Besides this, it also extends the {@link EventExecutorGroup} to allow for a generic
|
||||||
|
* way to access methods.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface EventExecutor extends EventExecutorGroup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to itself.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
EventExecutor next();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link EventExecutorGroup} which is the parent of this {@link EventExecutor},
|
||||||
|
*/
|
||||||
|
EventExecutorGroup parent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@link #inEventLoop(Thread)} with {@link Thread#currentThread()} as argument
|
||||||
|
*/
|
||||||
|
boolean inEventLoop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return {@code true} if the given {@link Thread} is executed in the event loop,
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
boolean inEventLoop(Thread thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link Promise}.
|
||||||
|
*/
|
||||||
|
<V> Promise<V> newPromise();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ProgressivePromise}.
|
||||||
|
*/
|
||||||
|
<V> ProgressivePromise<V> newProgressivePromise();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link Future} which is marked as succeeded already. So {@link Future#isSuccess()}
|
||||||
|
* will return {@code true}. All {@link FutureListener} added to it will be notified directly. Also
|
||||||
|
* every call of blocking methods will just return without blocking.
|
||||||
|
*/
|
||||||
|
<V> Future<V> newSucceededFuture(V result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link Future} which is marked as failed already. So {@link Future#isSuccess()}
|
||||||
|
* will return {@code false}. All {@link FutureListener} added to it will be notified directly. Also
|
||||||
|
* every call of blocking methods will just return without blocking.
|
||||||
|
*/
|
||||||
|
<V> Future<V> newFailedFuture(Throwable cause);
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory that creates new {@link EventExecutorChooser}s.
|
||||||
|
*/
|
||||||
|
public interface EventExecutorChooserFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link EventExecutorChooser}.
|
||||||
|
*/
|
||||||
|
EventExecutorChooser newChooser(EventExecutor[] executors);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chooses the next {@link EventExecutor} to use.
|
||||||
|
*/
|
||||||
|
interface EventExecutorChooser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the new {@link EventExecutor} to use.
|
||||||
|
*/
|
||||||
|
EventExecutor next();
|
||||||
|
}
|
||||||
|
}
|
82
src/main/java/org/xbib/event/loop/EventExecutorGroup.java
Normal file
82
src/main/java/org/xbib/event/loop/EventExecutorGroup.java
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import org.xbib.event.Future;
|
||||||
|
import org.xbib.event.ScheduledFuture;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link EventExecutorGroup} is responsible for providing the {@link EventExecutor}'s to use
|
||||||
|
* via its {@link #next()} method. Besides this, it is also responsible for handling their
|
||||||
|
* life-cycle and allows shutting them down in a global fashion.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface EventExecutorGroup extends ScheduledExecutorService, Iterable<EventExecutor> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if all {@link EventExecutor}s managed by this {@link EventExecutorGroup}
|
||||||
|
* are being {@linkplain #shutdownGracefully() shut down gracefully} or was {@linkplain #isShutdown() shut down}.
|
||||||
|
*/
|
||||||
|
boolean isShuttingDown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method for {@link #shutdownGracefully(long, long, TimeUnit)} with sensible default values.
|
||||||
|
*
|
||||||
|
* @return the {@link #terminationFuture()}
|
||||||
|
*/
|
||||||
|
Future<?> shutdownGracefully();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals this executor that the caller wants the executor to be shut down. Once this method is called,
|
||||||
|
* {@link #isShuttingDown()} starts to return {@code true}, and the executor prepares to shut itself down.
|
||||||
|
* Unlike {@link #shutdown()}, graceful shutdown ensures that no tasks are submitted for <i>'the quiet period'</i>
|
||||||
|
* (usually a couple seconds) before it shuts itself down. If a task is submitted during the quiet period,
|
||||||
|
* it is guaranteed to be accepted and the quiet period will start over.
|
||||||
|
*
|
||||||
|
* @param quietPeriod the quiet period as described in the documentation
|
||||||
|
* @param timeout the maximum amount of time to wait until the executor is {@linkplain #shutdown()}
|
||||||
|
* regardless if a task was submitted during the quiet period
|
||||||
|
* @param unit the unit of {@code quietPeriod} and {@code timeout}
|
||||||
|
*
|
||||||
|
* @return the {@link #terminationFuture()}
|
||||||
|
*/
|
||||||
|
Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link Future} which is notified when all {@link EventExecutor}s managed by this
|
||||||
|
* {@link EventExecutorGroup} have been terminated.
|
||||||
|
*/
|
||||||
|
Future<?> terminationFuture();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns one of the {@link EventExecutor}s managed by this {@link EventExecutorGroup}.
|
||||||
|
*/
|
||||||
|
EventExecutor next();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Iterator<EventExecutor> iterator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Future<?> submit(Runnable task);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
<T> Future<T> submit(Runnable task, T result);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
<T> Future<T> submit(Callable<T> task);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
|
||||||
|
}
|
6
src/main/java/org/xbib/event/loop/EventLoop.java
Normal file
6
src/main/java/org/xbib/event/loop/EventLoop.java
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
|
||||||
|
EventLoopGroup parent();
|
||||||
|
}
|
24
src/main/java/org/xbib/event/loop/EventLoopException.java
Normal file
24
src/main/java/org/xbib/event/loop/EventLoopException.java
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special {@link RuntimeException} which will be thrown by {@link EventLoop} and {@link EventLoopGroup}
|
||||||
|
* implementations when an error occurs.
|
||||||
|
*/
|
||||||
|
public class EventLoopException extends RuntimeException {
|
||||||
|
|
||||||
|
public EventLoopException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventLoopException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventLoopException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventLoopException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
src/main/java/org/xbib/event/loop/EventLoopGroup.java
Normal file
7
src/main/java/org/xbib/event/loop/EventLoopGroup.java
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
public interface EventLoopGroup extends EventExecutorGroup {
|
||||||
|
|
||||||
|
EventLoop next();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory used to create {@link Queue} instances that will be used to store tasks for an {@link EventLoop}.
|
||||||
|
*
|
||||||
|
* Generally speaking the returned {@link Queue} MUST be thread-safe and depending on the {@link EventLoop}
|
||||||
|
* implementation must be of type {@link java.util.concurrent.BlockingQueue}.
|
||||||
|
*/
|
||||||
|
public interface EventLoopTaskQueueFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Queue} to use.
|
||||||
|
* @param maxCapacity the maximum amount of elements that can be stored in the {@link Queue} at a given point
|
||||||
|
* in time.
|
||||||
|
* @return the new queue.
|
||||||
|
*/
|
||||||
|
Queue<Runnable> newTaskQueue(int maxCapacity);
|
||||||
|
}
|
265
src/main/java/org/xbib/event/loop/GlobalEventExecutor.java
Normal file
265
src/main/java/org/xbib/event/loop/GlobalEventExecutor.java
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import org.xbib.event.FailedFuture;
|
||||||
|
import org.xbib.event.Future;
|
||||||
|
import org.xbib.event.ScheduledFutureTask;
|
||||||
|
import org.xbib.event.thread.DefaultThreadFactory;
|
||||||
|
import org.xbib.event.thread.ThreadExecutorMap;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single-thread singleton {@link EventExecutor}. It starts the thread automatically and stops it when there is no
|
||||||
|
* task pending in the task queue for 1 second. Please note it is not scalable to schedule large number of tasks to
|
||||||
|
* this executor; use a dedicated executor.
|
||||||
|
*/
|
||||||
|
public final class GlobalEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(GlobalEventExecutor.class.getName());
|
||||||
|
|
||||||
|
private static final long SCHEDULE_QUIET_PERIOD_INTERVAL = TimeUnit.SECONDS.toNanos(1);
|
||||||
|
|
||||||
|
public static final GlobalEventExecutor INSTANCE = new GlobalEventExecutor();
|
||||||
|
|
||||||
|
final BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
|
||||||
|
final ScheduledFutureTask<Void> quietPeriodTask = new ScheduledFutureTask<Void>(
|
||||||
|
this, Executors.<Void>callable(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
}, null), ScheduledFutureTask.deadlineNanos(SCHEDULE_QUIET_PERIOD_INTERVAL), -SCHEDULE_QUIET_PERIOD_INTERVAL);
|
||||||
|
|
||||||
|
// because the GlobalEventExecutor is a singleton, tasks submitted to it can come from arbitrary threads and this
|
||||||
|
// can trigger the creation of a thread from arbitrary thread groups; for this reason, the thread factory must not
|
||||||
|
// be sticky about its thread group
|
||||||
|
// visible for testing
|
||||||
|
final ThreadFactory threadFactory;
|
||||||
|
private final TaskRunner taskRunner = new TaskRunner();
|
||||||
|
private final AtomicBoolean started = new AtomicBoolean();
|
||||||
|
volatile Thread thread;
|
||||||
|
|
||||||
|
private final Future<?> terminationFuture = new FailedFuture<Object>(this, new UnsupportedOperationException());
|
||||||
|
|
||||||
|
private GlobalEventExecutor() {
|
||||||
|
scheduledTaskQueue().add(quietPeriodTask);
|
||||||
|
threadFactory = ThreadExecutorMap.apply(new DefaultThreadFactory(
|
||||||
|
DefaultThreadFactory.toPoolName(getClass()), false, Thread.NORM_PRIORITY, null), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the next {@link Runnable} from the task queue and so will block if no task is currently present.
|
||||||
|
*
|
||||||
|
* @return {@code null} if the executor thread has been interrupted or waken up.
|
||||||
|
*/
|
||||||
|
Runnable takeTask() {
|
||||||
|
BlockingQueue<Runnable> taskQueue = this.taskQueue;
|
||||||
|
for (;;) {
|
||||||
|
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
|
||||||
|
if (scheduledTask == null) {
|
||||||
|
Runnable task = null;
|
||||||
|
try {
|
||||||
|
task = taskQueue.take();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
} else {
|
||||||
|
long delayNanos = scheduledTask.delayNanos();
|
||||||
|
Runnable task = null;
|
||||||
|
if (delayNanos > 0) {
|
||||||
|
try {
|
||||||
|
task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Waken up.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (task == null) {
|
||||||
|
// We need to fetch the scheduled tasks now as otherwise there may be a chance that
|
||||||
|
// scheduled tasks are never executed if there is always one task in the taskQueue.
|
||||||
|
// This is for example true for the read task of OIO Transport
|
||||||
|
// See https://github.com/netty/netty/issues/1614
|
||||||
|
fetchFromScheduledTaskQueue();
|
||||||
|
task = taskQueue.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task != null) {
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchFromScheduledTaskQueue() {
|
||||||
|
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
|
||||||
|
Runnable scheduledTask = pollScheduledTask(nanoTime);
|
||||||
|
while (scheduledTask != null) {
|
||||||
|
taskQueue.add(scheduledTask);
|
||||||
|
scheduledTask = pollScheduledTask(nanoTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of tasks that are pending for processing.
|
||||||
|
*/
|
||||||
|
public int pendingTasks() {
|
||||||
|
return taskQueue.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a task to the task queue, or throws a {@link RejectedExecutionException} if this instance was shutdown
|
||||||
|
* before.
|
||||||
|
*/
|
||||||
|
private void addTask(Runnable task) {
|
||||||
|
taskQueue.add(Objects.requireNonNull(task, "task"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inEventLoop(Thread thread) {
|
||||||
|
return thread == this.thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
|
||||||
|
return terminationFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> terminationFuture() {
|
||||||
|
return terminationFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void shutdown() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShuttingDown() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits until the worker thread of this executor has no tasks left in its task queue and terminates itself.
|
||||||
|
* Because a new worker thread will be started again when a new task is submitted, this operation is only useful
|
||||||
|
* when you want to ensure that the worker thread is terminated <strong>after</strong> your application is shut
|
||||||
|
* down and there's no chance of submitting a new task afterwards.
|
||||||
|
*
|
||||||
|
* @return {@code true} if and only if the worker thread has been terminated
|
||||||
|
*/
|
||||||
|
public boolean awaitInactivity(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
Objects.requireNonNull(unit, "unit");
|
||||||
|
|
||||||
|
final Thread thread = this.thread;
|
||||||
|
if (thread == null) {
|
||||||
|
throw new IllegalStateException("thread was not started");
|
||||||
|
}
|
||||||
|
thread.join(unit.toMillis(timeout));
|
||||||
|
return !thread.isAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable task) {
|
||||||
|
addTask(Objects.requireNonNull(task, "task"));
|
||||||
|
if (!inEventLoop()) {
|
||||||
|
startThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startThread() {
|
||||||
|
if (started.compareAndSet(false, true)) {
|
||||||
|
final Thread t = threadFactory.newThread(taskRunner);
|
||||||
|
// Set to null to ensure we not create classloader leaks by holds a strong reference to the inherited
|
||||||
|
// classloader.
|
||||||
|
// See:
|
||||||
|
// - https://github.com/netty/netty/issues/7290
|
||||||
|
// - https://bugs.openjdk.java.net/browse/JDK-7008595
|
||||||
|
t.setContextClassLoader(null);
|
||||||
|
|
||||||
|
// Set the thread before starting it as otherwise inEventLoop() may return false and so produce
|
||||||
|
// an assert error.
|
||||||
|
// See https://github.com/netty/netty/issues/4357
|
||||||
|
thread = t;
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TaskRunner implements Runnable {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (;;) {
|
||||||
|
Runnable task = takeTask();
|
||||||
|
if (task != null) {
|
||||||
|
try {
|
||||||
|
task.run();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.log(Level.WARNING, "Unexpected exception from the global event executor: ", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task != quietPeriodTask) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = GlobalEventExecutor.this.scheduledTaskQueue;
|
||||||
|
// Terminate if there is no task in the queue (except the noop task).
|
||||||
|
if (taskQueue.isEmpty() && (scheduledTaskQueue == null || scheduledTaskQueue.size() == 1)) {
|
||||||
|
// Mark the current thread as stopped.
|
||||||
|
// The following CAS must always success and must be uncontended,
|
||||||
|
// because only one thread should be running at the same time.
|
||||||
|
boolean stopped = started.compareAndSet(true, false);
|
||||||
|
assert stopped;
|
||||||
|
|
||||||
|
// Check if there are pending entries added by execute() or schedule*() while we do CAS above.
|
||||||
|
// Do not check scheduledTaskQueue because it is not thread-safe and can only be mutated from a
|
||||||
|
// TaskRunner actively running tasks.
|
||||||
|
if (taskQueue.isEmpty()) {
|
||||||
|
// A) No new task was added and thus there's nothing to handle
|
||||||
|
// -> safe to terminate because there's nothing left to do
|
||||||
|
// B) A new thread started and handled all the new tasks.
|
||||||
|
// -> safe to terminate the new thread will take care the rest
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are pending tasks added again.
|
||||||
|
if (!started.compareAndSet(false, true)) {
|
||||||
|
// startThread() started a new thread and set 'started' to true.
|
||||||
|
// -> terminate this thread so that the new thread reads from taskQueue exclusively.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New tasks were added, but this worker was faster to set 'started' to true.
|
||||||
|
// i.e. a new worker thread was not started by startThread().
|
||||||
|
// -> keep this thread alive to handle the newly added entries.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
package org.xbib.event.loop;
|
||||||
|
|
||||||
|
import org.xbib.event.DefaultPromise;
|
||||||
|
import org.xbib.event.Future;
|
||||||
|
import org.xbib.event.FutureListener;
|
||||||
|
import org.xbib.event.Promise;
|
||||||
|
import org.xbib.event.thread.DefaultThreadFactory;
|
||||||
|
import org.xbib.event.thread.ThreadPerTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for {@link EventExecutorGroup} implementations that handles their tasks with multiple threads at
|
||||||
|
* the same time.
|
||||||
|
*/
|
||||||
|
public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
|
||||||
|
|
||||||
|
private final EventExecutor[] children;
|
||||||
|
private final Set<EventExecutor> readonlyChildren;
|
||||||
|
private final AtomicInteger terminatedChildren = new AtomicInteger();
|
||||||
|
private final Promise<?> terminationFuture = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
|
||||||
|
private final EventExecutorChooserFactory.EventExecutorChooser chooser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance.
|
||||||
|
*
|
||||||
|
* @param nThreads the number of threads that will be used by this instance.
|
||||||
|
* @param threadFactory the ThreadFactory to use, or {@code null} if the default should be used.
|
||||||
|
* @param args arguments which will passed to each {@link #newChild(Executor, Object...)} call
|
||||||
|
*/
|
||||||
|
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
|
||||||
|
this(nThreads, threadFactory == null ? null : new ThreadPerTaskExecutor(threadFactory), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance.
|
||||||
|
*
|
||||||
|
* @param nThreads the number of threads that will be used by this instance.
|
||||||
|
* @param executor the Executor to use, or {@code null} if the default should be used.
|
||||||
|
* @param args arguments which will passed to each {@link #newChild(Executor, Object...)} call
|
||||||
|
*/
|
||||||
|
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
|
||||||
|
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance.
|
||||||
|
*
|
||||||
|
* @param nThreads the number of threads that will be used by this instance.
|
||||||
|
* @param executor the Executor to use, or {@code null} if the default should be used.
|
||||||
|
* @param chooserFactory the {@link EventExecutorChooserFactory} to use.
|
||||||
|
* @param args arguments which will passed to each {@link #newChild(Executor, Object...)} call
|
||||||
|
*/
|
||||||
|
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
|
||||||
|
EventExecutorChooserFactory chooserFactory, Object... args) {
|
||||||
|
if (nThreads <= 0) {
|
||||||
|
throw new IllegalArgumentException("nThreads must be positive " + nThreads);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executor == null) {
|
||||||
|
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
children = new EventExecutor[nThreads];
|
||||||
|
|
||||||
|
for (int i = 0; i < nThreads; i ++) {
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
children[i] = newChild(executor, args);
|
||||||
|
success = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// TODO: Think about if this is a good exception type
|
||||||
|
throw new IllegalStateException("failed to create a child event loop", e);
|
||||||
|
} finally {
|
||||||
|
if (!success) {
|
||||||
|
for (int j = 0; j < i; j ++) {
|
||||||
|
children[j].shutdownGracefully();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < i; j ++) {
|
||||||
|
EventExecutor e = children[j];
|
||||||
|
try {
|
||||||
|
while (!e.isTerminated()) {
|
||||||
|
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException interrupted) {
|
||||||
|
// Let the caller handle the interruption.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chooser = chooserFactory.newChooser(children);
|
||||||
|
|
||||||
|
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(Future<Object> future) throws Exception {
|
||||||
|
if (terminatedChildren.incrementAndGet() == children.length) {
|
||||||
|
terminationFuture.setSuccess(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (EventExecutor e: children) {
|
||||||
|
e.terminationFuture().addListener(terminationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
|
||||||
|
Collections.addAll(childrenSet, children);
|
||||||
|
readonlyChildren = Collections.unmodifiableSet(childrenSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ThreadFactory newDefaultThreadFactory() {
|
||||||
|
return new DefaultThreadFactory(getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventExecutor next() {
|
||||||
|
return chooser.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<EventExecutor> iterator() {
|
||||||
|
return readonlyChildren.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of {@link EventExecutor} this implementation uses. This number is the maps
|
||||||
|
* 1:1 to the threads it use.
|
||||||
|
*/
|
||||||
|
public final int executorCount() {
|
||||||
|
return children.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new EventExecutor which will later then accessible via the {@link #next()} method. This method will be
|
||||||
|
* called for each thread that will serve this {@link MultithreadEventExecutorGroup}.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
|
||||||
|
for (EventExecutor l: children) {
|
||||||
|
l.shutdownGracefully(quietPeriod, timeout, unit);
|
||||||
|
}
|
||||||
|
return terminationFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> terminationFuture() {
|
||||||
|
return terminationFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void shutdown() {
|
||||||
|
for (EventExecutor l: children) {
|
||||||
|
l.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShuttingDown() {
|
||||||
|
for (EventExecutor l: children) {
|
||||||
|
if (!l.isShuttingDown()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
for (EventExecutor l: children) {
|
||||||
|
if (!l.isShutdown()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
for (EventExecutor l: children) {
|
||||||
|
if (!l.isTerminated()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException {
|
||||||
|
long deadline = System.nanoTime() + unit.toNanos(timeout);
|
||||||
|
loop: for (EventExecutor l: children) {
|
||||||
|
for (;;) {
|
||||||
|
long timeLeft = deadline - System.nanoTime();
|
||||||
|
if (timeLeft <= 0) {
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
if (l.awaitTermination(timeLeft, TimeUnit.NANOSECONDS)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isTerminated();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue