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; import java.util.function.Supplier; public class Promise implements Future { 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> _resolvers = null; private List> _rejecters = null; public Promise() {} public static Promise resolved() { return resolved(null); } public static Promise resolved(T v) { var p = new Promise(); p.resolveWith(v); return p; } public static Promise rejected(Throwable e) { var p = new Promise(); 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> 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) { 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> 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 Promise andThen(Function ok) { return this.andThen(ok, null); } public void resolveCalling(Supplier f) { try { this.resolveWith(f.get()); } catch (Throwable e) { this.rejectWith(e); } } public synchronized Promise andThen(final Function ok, final Function fail) { Actor a0 = Actor.current(); final Actor a = a0 != null ? a0 : new Actor(); Promise 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 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 { Actor a = Actor.current(); if (a == null) { Semaphore s = new Semaphore(0); this.whenFulfilled((_t) -> s.release()); this.whenRejected((_e) -> s.release()); if (delay == -1) { s.acquire(); } else { if (!s.tryAcquire(delay, unit)) throw TIMEOUT_WAITING_FOR_PROMISE_RESOLUTION; } } else { this.whenFulfilled((_t) -> { synchronized (a) { a.notifyAll(); } }); this.whenRejected((_e) -> { synchronized (a) { a.notifyAll(); } }); synchronized (a) { if (delay == -1) { while (this.isPending()) a.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; a.wait(targetTime - now); } } } } if (this.isFulfilled()) { return this._value; } else { throw new BrokenPromise(this); } } private synchronized void whenFulfilled(Consumer 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 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 @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()); } } }