package com.linkedin.r2.message.stream.entitystream;
import com.linkedin.data.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
;
/**
* A class consists exclusively of static methods to deal with EntityStream {@link com.linkedin.r2.message.stream.entitystream.EntityStream}
*
* @author Zhenkai Zhu
*/
public final class EntityStreams
{
private static final Logger LOG = LoggerFactory.getLogger(EntityStreams.class);
private EntityStreams() {}
public static EntityStream emptyStream()
{
return newEntityStream(new Writer()
{
private WriteHandle _wh;
@Override
public void onInit(WriteHandle wh)
{
_wh = wh;
}
@Override
public void onWritePossible()
{
_wh.done();
}
@Override
public void onAbort(Throwable e)
{
// do nothing
}
});
}
/**
* The method to create a new EntityStream with a writer for the stream
*
* @param writer the writer for the stream who would provide the data
* @return an instance of EntityStream
*/
public static EntityStream newEntityStream(Writer writer)
{
return new EntityStreamImpl(writer);
}
private enum State
{
UNINITIALIZED,
ACTIVE,
FINISHED,
ABORTED,
ABORT_REQUESTED,
}
private static class EntityStreamImpl implements EntityStream
{
private final Writer _writer;
private final Object _lock;
private List<Observer> _observers;
private Reader _reader;
private int _remaining;
private boolean _notifyWritePossible;
private State _state;
EntityStreamImpl(Writer writer)
{
_writer = writer;
_lock = new Object();
_observers = new ArrayList<Observer>();
_remaining = 0;
_notifyWritePossible = true;
_state = State.UNINITIALIZED;
}
public void addObserver(Observer o)
{
synchronized (_lock)
{
checkInit();
_observers.add(o);
}
}
public void setReader(Reader r)
{
synchronized (_lock)
{
checkInit();
_state = State.ACTIVE;
_reader = r;
_observers = Collections.unmodifiableList(_observers);
}
final WriteHandle wh = new WriteHandleImpl();
Throwable writerInitEx = null;
try
{
_writer.onInit(wh);
}
catch (Throwable ex)
{
LOG.warn("Writer throws exception at onInit", ex);
synchronized (_lock)
{
_state = State.ABORTED;
}
safeAbortWriter(ex);
writerInitEx = ex;
}
final AtomicBoolean _notified = new AtomicBoolean(false);
final ReadHandle rh;
if (writerInitEx == null)
{
rh = new ReadHandleImpl();
}
else
{
final Throwable cause = writerInitEx;
rh = new ReadHandle()
{
@Override
public void request(int n)
{
notifyError();
}
@Override
public void cancel()
{
notifyError();
}
void notifyError()
{
if (_notified.compareAndSet(false, true))
{
safeNotifyErrorToObservers(cause);
safeNotifyErrorToReader(cause);
}
}
};
}
try
{
_reader.onInit(rh);
}
catch (RuntimeException ex)
{
LOG.warn("Reader throws exception at onInit", ex);
synchronized (_lock)
{
if (_state != State.ACTIVE && _state != State.ABORT_REQUESTED && writerInitEx == null)
{
return;
}
else
{
_state = State.ABORTED;
}
}
if (writerInitEx == null)
{
doCancel(ex, true);
}
else
{
if (_notified.compareAndSet(false, true))
{
safeNotifyErrorToObservers(ex);
safeNotifyErrorToReader(ex);
}
}
}
}
private class WriteHandleImpl implements WriteHandle
{
@Override
public void write(final ByteString data)
{
boolean doCancelNow = false;
synchronized (_lock)
{
if (_state == State.FINISHED)
{
throw new IllegalStateException("Attempting to write after done or error of WriteHandle is invoked");
}
if (_state == State.ABORTED)
{
return;
}
_remaining--;
if (_remaining < 0)
{
throw new IllegalStateException("Attempt to write when remaining is 0");
}
if (_state == State.ABORT_REQUESTED)
{
doCancelNow = true;
_state = State.ABORTED;
}
}
if (doCancelNow)
{
doCancel(getAbortedException(), false);
return;
}
for (Observer observer : _observers)
{
try
{
observer.onDataAvailable(data);
}
catch (Throwable ex)
{
LOG.warn("Observer throws exception at onDataAvailable", ex);
}
}
try
{
_reader.onDataAvailable(data);
}
catch (Throwable ex)
{
LOG.warn("Reader throws exception at onDataAvailable", ex);
// the lock ensures that once we change the _state to ABORTED, it will stay as ABORTED
synchronized (_lock)
{
_state = State.ABORTED;
}
// we can safely do cancel here because no other place could be doing cancel (mutually exclusively by design)
doCancel(ex, true);
}
}
@Override
public void done()
{
boolean doCancelNow = false;
synchronized (_lock)
{
if (_state != State.ACTIVE && _state != State.ABORT_REQUESTED)
{
return;
}
if (_state == State.ABORT_REQUESTED)
{
doCancelNow = true;
_state = State.ABORTED;
}
else
{
_state = State.FINISHED;
}
}
if (doCancelNow)
{
doCancel(getAbortedException(), false);
return;
}
for (Observer observer : _observers)
{
try
{
observer.onDone();
}
catch (Throwable ex)
{
LOG.warn("Observer throws exception at onDone, ignored.", ex);
}
}
try
{
_reader.onDone();
}
catch (Throwable ex)
{
LOG.warn("Reader throws exception at onDone; notifying writer", ex);
// At this point, no cancel had happened and no cancel will happen, _writer.onAbort will not be invoked more than once
// This is still a value to let writer know about this exception, e.g. see DispatcherRequestFilter.Connector
safeAbortWriter(ex);
}
}
@Override
public void error(final Throwable e)
{
boolean doCancelNow = false;
synchronized (_lock)
{
if (_state != State.ACTIVE && _state != State.ABORT_REQUESTED)
{
return;
}
if (_state == State.ABORT_REQUESTED)
{
doCancelNow = true;
_state = State.ABORTED;
}
else
{
_state = State.FINISHED;
}
}
if (doCancelNow)
{
doCancel(getAbortedException(), false);
return;
}
safeNotifyErrorToObservers(e);
try
{
_reader.onError(e);
}
catch (Throwable ex)
{
LOG.warn("Reader throws exception at onError; notifying writer", ex);
// at this point, no cancel had happened and no cancel will happen, _writer.onAbort will not be invoked more than once
// This is still a value to let writer know about this exception, e.g. see DispatcherRequestFilter.Connector
safeAbortWriter(ex);
}
}
@Override
public int remaining()
{
int result;
boolean doCancelNow = false;
synchronized (_lock)
{
if (_state != State.ACTIVE && _state != State.ABORT_REQUESTED)
{
return 0;
}
if (_state == State.ABORT_REQUESTED)
{
doCancelNow = true;
_state = State.ABORTED;
result = 0;
}
else
{
if (_remaining == 0)
{
_notifyWritePossible = true;
}
result = _remaining;
}
}
if (doCancelNow)
{
doCancel(getAbortedException(), false);
}
return result;
}
}
private class ReadHandleImpl implements ReadHandle
{
@Override
public void request(final int chunkNum)
{
if (chunkNum <= 0)
{
throw new IllegalArgumentException("cannot request non-positive number of data chunks: " + chunkNum);
}
boolean needNotify = false;
synchronized (_lock)
{
if (_state != State.ACTIVE)
{
return;
}
_remaining += chunkNum;
// overflow
if (_remaining < 0)
{
LOG.warn("chunkNum overflow, setting to Integer.MAX_VALUE");
_remaining = Integer.MAX_VALUE;
}
// notify the writer if needed
if (_notifyWritePossible)
{
needNotify = true;
_notifyWritePossible = false;
}
}
if (needNotify)
{
try
{
_writer.onWritePossible();
}
catch (Throwable ex)
{
LOG.warn("Writer throws at onWritePossible", ex);
// we can safely do cancel here as no WriteHandle method could be called at the same time
synchronized (_lock)
{
_state = State.ABORTED;
}
doCancel(ex, true);
}
}
}
@Override
public void cancel()
{
boolean doCancelNow;
synchronized (_lock)
{
// this means writer is waiting for on WritePossible (cannot call WriteHandle.write) and has not called
// WriteHandle.onDone() or WriteHandle.onError() yet, so we can safely do cancel here
// otherwise, we would let the writer thread invoke doCancel later
doCancelNow = _notifyWritePossible && _state == State.ACTIVE;
if (doCancelNow)
{
_state = State.ABORTED;
}
else if (_state == State.ACTIVE)
{
_state = State.ABORT_REQUESTED;
}
}
if (doCancelNow)
{
doCancel(getAbortedException(), false);
}
}
}
private void checkInit()
{
if (_state != State.UNINITIALIZED)
{
throw new IllegalStateException("EntityStream had already been initialized and can no longer accept Observers or Reader");
}
}
private void safeAbortWriter(Throwable throwable)
{
try
{
_writer.onAbort(throwable);
}
catch (Throwable ex)
{
LOG.warn("Writer throws exception at onAbort", ex);
}
}
private void safeNotifyErrorToObservers(Throwable throwable)
{
for (Observer observer : _observers)
{
try
{
observer.onError(throwable);
}
catch (Throwable ex)
{
LOG.warn("Observer throws exception at onError, ignored.", ex);
}
}
}
private void safeNotifyErrorToReader(Throwable throwable)
{
try
{
_reader.onError(throwable);
}
catch (Throwable ex)
{
LOG.error("Reader throws exception at onError", ex);
}
}
private void doCancel(Throwable e, boolean notifyReader)
{
safeAbortWriter(e);
safeNotifyErrorToObservers(e);
if (notifyReader)
{
safeNotifyErrorToReader(e);
}
}
private static Exception getAbortedException()
{
return new AbortedException("Reader aborted");
}
}
}