reactor.ipc.netty.channel.ChannelOperations Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of reactor-netty Show documentation
Show all versions of reactor-netty Show documentation
Reactive Streams Netty driver
/*
* Copyright (c) 2011-2018 Pivotal Software Inc, All Rights Reserved.
*
* 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 reactor.ipc.netty.channel;
import java.net.InetSocketAddress;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.DirectProcessor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Operators;
import reactor.ipc.netty.NettyConnector;
import reactor.ipc.netty.NettyContext;
import reactor.ipc.netty.NettyInbound;
import reactor.ipc.netty.NettyOutbound;
import reactor.ipc.netty.NettyPipeline;
import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.context.Context;
/**
* A bridge between an immutable {@link Channel} and {@link NettyInbound} /
* {@link NettyOutbound} semantics exposed to user
* {@link NettyConnector#newHandler(BiFunction)}
*
* @author Stephane Maldini
* @since 0.6
*/
public class ChannelOperations
implements NettyInbound, NettyOutbound, NettyContext, CoreSubscriber {
/**
* Create a new {@link ChannelOperations} attached to the {@link Channel} attribute
* {@link #OPERATIONS_KEY}.
* Attach the {@link NettyPipeline#ReactiveBridge} handle.
*
* @param channel the new {@link Channel} connection
* @param handler the user-provided {@link BiFunction} i/o handler
* @param context the dispose callback
* @param the {@link NettyInbound} type
* @param the {@link NettyOutbound} type
*
* @return the created {@link ChannelOperations} bridge
*/
public static ChannelOperations bind(
Channel channel,
BiFunction super INBOUND, ? super OUTBOUND, ? extends Publisher> handler,
ContextHandler> context) {
@SuppressWarnings("unchecked") ChannelOperations ops =
new ChannelOperations<>(channel, handler, context);
return ops;
}
/**
* Return a Noop {@link BiFunction} handler
*
* @param reified inbound type
* @param reified outbound type
*
* @return a Noop {@link BiFunction} handler
*/
@SuppressWarnings("unchecked")
public static BiFunction super INBOUND, ? super OUTBOUND, ? extends Publisher> noopHandler() {
return PING;
}
/**
* Return the current {@link Channel} bound
* {@link ChannelOperations} or null if none
*
* @param ch the current {@link Channel}
*
* @return the current {@link Channel} bound
* {@link ChannelOperations} or null if none
*/
public static ChannelOperations, ?> get(Channel ch) {
return ch.attr(OPERATIONS_KEY)
.get();
}
static ChannelOperations, ?> tryGetAndSet(Channel ch, ChannelOperations, ?> ops) {
Attribute attr = ch.attr(ChannelOperations.OPERATIONS_KEY);
for (; ; ) {
ChannelOperations, ?> op = attr.get();
if (op != null) {
return op;
}
if (attr.compareAndSet(null, ops)) {
return null;
}
}
}
final BiFunction super INBOUND, ? super OUTBOUND, ? extends Publisher>
handler;
final Channel channel;
final FluxReceive inbound;
final DirectProcessor onInactive;
final ContextHandler> context;
@SuppressWarnings("unchecked")
volatile Subscription outboundSubscription;
protected ChannelOperations(Channel channel,
ChannelOperations replaced) {
this(channel, replaced.handler, replaced.context, replaced.onInactive);
}
protected ChannelOperations(Channel channel,
BiFunction super INBOUND, ? super OUTBOUND, ? extends Publisher> handler,
ContextHandler> context) {
this(channel, handler, context, DirectProcessor.create());
}
protected ChannelOperations(Channel channel,
BiFunction super INBOUND, ? super OUTBOUND, ? extends Publisher> handler,
ContextHandler> context, DirectProcessor processor) {
this.handler = Objects.requireNonNull(handler, "handler");
this.channel = Objects.requireNonNull(channel, "channel");
this.context = Objects.requireNonNull(context, "context");
this.inbound = new FluxReceive(this);
this.onInactive = processor;
Subscription[] _s = new Subscription[1];
Mono.fromDirect(context.onCloseOrRelease(channel))
.doOnSubscribe(s -> _s[0] = s)
.subscribe(onInactive);
if(_s[0] != null) { //remove closeFuture listener ref by onCloseOrRelease
// subscription when onInactive is called for any reason from
// onHandlerTerminate
onInactive.subscribe(null, null, _s[0]::cancel);
}
}
@Override
public InetSocketAddress address() {
Channel c = channel();
if (c instanceof SocketChannel) {
return ((SocketChannel) c).remoteAddress();
}
if (c instanceof DatagramChannel) {
InetSocketAddress a = ((DatagramChannel) c).remoteAddress();
return a != null ? a : ((DatagramChannel)c ).localAddress();
}
throw new IllegalStateException("Does not have an InetSocketAddress");
}
@Override
public final Channel channel() {
return channel;
}
@Override
public final NettyContext context() {
return this;
}
@Override
public ChannelOperations context(Consumer contextCallback) {
contextCallback.accept(context());
return this;
}
@Override
public void dispose() {
inbound.cancel();
channel.close();
}
@Override
public final boolean isDisposed() {
return get(channel()) != this;
}
@Override
public final Mono onClose() {
return Mono.fromDirect(onInactive);
}
@Override
public NettyContext onClose(final Runnable onClose) {
onInactive.subscribe(null, e -> onClose.run(), onClose);
return this;
}
@Override
public final void onComplete() {
Subscription s =
OUTBOUND_CLOSE.getAndSet(this, Operators.cancelledSubscription());
if (s == Operators.cancelledSubscription() || isDisposed()) {
return;
}
onOutboundComplete();
}
@Override
public final void onError(Throwable t) {
Subscription s =
OUTBOUND_CLOSE.getAndSet(this, Operators.cancelledSubscription());
if (s == Operators.cancelledSubscription() || isDisposed()) {
if(log.isDebugEnabled()){
log.error("An outbound error could not be processed", t);
}
return;
}
onOutboundError(t);
}
@Override
public final void onNext(Void aVoid) {
}
@Override
public final void onSubscribe(Subscription s) {
if (Operators.setOnce(OUTBOUND_CLOSE, this, s)) {
s.request(Long.MAX_VALUE);
}
}
@Override
public Flux> receiveObject() {
return inbound;
}
@Override
public final InetSocketAddress remoteAddress() {
return (InetSocketAddress) channel.remoteAddress();
}
@Override
public String toString() {
return channel.toString();
}
/**
* Return true if inbound traffic is not expected anymore
*
* @return true if inbound traffic is not expected anymore
*/
protected final boolean isInboundCancelled() {
return inbound.isCancelled();
}
/**
* Return true if inbound traffic is not incoming or expected anymore
*
* @return true if inbound traffic is not incoming or expected anymore
*/
protected final boolean isInboundDisposed() {
return inbound.isDisposed();
}
/**
* Connector handler provided by user
*
* @return Connector handler provided by user
*/
protected final BiFunction super INBOUND, ? super OUTBOUND, ? extends Publisher> handler() {
return handler;
}
/**
* React on input initialization
*
*/
@SuppressWarnings("unchecked")
protected void onHandlerStart() {
applyHandler();
context.fireContextActive(this);
}
/**
* React on inbound {@link Channel#read}
*
* @param ctx the context
* @param msg the read payload
*/
protected void onInboundNext(ChannelHandlerContext ctx, Object msg) {
if (msg == null) {
onInboundError(new NullPointerException("msg is null"));
return;
}
inbound.onInboundNext(msg);
}
/**
* Replace and complete previous operation inbound
*
* @param ops a new operations
*
* @return true if replaced
*/
protected final boolean replace(ChannelOperations, ?> ops) {
return channel.attr(OPERATIONS_KEY)
.compareAndSet(this, ops);
}
/**
* React on inbound cancel (receive() subscriber cancelled)
*/
protected void onInboundCancel() {
}
/**
* React on inbound completion (last packet)
*/
protected void onInboundComplete() {
inbound.onInboundComplete();
}
/**
* React on inbound close (channel closed prematurely)
*/
protected void onInboundClose() {
onHandlerTerminate();
}
/**
* React on inbound/outbound completion (last packet)
*/
protected void onOutboundComplete() {
if (log.isDebugEnabled()) {
log.debug("[{}] {} User Handler requesting close connection", formatName(), channel());
}
markPersistent(false);
onHandlerTerminate();
}
/**
* React on inbound/outbound error
*
* @param err the {@link Throwable} cause
*/
protected void onOutboundError(Throwable err) {
discreteRemoteClose(err);
markPersistent(false);
onHandlerTerminate();
}
/**
* Apply the user-provided {@link NettyConnector} handler
*/
@SuppressWarnings("unchecked")
protected final void applyHandler() {
// channel.pipeline()
// .fireUserEventTriggered(NettyPipeline.handlerStartedEvent());
if (log.isDebugEnabled()) {
log.debug("[{}] {} handler is being applied: {}", formatName(), channel
(), handler);
}
Mono.fromDirect(handler.apply((INBOUND) this, (OUTBOUND) this))
.subscribe(this);
}
/**
* Try filtering out remote close unless traced, return true if filtered
*
* @param err the error to check
*
* @return true if filtered
*/
protected final boolean discreteRemoteClose(Throwable err) {
if (AbortedException.isConnectionReset(err)) {
if (log.isDebugEnabled()) {
log.debug("{} [{}] Connection closed remotely", channel.toString(),
formatName(),
err);
}
return true;
}
log.error("[" + formatName() + "] Error processing connection. Requesting close the channel",
err);
return false;
}
/**
* Final release/close (last packet)
*/
protected final void onHandlerTerminate() {
if (replace(null)) {
if(log.isTraceEnabled()){
log.trace("{} Disposing ChannelOperation from a channel", channel(), new Exception
("ChannelOperation terminal stack"));
}
try {
Operators.terminate(OUTBOUND_CLOSE, this);
onInactive.onComplete(); //signal senders and other interests
// Do not call directly inbound.onInboundComplete()
// HttpClientOperations need to notify with error
// when there is no response state
onInboundComplete();
}
finally {
channel.pipeline()
.fireUserEventTriggered(NettyPipeline.handlerTerminatedEvent());
}
}
}
/**
* Drop pending content and complete inbound
*/
protected final void discard(){
if(log.isDebugEnabled()){
log.debug("{} Discarding inbound content", channel);
}
inbound.discard();
}
/**
* React on inbound error
*
* @param err the {@link Throwable} cause
*/
protected final void onInboundError(Throwable err) {
discreteRemoteClose(err);
inbound.onInboundError(err);
}
/**
* Return the available parent {@link ContextHandler} for user-facing lifecycle
* handling
*
* @return the available parent {@link ContextHandler}for user-facing lifecycle
* handling
*/
protected final ContextHandler> parentContext() {
return context;
}
/**
* Return formatted name of this operation
*
* @return formatted name of this operation
*/
protected final String formatName() {
return getClass().getSimpleName()
.replace("Operations", "");
}
@Override
public Context currentContext() {
return context.sink.currentContext();
}
/**
* A {@link ChannelOperations} factory
*/
@FunctionalInterface
public interface OnNew {
/**
* Create a new {@link ChannelOperations} given a netty channel, a parent
* {@link ContextHandler} and an optional message (nullable).
*
* @param c a {@link Channel}
* @param contextHandler a {@link ContextHandler}
* @param msg an optional message
*
* @return a new {@link ChannelOperations}
*/
ChannelOperations, ?> create(CHANNEL c, ContextHandler> contextHandler, Object msg);
}
/**
* The attribute in {@link Channel} to store the current {@link ChannelOperations}
*/
protected static final AttributeKey OPERATIONS_KEY = AttributeKey.newInstance("nettyOperations");
static final Logger log = Loggers.getLogger(ChannelOperations.class);
static final BiFunction PING = (i, o) -> Flux.empty();
static final AtomicReferenceFieldUpdater
OUTBOUND_CLOSE = AtomicReferenceFieldUpdater.newUpdater(ChannelOperations.class,
Subscription.class,
"outboundSubscription");
}