net.schmizz.concurrent.Promise Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.concurrent;
import net.schmizz.sshj.common.LoggerFactory;
import org.slf4j.Logger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Represents promised data of the parameterized type {@code V} and allows waiting on it. An exception may also be
* delivered to a waiter, and will be of the parameterized type {@code T}.
*
* For atomic operations on a promise, e.g. checking if a value is delivered and if it is not then setting it, the
* associated lock for the promise should be acquired while doing so.
*/
public class Promise {
private final Logger log;
private final String name;
private final ExceptionChainer chainer;
private final ReentrantLock lock;
private final Condition cond;
private V val;
private T pendingEx;
/**
* Creates this promise with given {@code name} and exception {@code chainer}. Allocates a new {@link
* java.util.concurrent.locks.Lock lock} object for this promise.
*
* @param name name of this promise
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
*/
public Promise(String name, ExceptionChainer chainer, LoggerFactory loggerFactory) {
this(name, chainer, null, loggerFactory);
}
/**
* Creates this promise with given {@code name}, exception {@code chainer}, and associated {@code lock}.
*
* @param name name of this promise
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
* @param lock lock to use
*/
public Promise(String name, ExceptionChainer chainer, ReentrantLock lock, LoggerFactory loggerFactory) {
this.name = name;
this.chainer = chainer;
this.lock = lock == null ? new ReentrantLock() : lock;
this.log = loggerFactory.getLogger(getClass());
this.cond = this.lock.newCondition();
}
/**
* Set this promise's value to {@code val}. Any waiters will be delivered this value.
*
* @param val the value
*/
public void deliver(V val) {
lock.lock();
try {
log.debug("Setting <<{}>> to `{}`", name, val);
this.val = val;
cond.signalAll();
} finally {
lock.unlock();
}
}
/**
* Queues error that will be thrown in any waiting thread or any thread that attempts to wait on this promise
* hereafter.
*
* @param e the error
*/
public void deliverError(Throwable e) {
lock.lock();
try {
pendingEx = chainer.chain(e);
cond.signalAll();
} finally {
lock.unlock();
}
}
/** Clears this promise by setting its value and queued exception to {@code null}. */
public void clear() {
lock.lock();
try {
pendingEx = null;
log.debug("Clearing <<{}>>", name);
val = null;
} finally {
lock.unlock();
}
}
/**
* Wait indefinitely for this promise's value to be deliver.
*
* @return the value
*
* @throws T in case another thread informs the promise of an error meanwhile
*/
public V retrieve()
throws T {
return tryRetrieve(0, TimeUnit.SECONDS);
}
/**
* Wait for {@code timeout} duration for this promise's value to be deliver.
*
* @param timeout the timeout
* @param unit time unit for the timeout
*
* @return the value
*
* @throws T in case another thread informs the promise of an error meanwhile, or the timeout expires
*/
public V retrieve(long timeout, TimeUnit unit)
throws T {
final V value = tryRetrieve(timeout, unit);
if (value == null)
throw chainer.chain(new TimeoutException("Timeout expired: " + timeout + " " + unit));
else
return value;
}
/**
* Wait for {@code timeout} duration for this promise's value to be deliver.
*
* If the value is not deliver by the time the timeout expires, returns {@code null}.
*
* @param timeout the timeout
* @param unit time unit for the timeout
*
* @return the value or {@code null}
*
* @throws T in case another thread informs the promise of an error meanwhile
*/
public V tryRetrieve(long timeout, TimeUnit unit)
throws T {
lock.lock();
try {
if (pendingEx != null)
throw pendingEx;
if (val != null)
return val;
log.debug("Awaiting <<{}>>", name);
if (timeout == 0) {
while (val == null && pendingEx == null) {
cond.await();
}
} else {
if (!cond.await(timeout, unit))
return null;
}
if (pendingEx != null) {
log.error("<<{}>> woke to: {}", name, pendingEx.toString());
throw pendingEx;
}
return val;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw chainer.chain(ie);
} finally {
lock.unlock();
}
}
/** @return whether this promise has a value delivered, and no error waiting to pop. */
public boolean isDelivered() {
lock.lock();
try {
return pendingEx == null && val != null;
} finally {
lock.unlock();
}
}
/** @return whether this promise has been delivered an error. */
public boolean inError() {
lock.lock();
try {
return pendingEx != null;
} finally {
lock.unlock();
}
}
/** @return whether this promise was fulfilled with either a value or an error. */
public boolean isFulfilled() {
lock.lock();
try {
return pendingEx != null || val != null;
} finally {
lock.unlock();
}
}
/** @return whether this promise has threads waiting on it. */
public boolean hasWaiters() {
lock.lock();
try {
return lock.hasWaiters(cond);
} finally {
lock.unlock();
}
}
/** Acquire the lock associated with this promise. */
public void lock() {
lock.lock();
}
/** Release the lock associated with this promise. */
public void unlock() {
lock.unlock();
}
@Override
public String toString() {
return name;
}
}