/*
* Copyright 2012 LinkedIn, Inc
*
* 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 com.linkedin.parseq.promise;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.linkedin.parseq.internal.Continuations;
/**
* @author Chris Pettitt (cpettitt@linkedin.com)
* @author Chi Chan (ckchan@linkedin.com)
* @author Jaroslaw Odzga (jodzga@linkedin.com)
*/
/* package private */ class SettablePromiseImpl<T> implements SettablePromise<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(SettablePromiseImpl.class);
private static final Continuations CONTINUATIONS = new Continuations();
private final Object _lock = new Object();
private final List<PromiseListener<T>> _listeners = new ArrayList<PromiseListener<T>>();
private final CountDownLatch _valueLatch = new CountDownLatch(1);
private final CountDownLatch _awaitLatch = new CountDownLatch(1);
private volatile T _value;
private volatile Throwable _error;
@Override
public void done(final T value) throws PromiseResolvedException {
doFinish(value, null);
}
@Override
public void fail(final Throwable error) throws PromiseResolvedException {
doFinish(null, error);
}
@Override
public T get() throws PromiseException {
ensureDone();
if (_error != null) {
throw new PromiseException(_error);
}
return _value;
}
@Override
public Throwable getError() throws PromiseUnresolvedException {
ensureDone();
return _error;
}
@Override
public T getOrDefault(final T defaultValue) throws PromiseUnresolvedException {
ensureDone();
if (_error != null) {
return defaultValue;
}
return _value;
}
@Override
public void await() throws InterruptedException {
_awaitLatch.await();
}
@Override
public boolean await(final long time, final TimeUnit unit) throws InterruptedException {
return _awaitLatch.await(time, unit);
}
@Override
public void addListener(final PromiseListener<T> listener) {
synchronized (_lock) {
if (!isDone()) {
_listeners.add(listener);
return;
}
}
notifyListener(listener);
}
@Override
public boolean isDone() {
return _valueLatch.getCount() == 0;
}
@Override
public boolean isFailed() {
return isDone() && _error != null;
}
private void doFinish(final T value, final Throwable error) throws PromiseResolvedException {
final List<PromiseListener<T>> listeners = finalizeResult(value, error);
CONTINUATIONS.submit(() -> notifyListeners(listeners));
CONTINUATIONS.submit(_awaitLatch::countDown);
}
private List<PromiseListener<T>> finalizeResult(T value, Throwable error) {
final List<PromiseListener<T>> listeners;
synchronized (_lock) {
ensureNotDone();
_value = value;
_error = error;
_valueLatch.countDown();
listeners = new ArrayList<PromiseListener<T>>(_listeners);
_listeners.clear();
}
return listeners;
}
private void notifyListeners(final List<PromiseListener<T>> listeners) {
for (int i = listeners.size() - 1; i >= 0; i--) {
notifyListener(listeners.get(i));
}
}
private void notifyListener(final PromiseListener<T> listener) {
// We intentionally catch Throwable around the listener invocation because
// it will cause the notifier loop and subsequent count down in doFinish to
// be skipped, which will certainly lead to bad behavior. It could be argued
// that the catch should not apply for use of notifyListener from
// addListener, but it seems better to err on the side of consistency and
// least surprise.
try {
listener.onResolved(this);
} catch (Throwable e) {
LOGGER.error("An exception was thrown by listener", e);
}
}
private void ensureNotDone() throws PromiseResolvedException {
if (isDone()) {
throw new PromiseResolvedException("Promise has already been satisfied");
}
}
private void ensureDone() throws PromiseUnresolvedException {
if (!isDone()) {
throw new PromiseUnresolvedException("Promise has not yet been satisfied");
}
}
}