/*
* Copyright 2015 Netflix, 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 io.reactivex.netty.channel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.util.AttributeKey;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.EmptyArrays;
import io.reactivex.netty.channel.events.ConnectionEventListener;
import io.reactivex.netty.events.EventPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Producer;
import rx.Subscriber;
import rx.exceptions.MissingBackpressureException;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
/**
* A bridge between a {@link Connection} instance and the associated {@link Channel}.
*
* All operations on {@link Connection} will pass through this bridge to an appropriate action on the {@link Channel}
*
* <h2>Lazy {@link Connection#getInput()} subscription</h2>
*
* Lazy subscriptions are allowed on {@link Connection#getInput()} if and only if the channel is configured to
* not read data automatically (i.e. {@link ChannelOption#AUTO_READ} is set to {@code false}). Otherwise,
* if {@link Connection#getInput()} is subscribed lazily, the subscriber always receives an error. The content
* in this case is disposed upon reading.
*
* @param <R> Type read from the connection held by this handler.
* @param <W> Type written to the connection held by this handler.
*/
public abstract class AbstractConnectionToChannelBridge<R, W> extends BackpressureManagingHandler {
private static final Logger logger = LoggerFactory.getLogger(AbstractConnectionToChannelBridge.class);
@SuppressWarnings("ThrowableInstanceNeverThrown")
private static final IllegalStateException ONLY_ONE_CONN_SUB_ALLOWED =
new IllegalStateException("Only one subscriber allowed for connection observable.");
@SuppressWarnings("ThrowableInstanceNeverThrown")
private static final IllegalStateException ONLY_ONE_CONN_INPUT_SUB_ALLOWED =
new IllegalStateException("Only one subscriber allowed for connection input.");
@SuppressWarnings("ThrowableInstanceNeverThrown")
private static final IllegalStateException LAZY_CONN_INPUT_SUB =
new IllegalStateException("Channel is set to auto-read but the subscription was lazy.");
@SuppressWarnings("ThrowableInstanceNeverThrown")
private static final ClosedChannelException CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException();
static {
ONLY_ONE_CONN_INPUT_SUB_ALLOWED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
ONLY_ONE_CONN_SUB_ALLOWED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
LAZY_CONN_INPUT_SUB.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
CLOSED_CHANNEL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
}
private final AttributeKey<ConnectionEventListener> eventListenerAttributeKey;
private final AttributeKey<EventPublisher> eventPublisherAttributeKey;
protected ConnectionEventListener eventListener;
protected EventPublisher eventPublisher;
private Subscriber<? super Channel> newChannelSub;
private ReadProducer<R> readProducer;
private boolean raiseErrorOnInputSubscription;
private boolean connectionEmitted;
protected AbstractConnectionToChannelBridge(String thisHandlerName, ConnectionEventListener eventListener,
EventPublisher eventPublisher) {
super(thisHandlerName);
if (null == eventListener) {
throw new IllegalArgumentException("Event listener can not be null.");
}
if (null == eventPublisher) {
throw new IllegalArgumentException("Event publisher can not be null.");
}
this.eventListener = eventListener;
this.eventPublisher = eventPublisher;
eventListenerAttributeKey = null;
eventPublisherAttributeKey = null;
}
protected AbstractConnectionToChannelBridge(String thisHandlerName,
AttributeKey<ConnectionEventListener> eventListenerAttributeKey,
AttributeKey<EventPublisher> eventPublisherAttributeKey) {
super(thisHandlerName);
this.eventListenerAttributeKey = eventListenerAttributeKey;
this.eventPublisherAttributeKey = eventPublisherAttributeKey;
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (null == eventListener && null == eventPublisher) {
eventListener = ctx.channel().attr(eventListenerAttributeKey).get();
eventPublisher = ctx.channel().attr(eventPublisherAttributeKey).get();
}
if (null == eventPublisher) {
logger.error("No Event publisher bound to the channel, closing channel.");
ctx.channel().close();
return;
}
if (eventPublisher.publishingEnabled() && null == eventListener) {
logger.error("No Event listener bound to the channel and publising is enabled, closing channel.");
ctx.channel().close();
return;
}
ctx.pipeline().addFirst(new BytesInspector(eventPublisher, eventListener));
super.handlerAdded(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (!connectionEmitted && isValidToEmit(newChannelSub)) {
emitNewConnection(ctx.channel());
connectionEmitted = true;
}
super.channelInactive(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
if (isValidToEmitToReadSubscriber(readProducer)) {
/*If the subscriber is still active, then it expects data but the channel is closed.*/
readProducer.sendOnError(CLOSED_CHANNEL_EXCEPTION);
}
super.channelUnregistered(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof EmitConnectionEvent) {
if (!connectionEmitted) {
emitNewConnection(ctx.channel());
connectionEmitted = true;
}
} else if (evt instanceof ConnectionCreationFailedEvent) {
if (isValidToEmit(newChannelSub)) {
newChannelSub.onError(((ConnectionCreationFailedEvent)evt).getThrowable());
}
} else if (evt instanceof ChannelSubscriberEvent) {
@SuppressWarnings("unchecked")
final ChannelSubscriberEvent<R, W> channelSubscriberEvent = (ChannelSubscriberEvent<R, W>) evt;
newConnectionSubscriber(channelSubscriberEvent);
} else if (evt instanceof ConnectionInputSubscriberEvent) {
@SuppressWarnings("unchecked")
ConnectionInputSubscriberEvent<R, W> event = (ConnectionInputSubscriberEvent<R, W>) evt;
newConnectionInputSubscriber(ctx.channel(), event.getSubscriber(), false);
} else if (evt instanceof ConnectionInputSubscriberResetEvent) {
resetConnectionInputSubscriber();
} else if (evt instanceof ConnectionInputSubscriberReplaceEvent) {
@SuppressWarnings("unchecked")
ConnectionInputSubscriberReplaceEvent<R, W> event = (ConnectionInputSubscriberReplaceEvent<R, W>) evt;
replaceConnectionInputSubscriber(ctx.channel(), event);
}
super.userEventTriggered(ctx, evt);
}
@SuppressWarnings("unchecked")
@Override
public void newMessage(ChannelHandlerContext ctx, Object msg) {
if (isValidToEmitToReadSubscriber(readProducer)) {
try {
readProducer.sendOnNext((R) msg);
} catch (ClassCastException e) {
ReferenceCountUtil.release(msg); // Since, this was not sent to the subscriber, release the msg.
readProducer.sendOnError(e);
}
} else {
if (logger.isWarnEnabled()) {
logger.warn("Data received on channel, but no subscriber registered. Discarding data. Message class: "
+ msg.getClass().getName() + ", channel: " + ctx.channel());
}
ReferenceCountUtil.release(msg); // No consumer of the message, so discard.
}
}
@Override
public boolean shouldReadMore(ChannelHandlerContext ctx) {
return null != readProducer && readProducer.shouldReadMore(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (!connectionEmitted && isValidToEmit(newChannelSub)) {
newChannelSub.onError(cause);
} else if (isValidToEmitToReadSubscriber(readProducer)) {
readProducer.sendOnError(cause);
} else {
logger.info("Exception in the pipeline and none of the subscribers are active.", cause);
}
}
protected static boolean isValidToEmit(Subscriber<?> subscriber) {
return null != subscriber && !subscriber.isUnsubscribed();
}
private static boolean isValidToEmitToReadSubscriber(ReadProducer<?> readProducer) {
return null != readProducer && !readProducer.subscriber.isUnsubscribed();
}
protected boolean connectionInputSubscriberExists(Channel channel) {
assert channel.eventLoop().inEventLoop();
return null != readProducer && null != readProducer.subscriber && !readProducer.subscriber.isUnsubscribed();
}
protected void onNewReadSubscriber(Subscriber<? super R> subscriber) {
// NOOP
}
protected final void checkEagerSubscriptionIfConfigured(Channel channel) {
if (channel.config().isAutoRead() && null == readProducer) {
// If the channel is set to auto-read and there is no eager subscription then, we should raise errors
// when a subscriber arrives.
raiseErrorOnInputSubscription = true;
final Subscriber<? super R> discardAll = ConnectionInputSubscriberEvent.discardAllInput()
.getSubscriber();
final ReadProducer<R> producer = new ReadProducer<>(discardAll, channel);
discardAll.setProducer(producer);
readProducer = producer;
}
}
protected final Subscriber<? super Channel> getNewChannelSub() {
return newChannelSub;
}
private void emitNewConnection(Channel channel) {
if (isValidToEmit(newChannelSub)) {
try {
newChannelSub.onNext(channel);
connectionEmitted = true;
checkEagerSubscriptionIfConfigured(channel);
newChannelSub.onCompleted();
} catch (Exception e) {
logger.error("Error emitting a new connection. Closing this channel.", e);
channel.close();
}
} else {
channel.close(); // Closing the connection if not sent to a subscriber.
}
}
private void resetConnectionInputSubscriber() {
final Subscriber<? super R> connInputSub = null == readProducer? null : readProducer.subscriber;
if (isValidToEmit(connInputSub)) {
connInputSub.onCompleted();
}
raiseErrorOnInputSubscription = false;
readProducer = null; // A subsequent event should set it to the desired subscriber.
}
private void newConnectionInputSubscriber(final Channel channel, final Subscriber<? super R> subscriber,
boolean replace) {
final Subscriber<? super R> connInputSub = null == readProducer ? null : readProducer.subscriber;
if (isValidToEmit(connInputSub)) {
if (!replace) {
/*Allow only once concurrent input subscriber but allow concatenated subscribers*/
subscriber.onError(ONLY_ONE_CONN_INPUT_SUB_ALLOWED);
} else {
setNewReadProducer(channel, subscriber);
connInputSub.onCompleted();
}
} else if (raiseErrorOnInputSubscription) {
subscriber.onError(LAZY_CONN_INPUT_SUB);
} else {
setNewReadProducer(channel, subscriber);
}
}
private void setNewReadProducer(Channel channel, Subscriber<? super R> subscriber) {
final ReadProducer<R> producer = new ReadProducer<>(subscriber, channel);
subscriber.setProducer(producer);
onNewReadSubscriber(subscriber);
readProducer = producer;
}
private void replaceConnectionInputSubscriber(Channel channel, ConnectionInputSubscriberReplaceEvent<R, W> event) {
ConnectionInputSubscriberEvent<R, W> newSubEvent = event.getNewSubEvent();
newConnectionInputSubscriber(channel, newSubEvent.getSubscriber(),
true);
}
private void newConnectionSubscriber(ChannelSubscriberEvent<R, W> event) {
if (null == newChannelSub) {
newChannelSub = event.getSubscriber();
} else {
event.getSubscriber().onError(ONLY_ONE_CONN_SUB_ALLOWED);
}
}
/*Visible for testing*/ static final class ReadProducer<T> extends RequestReadIfRequiredEvent implements Producer {
@SuppressWarnings("rawtypes")
private static final AtomicLongFieldUpdater<ReadProducer> REQUEST_UPDATER =
AtomicLongFieldUpdater.newUpdater(ReadProducer.class, "requested");/*Updater for requested*/
private volatile long requested; // Updated by REQUEST_UPDATER, required to be volatile.
private final Subscriber<? super T> subscriber;
private final Channel channel;
/*Visible for testing*/ ReadProducer(Subscriber<? super T> subscriber, Channel channel) {
this.subscriber = subscriber;
this.channel = channel;
}
@Override
public void request(long n) {
if (Long.MAX_VALUE != requested) {
if (Long.MAX_VALUE == n) {
// Now turning off backpressure
REQUEST_UPDATER.set(this, Long.MAX_VALUE);
} else {
// add n to field but check for overflow
while (true) {
final long current = requested;
long next = current + n;
// check for overflow
if (next < 0) {
next = Long.MAX_VALUE;
}
if (REQUEST_UPDATER.compareAndSet(this, current, next)) {
break;
}
}
}
}
if (!channel.config().isAutoRead()) {
channel.pipeline().fireUserEventTriggered(this);
}
}
public void sendOnError(Throwable throwable) {
subscriber.onError(throwable);
}
public void sendOnComplete() {
subscriber.onCompleted();
}
public void sendOnNext(T nextItem) {
if (requested > 0) {
if (REQUEST_UPDATER.get(this) != Long.MAX_VALUE) {
REQUEST_UPDATER.decrementAndGet(this);
}
subscriber.onNext(nextItem);
} else {
subscriber.onError(new MissingBackpressureException(
"Received more data on the channel than demanded by the subscriber."));
}
}
@Override
protected boolean shouldReadMore(ChannelHandlerContext ctx) {
return !subscriber.isUnsubscribed() && REQUEST_UPDATER.get(this) > 0;
}
/*Visible for testing*/long getRequested() {
return requested;
}
@Override
public String toString() {
return "ReadProducer{" + "requested=" + requested + '}';
}
}
}