added the async code from Vert.x and Netty

This commit is contained in:
Jörg Prante 2023-04-26 17:57:54 +02:00
parent 156af9f273
commit 19e0a1aaec
131 changed files with 12987 additions and 43 deletions

View file

@ -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

View file

@ -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

View file

@ -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'
if (project.convention.findPlugin(JavaPluginConvention)) { languageLevel = '17'
//sourceSets.main.output.classesDirs = file("build/classes/java/main") vcs = 'Git'
//sourceSets.test.output.classesDirs = file("build/classes/java/test") }
} }

View file

@ -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;

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}
}

View file

@ -1,7 +0,0 @@
package org.xbib.event;
public class EventService {
public EventService() {
}
}

View 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;
}
}

View file

@ -1,7 +0,0 @@
package org.xbib.event;
public class FileFollowEvent {
public FileFollowEvent() {
}
}

View 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);
}

View file

@ -0,0 +1,4 @@
package org.xbib.event;
public interface FutureListener<V> extends GenericFutureListener<Future<V>> {
}

View 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;
}

View file

@ -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;
}

View 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();
}

View 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();
}

View 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();
}

View 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(')');
}
}

View 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> {
}

View 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;
}
}

View 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;
}
}

View 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();
}

View 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 +
'}';
}
}

View 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);
}
}

View 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);
}

View 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;
}
}

View 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();
}

View 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);
}
}

View 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();
}
}

View 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);
}

View file

@ -0,0 +1,8 @@
package org.xbib.event.async;
public class NoStackTraceThrowable extends Throwable {
public NoStackTraceThrowable(String message) {
super(message, null, false, false);
}
}

View 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();
}

View 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();
}

View 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();
}

View 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;
}
}

View 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;
}
}
}

View 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();
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View file

@ -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();
}
}

View 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;
}

View 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;
}
}
}
}
}

View 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();
}
}

View file

@ -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(')');
}
}

View file

@ -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);
}
};
}
}

View file

@ -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);
}
});
}
}

View 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() + "}";
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View 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;
}
}

View 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;
}
}
}

View file

@ -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);
}

View 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);
}

View 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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}
}

View file

@ -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();
}

View file

@ -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 + "}";
}
}

View file

@ -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);
}
};
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
}

View 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();
}

View 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);
}
}

View 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);
}

View 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);
}

View 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);
}
}
}

View file

@ -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;

View file

@ -1,4 +1,4 @@
package org.xbib.event.async; package org.xbib.event.io;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;

View file

@ -1,4 +1,4 @@
package org.xbib.event.async; package org.xbib.event.io;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;

View file

@ -1,4 +1,4 @@
package org.xbib.event.async; package org.xbib.event.io;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -1,4 +1,4 @@
package org.xbib.event.async; package org.xbib.event.io;
import org.reactivestreams.Subscription; import org.reactivestreams.Subscription;

View file

@ -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;

View file

@ -1,4 +1,4 @@
package org.xbib.event.async; package org.xbib.event.io;
import java.util.function.Consumer; import java.util.function.Consumer;

View 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 { }
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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)];
}
}
}

View 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);
}

View file

@ -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();
}
}

View 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);
}

View file

@ -0,0 +1,6 @@
package org.xbib.event.loop;
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
EventLoopGroup parent();
}

View 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);
}
}

View file

@ -0,0 +1,7 @@
package org.xbib.event.loop;
public interface EventLoopGroup extends EventExecutorGroup {
EventLoop next();
}

View file

@ -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);
}

View 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.
}
}
}
}
}

View file

@ -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