io.reactivex.netty.channel.ObservableConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rx-netty Show documentation
Show all versions of rx-netty Show documentation
rx-netty developed by Netflix
/*
* 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 super Void> call(final Subscriber super Void> 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 super Void> 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;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy