All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactivex.netty.channel.ObservableConnection Maven / Gradle / Ivy

There is a newer version: 0.3.18
Show newest version
/*
 * Copyright 2014 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.ChannelHandlerContext;
import io.reactivex.netty.metrics.Clock;
import io.reactivex.netty.metrics.MetricEventsSubject;
import io.reactivex.netty.pipeline.ReadTimeoutPipelineConfigurator;
import io.reactivex.netty.util.NoOpSubscriber;
import rx.Observable;
import rx.Subscriber;
import rx.subjects.PublishSubject;

/**
 * An observable connection for connection oriented protocols.
 *
 * @param  The type of the object that is read from this connection.
 * @param  The type of objects that are written to this connection.
 */
public class ObservableConnection extends DefaultChannelWriter {

    private PublishSubject inputSubject;
    @SuppressWarnings("rawtypes")private final MetricEventsSubject eventsSubject;
    private final ChannelMetricEventProvider metricEventProvider;
    /* Guarded by closeIssued so that its only updated once*/ protected volatile long closeStartTimeMillis = -1;

    protected ObservableConnection(final Channel channel, ChannelMetricEventProvider metricEventProvider,
                                   MetricEventsSubject eventsSubject) {
        super(channel, eventsSubject, metricEventProvider);
        this.eventsSubject = eventsSubject;
        this.metricEventProvider = metricEventProvider;
        inputSubject = PublishSubject.create();
    }

    public Observable getInput() {
        return inputSubject;
    }

    public static   ObservableConnection create(final Channel channel,
                                                            final MetricEventsSubject eventsSubject,
                                                            final ChannelMetricEventProvider metricEventProvider) {
        final ObservableConnection toReturn = new ObservableConnection(channel, metricEventProvider,
                                                                                eventsSubject);
        /**
         * Sending the event here does not leak "this" via the NewRxConnectionEvent as opposed to doing it inside the
         * constructor.
         */
        toReturn.fireNewRxConnectionEvent();

        return toReturn;
    }

    /**
     * Fires a {@link NewRxConnectionEvent} for this connection.
     *
     * This must only be called before passing the connection instance to any other code. The reason why this is
     * not done as part of the constructor is that {@link NewRxConnectionEvent} requires the {@link ObservableConnection}
     * instance which when sending from the constructor will escape "this"
     */
    protected void fireNewRxConnectionEvent() {
        ChannelHandlerContext firstContext = getChannel().pipeline().firstContext();
        firstContext.fireUserEventTriggered(new NewRxConnectionEvent(this, inputSubject));
    }

    /**
     * Closes this connection. This method is idempotent, so it can be called multiple times without any side-effect on
     * the channel. 
* This will also cancel any pending writes on the underlying channel.
* * @return Observable signifying the close on the connection. Returns {@link rx.Observable#error(Throwable)} if the * close is already issued (may not be completed) */ @Override public Observable close() { return super.close(); } @Override protected Observable _close(boolean flush) { final PublishSubject thisSubject = inputSubject; ReadTimeoutPipelineConfigurator.disableReadTimeout(getChannel().pipeline()); if (flush) { Observable toReturn = flush().lift(new Observable.Operator() { @Override public Subscriber call(final Subscriber child) { return new Subscriber() { @Override public void onCompleted() { _closeChannel().subscribe(child); thisSubject.onCompleted(); // Even though closeChannel() returns an Observable, close itself is eager. // So this makes sure we send onCompleted() on subject after close is initialized. // This results in more deterministic behavior for clients. } @Override public void onError(Throwable e) { child.onError(e); } @Override public void onNext(Void aVoid) { // Insignificant } }; } }); toReturn.subscribe(new NoOpSubscriber()); // Since subscribing to returned Observable is not required // by the caller and we need to be subscribed to trigger the // close of channel (_closeChannel()), it is required to // subscribe to the returned Observable. We are not // interested in the result so NoOpSub is used. return toReturn; } else { Observable toReturn = _closeChannel(); thisSubject.onCompleted(); // Even though closeChannel() returns an Observable, close itself is eager. // So this makes sure we send onCompleted() on subject after close is initialized. // This results in more deterministic behavior for clients. return toReturn; } } @SuppressWarnings("unchecked") protected Observable _closeChannel() { closeStartTimeMillis = Clock.newStartTimeMillis(); eventsSubject.onEvent(metricEventProvider.getChannelCloseStartEvent()); final ChannelFuture closeFuture = getChannel().close(); /** * This listener if added inside the returned Observable onSubscribe() function, would mean that the * metric events will only be fired if someone subscribed to the close() Observable. However, we need them to * fire independent of someone subscribing. */ closeFuture.addListener(new ChannelFutureListener() { @SuppressWarnings("unchecked") @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { eventsSubject.onEvent(metricEventProvider.getChannelCloseSuccessEvent(), Clock.onEndMillis(closeStartTimeMillis)); } else { eventsSubject.onEvent(metricEventProvider.getChannelCloseFailedEvent(), Clock.onEndMillis(closeStartTimeMillis), future.cause()); } } }); return Observable.create(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { closeFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { subscriber.onCompleted(); } else { subscriber.onError(future.cause()); } } }); } }); } protected void updateInputSubject(PublishSubject newSubject) { inputSubject = newSubject; } }