syndicate-java/src/main/java/org/syndicate_lang/actors/Promise.java

254 lines
7.1 KiB
Java

package org.syndicate_lang.actors;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Function;
public class Promise<T> implements Future<T> {
public static final TimeoutException TIMEOUT_WAITING_FOR_PROMISE_RESOLUTION = new TimeoutException("Waiting for promise resolution");
public enum State {
PENDING,
FULFILLED,
REJECTED
}
private volatile State _state = State.PENDING;
private T _value = null;
private Throwable _reason = null;
private List<Consumer<T>> _resolvers = null;
private List<Consumer<Throwable>> _rejecters = null;
public Promise() {}
public static<T> Promise<T> resolved() {
return resolved(null);
}
public static<T> Promise<T> resolved(T v) {
var p = new Promise<T>();
p.resolveWith(v);
return p;
}
public static<T> Promise<T> rejected(Throwable e) {
var p = new Promise<T>();
p.rejectWith(e);
return p;
}
public State getState() {
return _state;
}
public boolean isPending() {
return _state == State.PENDING;
}
public boolean isFulfilled() {
return _state == State.FULFILLED;
}
public boolean isRejected() {
return _state == State.REJECTED;
}
public T getValue() {
return _value;
}
public Throwable getReason() {
return _reason;
}
public void resolve() {
this.resolveWith(null);
}
public void resolveWith(T t) {
List<Consumer<T>> worklist = null;
synchronized (this) {
if (this.isPending()) {
this._value = t;
this._state = State.FULFILLED;
worklist = _resolvers;
_resolvers = null;
_rejecters = null;
}
}
if (worklist != null) {
for (var callback : worklist) {
callback.accept(t);
}
}
}
public void chain(Promise<T> t) {
if (this == t) {
throw new IllegalArgumentException("cannot chain promise immediately to itself");
}
t.whenFulfilled(this::resolveWith);
t.whenRejected(this::rejectWith);
}
public void rejectWith(Throwable e) {
List<Consumer<Throwable>> worklist = null;
synchronized (this) {
if (this.isPending()) {
this._reason = e;
this._state = State.REJECTED;
worklist = _rejecters;
_resolvers = null;
_rejecters = null;
}
}
if (worklist != null) {
for (var callback : worklist) {
callback.accept(e);
}
}
}
public<R> Promise<R> andThen(Actor a, Function<T, R> ok) {
return this.andThen(a, ok, null);
}
public void resolveCalling(ThrowingSupplier<T> f) {
try {
this.resolveWith(f.get());
} catch (Throwable e) {
this.rejectWith(e);
}
}
public synchronized<R> Promise<R> andThen(Actor a0, final Function<T, R> ok, final Function<Throwable, R> fail) {
final Actor a = a0 != null ? a0 : new Actor();
Promise<R> p = new Promise<>();
this.whenFulfilled((t) -> a.execute(
() -> p.resolveCalling(() -> ok.apply(t)),
() -> p.rejectWith(new ActorTerminated(a))));
this.whenRejected((e) -> a.execute(
() -> {
if (fail == null) {
p.rejectWith(e);
} else {
p.resolveCalling(() -> fail.apply(e));
}
},
() -> p.rejectWith(new ActorTerminated(a))));
return p;
}
private static<T> T unexpectedTimeout(TimeoutException e) {
throw new InternalError("await() without delay signalled TimeoutException", e);
}
public T await() {
try {
return this.await(-1);
} catch (TimeoutException e) {
return unexpectedTimeout(e);
}
}
public T await(long delayMilliseconds) throws TimeoutException {
try {
return _await(delayMilliseconds, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
this.rejectWith(e);
throw new BrokenPromise(this);
}
}
public T _await(long delay, TimeUnit unit) throws TimeoutException, InterruptedException {
this.whenFulfilled((_t) -> { synchronized (this) { this.notifyAll(); } });
this.whenRejected((_e) -> { synchronized (this) { this.notifyAll(); } });
synchronized (this) {
if (delay == -1) {
while (this.isPending()) this.wait();
} else {
long targetTime = System.currentTimeMillis() + unit.toMillis(delay);
while (this.isPending()) {
long now = System.currentTimeMillis();
if (now >= targetTime) throw TIMEOUT_WAITING_FOR_PROMISE_RESOLUTION;
this.wait(targetTime - now);
}
}
}
if (this.isFulfilled()) {
return this._value;
} else {
throw new BrokenPromise(this);
}
}
private synchronized void whenFulfilled(Consumer<T> callback) {
switch (this._state) {
case PENDING:
if (_resolvers == null) _resolvers = new ArrayList<>();
_resolvers.add(callback);
break;
case FULFILLED:
callback.accept(this._value);
break;
case REJECTED:
break;
}
}
private synchronized void whenRejected(Consumer<Throwable> callback) {
switch (this._state) {
case PENDING:
if (_rejecters == null) _rejecters = new ArrayList<>();
_rejecters.add(callback);
break;
case FULFILLED:
break;
case REJECTED:
callback.accept(this._reason);
break;
}
}
// Implementation of Future<T>
@Override
public boolean cancel(boolean b) {
rejectWith(new CancellationException());
return isRejected();
}
@Override
public boolean isCancelled() {
return isRejected();
}
@Override
public boolean isDone() {
return !isPending();
}
@Override
public T get() throws InterruptedException, ExecutionException {
try {
return _await(-1, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
return unexpectedTimeout(e);
} catch (BrokenPromise e) {
throw new ExecutionException(e.getReason());
}
}
@Override
public T get(long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
try {
return _await(l, timeUnit);
} catch (BrokenPromise e) {
throw new ExecutionException(e.getReason());
}
}
}