2020-12-04 18:57:47 +00:00
|
|
|
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> {
|
2020-12-05 22:50:41 +00:00
|
|
|
|
|
|
|
public static final TimeoutException TIMEOUT_WAITING_FOR_PROMISE_RESOLUTION = new TimeoutException("Waiting for promise resolution");
|
|
|
|
|
|
|
|
public enum State {
|
2020-12-04 18:57:47 +00:00
|
|
|
PENDING,
|
|
|
|
FULFILLED,
|
|
|
|
REJECTED
|
2020-12-05 22:50:41 +00:00
|
|
|
}
|
2020-12-04 18:57:47 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-12-05 22:50:41 +00:00
|
|
|
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);
|
2020-12-04 18:57:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void chain(Promise<T> t) {
|
|
|
|
if (this == t) {
|
|
|
|
throw new IllegalArgumentException("cannot chain promise immediately to itself");
|
|
|
|
}
|
2020-12-05 22:50:41 +00:00
|
|
|
t.whenFulfilled(this::resolveWith);
|
|
|
|
t.whenRejected(this::rejectWith);
|
2020-12-04 18:57:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void rejectWith(Throwable e) {
|
2020-12-05 22:50:41 +00:00
|
|
|
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);
|
2020-12-04 18:57:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 15:00:15 +00:00
|
|
|
public<R> Promise<R> andThen(Actor a, Function<T, R> ok) {
|
|
|
|
return this.andThen(a, ok, null);
|
2020-12-04 18:57:47 +00:00
|
|
|
}
|
|
|
|
|
2020-12-07 22:52:16 +00:00
|
|
|
public void resolveCalling(ThrowingSupplier<T> f) {
|
2020-12-05 22:50:41 +00:00
|
|
|
try {
|
|
|
|
this.resolveWith(f.get());
|
|
|
|
} catch (Throwable e) {
|
|
|
|
this.rejectWith(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 15:00:15 +00:00
|
|
|
public synchronized<R> Promise<R> andThen(Actor a0, final Function<T, R> ok, final Function<Throwable, R> fail) {
|
2020-12-04 18:57:47 +00:00
|
|
|
final Actor a = a0 != null ? a0 : new Actor();
|
|
|
|
Promise<R> p = new Promise<>();
|
2020-12-05 22:50:41 +00:00
|
|
|
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))));
|
2020-12-04 18:57:47 +00:00
|
|
|
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 {
|
2021-04-14 15:00:15 +00:00
|
|
|
this.whenFulfilled((_t) -> { synchronized (this) { this.notifyAll(); } });
|
|
|
|
this.whenRejected((_e) -> { synchronized (this) { this.notifyAll(); } });
|
|
|
|
synchronized (this) {
|
2020-12-05 22:50:41 +00:00
|
|
|
if (delay == -1) {
|
2021-04-14 15:00:15 +00:00
|
|
|
while (this.isPending()) this.wait();
|
2020-12-05 22:50:41 +00:00
|
|
|
} else {
|
2021-04-14 15:00:15 +00:00
|
|
|
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);
|
2020-12-05 22:50:41 +00:00
|
|
|
}
|
2020-12-04 18:57:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|