/* * Copyright 2016 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.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.FileRegion; import io.reactivex.netty.channel.events.ConnectionEventListener; import io.reactivex.netty.events.Clock; import io.reactivex.netty.events.EventPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Subscriber; import rx.functions.Action0; import rx.functions.Action1; import rx.functions.Actions; import rx.functions.Func1; import rx.subscriptions.Subscriptions; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static java.util.concurrent.TimeUnit.*; /** * Default implementation for {@link ChannelOperations}. * * @param <W> Type of data that can be written on the associated channel. */ public class DefaultChannelOperations<W> implements ChannelOperations<W> { private static final Logger logger = LoggerFactory.getLogger(DefaultChannelOperations.class); /** Field updater for closeIssued. */ @SuppressWarnings("rawtypes") private static final AtomicIntegerFieldUpdater<DefaultChannelOperations> CLOSE_ISSUED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(DefaultChannelOperations.class, "closeIssued"); @SuppressWarnings("unused") private volatile int closeIssued; // updated by the atomic updater, so required to be volatile. private final Channel nettyChannel; private final ConnectionEventListener eventListener; private final EventPublisher eventPublisher; private final Observable<Void> closeObservable; private final Observable<Void> flushAndCloseObservable; private final Func1<W, Boolean> flushOnEachSelector = new Func1<W, Boolean>() { @Override public Boolean call(W w) { return true; } }; public DefaultChannelOperations(final Channel nettyChannel, ConnectionEventListener eventListener, EventPublisher eventPublisher) { this.nettyChannel = nettyChannel; this.eventListener = eventListener; this.eventPublisher = eventPublisher; closeObservable = Observable.create(new OnSubscribeForClose(nettyChannel)); flushAndCloseObservable = closeObservable.doOnSubscribe(new Action0() { @Override public void call() { flush(); } }); } @Override public Observable<Void> write(final Observable<W> msgs) { return _write(msgs); } @Override public Observable<Void> write(Observable<W> msgs, final Func1<W, Boolean> flushSelector) { return _write(msgs, flushSelector); } @Override public Observable<Void> writeAndFlushOnEach(Observable<W> msgs) { return _write(msgs, flushOnEachSelector); } @Override public Observable<Void> writeString(Observable<String> msgs) { return _write(msgs); } @Override public Observable<Void> writeString(Observable<String> msgs, Func1<String, Boolean> flushSelector) { return _write(msgs, flushSelector); } @Override public Observable<Void> writeStringAndFlushOnEach(Observable<String> msgs) { return writeString(msgs, FLUSH_ON_EACH_STRING); } @Override public Observable<Void> writeBytes(Observable<byte[]> msgs) { return _write(msgs); } @Override public Observable<Void> writeBytes(Observable<byte[]> msgs, Func1<byte[], Boolean> flushSelector) { return _write(msgs, flushSelector); } @Override public Observable<Void> writeBytesAndFlushOnEach(Observable<byte[]> msgs) { return _write(msgs, FLUSH_ON_EACH_BYTES); } @Override public Observable<Void> writeFileRegion(Observable<FileRegion> msgs) { return _write(msgs); } @Override public Observable<Void> writeFileRegion(Observable<FileRegion> msgs, Func1<FileRegion, Boolean> flushSelector) { return _write(msgs, flushSelector); } @Override public Observable<Void> writeFileRegionAndFlushOnEach(Observable<FileRegion> msgs) { return writeFileRegion(msgs, FLUSH_ON_EACH_FILE_REGION); } @Override public <WW> ChannelOperations<WW> transformWrite(AllocatingTransformer<WW, W> transformer) { nettyChannel.pipeline().fireUserEventTriggered(new AppendTransformerEvent<>(transformer)); return new DefaultChannelOperations<>(nettyChannel, eventListener, eventPublisher); } @Override public void flush() { if (eventPublisher.publishingEnabled()) { final long startTimeNanos = Clock.newStartTimeNanos(); eventListener.onFlushStart(); if (nettyChannel.eventLoop().inEventLoop()) { _flushInEventloop(startTimeNanos); } else { nettyChannel.eventLoop() .execute(new Runnable() { @Override public void run() { _flushInEventloop(startTimeNanos); } }); } } else { nettyChannel.flush(); } } @Override public Observable<Void> close() { return close(true); } @Override public Observable<Void> close(boolean flush) { return flush ? flushAndCloseObservable : closeObservable; } @Override public void closeNow() { close().subscribe(Actions.empty(), new Action1<Throwable>() { @Override public void call(Throwable throwable) { logger.error("Error closing connection.", throwable); } }); } @Override public Observable<Void> closeListener() { return Observable.create(new OnSubscribe<Void>() { @Override public void call(final Subscriber<? super Void> subscriber) { final SubscriberToChannelFutureBridge l = new SubscriberToChannelFutureBridge() { @Override protected void doOnSuccess(ChannelFuture future) { subscriber.onCompleted(); } @Override protected void doOnFailure(ChannelFuture future, Throwable cause) { subscriber.onCompleted(); } }; l.bridge(nettyChannel.closeFuture(), subscriber); } }); } private <X> Observable<Void> _write(final Observable<X> msgs, Func1<X, Boolean> flushSelector) { return _write(msgs.lift(new FlushSelectorOperator<>(flushSelector, this))); } private void _flushInEventloop(long startTimeNanos) { assert nettyChannel.eventLoop().inEventLoop(); nettyChannel.flush(); // Flush is sync when from eventloop. eventListener.onFlushComplete(Clock.onEndNanos(startTimeNanos), NANOSECONDS); } private Observable<Void> _write(final Observable<?> msgs) { return Observable.create(new OnSubscribe<Void>() { @Override public void call(final Subscriber<? super Void> subscriber) { final long startTimeNanos = Clock.newStartTimeNanos(); if (eventPublisher.publishingEnabled()) { eventListener.onWriteStart(); } /* * If a write happens from outside the eventloop, it does not wakeup the selector, till a flush happens. * In absence of a selector wakeup, this write will be delayed by the selector sleep interval. * The code below makes sure that the selector is woken up on a write (by executing a task that does * the write) */ if (nettyChannel.eventLoop().inEventLoop()) { _writeStreamToChannel(subscriber, startTimeNanos); } else { nettyChannel.eventLoop() .execute(new Runnable() { @Override public void run() { _writeStreamToChannel(subscriber, startTimeNanos); } }); } } private void _writeStreamToChannel(final Subscriber<? super Void> subscriber, final long startTimeNanos) { final ChannelFuture writeFuture = nettyChannel.write(msgs.doOnCompleted(new Action0() { @Override public void call() { Boolean shdNotFlush = nettyChannel.attr(FLUSH_ONLY_ON_READ_COMPLETE).get(); if (null == shdNotFlush || !shdNotFlush) { flush(); } } })); subscriber.add(Subscriptions.create(new Action0() { @Override public void call() { writeFuture.cancel(false); // cancel write on unsubscribe. } })); writeFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (subscriber.isUnsubscribed()) { /*short-circuit if subscriber is unsubscribed*/ return; } if (future.isSuccess()) { if (eventPublisher.publishingEnabled()) { eventListener.onWriteSuccess(Clock.onEndNanos(startTimeNanos), NANOSECONDS); } subscriber.onCompleted(); } else { if (eventPublisher.publishingEnabled()) { eventListener.onWriteFailed(Clock.onEndNanos(startTimeNanos), NANOSECONDS, future.cause()); } subscriber.onError(future.cause()); } } }); } }); } private class OnSubscribeForClose implements OnSubscribe<Void> { private final Channel nettyChannel; public OnSubscribeForClose(Channel nettyChannel) { this.nettyChannel = nettyChannel; } @Override @SuppressWarnings("unchecked") public void call(final Subscriber<? super Void> subscriber) { final long closeStartTimeNanos = Clock.newStartTimeNanos(); final ChannelCloseListener closeListener; if (CLOSE_ISSUED_UPDATER.compareAndSet(DefaultChannelOperations.this, 0, 1)) { if (eventPublisher.publishingEnabled()) { eventListener.onConnectionCloseStart(); } nettyChannel.close(); // close only once. closeListener = new ChannelCloseListener(eventListener, eventPublisher, closeStartTimeNanos, subscriber); } else { closeListener = new ChannelCloseListener(subscriber); } closeListener.bridge(nettyChannel.closeFuture(), subscriber); } private class ChannelCloseListener extends SubscriberToChannelFutureBridge { private final long closeStartTimeNanos; private final Subscriber<? super Void> subscriber; private final ConnectionEventListener eventListener; private final EventPublisher eventPublisher; public ChannelCloseListener(ConnectionEventListener eventListener, EventPublisher eventPublisher, long closeStartTimeNanos, Subscriber<? super Void> subscriber) { this.eventListener = eventListener; this.eventPublisher = eventPublisher; this.closeStartTimeNanos = closeStartTimeNanos; this.subscriber = subscriber; } public ChannelCloseListener(Subscriber<? super Void> subscriber) { this(null, null, -1, subscriber); } @Override protected void doOnSuccess(ChannelFuture future) { if (null != eventListener && eventPublisher.publishingEnabled()) { eventListener.onConnectionCloseSuccess(Clock.onEndNanos(closeStartTimeNanos), NANOSECONDS); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } } @Override protected void doOnFailure(ChannelFuture future, Throwable cause) { if (null != eventListener && eventPublisher.publishingEnabled()) { eventListener.onConnectionCloseFailed(Clock.onEndNanos(closeStartTimeNanos), NANOSECONDS, future.cause()); } if (!subscriber.isUnsubscribed()) { subscriber.onError(future.cause()); } } } } }