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

io.rsocket.core.RequestChannelResponderSubscriber Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015-2020 the original author or authors.
 *
 * 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.rsocket.core;

import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE;
import static io.rsocket.core.PayloadValidationUtils.isValid;
import static io.rsocket.core.SendUtils.sendReleasingPayload;
import static io.rsocket.core.StateUtils.*;
import static reactor.core.Exceptions.TERMINATED;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ReferenceCountUtil;
import io.rsocket.DuplexConnection;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.exceptions.CanceledException;
import io.rsocket.frame.CancelFrameCodec;
import io.rsocket.frame.ErrorFrameCodec;
import io.rsocket.frame.FrameType;
import io.rsocket.frame.PayloadFrameCodec;
import io.rsocket.frame.RequestNFrameCodec;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.plugins.RequestInterceptor;
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.CoreSubscriber;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Operators;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;

final class RequestChannelResponderSubscriber extends Flux
    implements ResponderFrameHandler, Subscription, CoreSubscriber {

  static final Logger logger = LoggerFactory.getLogger(RequestChannelResponderSubscriber.class);

  final int streamId;
  final ByteBufAllocator allocator;
  final PayloadDecoder payloadDecoder;
  final int mtu;
  final int maxFrameLength;
  final int maxInboundPayloadSize;
  final RequesterResponderSupport requesterResponderSupport;
  final DuplexConnection connection;
  final long firstRequest;

  @Nullable final RequestInterceptor requestInterceptor;

  final RSocket handler;

  volatile long state;
  static final AtomicLongFieldUpdater STATE =
      AtomicLongFieldUpdater.newUpdater(RequestChannelResponderSubscriber.class, "state");

  Payload firstPayload;

  Subscription outboundSubscription;
  CoreSubscriber inboundSubscriber;

  CompositeByteBuf frames;

  volatile Throwable inboundError;
  static final AtomicReferenceFieldUpdater
      INBOUND_ERROR =
          AtomicReferenceFieldUpdater.newUpdater(
              RequestChannelResponderSubscriber.class, Throwable.class, "inboundError");

  boolean inboundDone;
  boolean outboundDone;
  long requested;
  long produced;

  public RequestChannelResponderSubscriber(
      int streamId,
      long firstRequestN,
      ByteBuf firstFrame,
      RequesterResponderSupport requesterResponderSupport,
      RSocket handler) {
    this.streamId = streamId;
    this.allocator = requesterResponderSupport.getAllocator();
    this.mtu = requesterResponderSupport.getMtu();
    this.maxFrameLength = requesterResponderSupport.getMaxFrameLength();
    this.maxInboundPayloadSize = requesterResponderSupport.getMaxInboundPayloadSize();
    this.requesterResponderSupport = requesterResponderSupport;
    this.connection = requesterResponderSupport.getDuplexConnection();
    this.payloadDecoder = requesterResponderSupport.getPayloadDecoder();
    this.requestInterceptor = requesterResponderSupport.getRequestInterceptor();
    this.handler = handler;
    this.firstRequest = firstRequestN;

    this.frames =
        ReassemblyUtils.addFollowingFrame(
            allocator.compositeBuffer(), firstFrame, true, maxInboundPayloadSize);
    STATE.lazySet(this, REASSEMBLING_FLAG);
  }

  public RequestChannelResponderSubscriber(
      int streamId,
      long firstRequestN,
      Payload firstPayload,
      RequesterResponderSupport requesterResponderSupport) {
    this.streamId = streamId;
    this.allocator = requesterResponderSupport.getAllocator();
    this.mtu = requesterResponderSupport.getMtu();
    this.maxFrameLength = requesterResponderSupport.getMaxFrameLength();
    this.maxInboundPayloadSize = requesterResponderSupport.getMaxInboundPayloadSize();
    this.requesterResponderSupport = requesterResponderSupport;
    this.connection = requesterResponderSupport.getDuplexConnection();
    this.payloadDecoder = requesterResponderSupport.getPayloadDecoder();
    this.requestInterceptor = requesterResponderSupport.getRequestInterceptor();
    this.firstRequest = firstRequestN;
    this.firstPayload = firstPayload;

    this.handler = null;
    this.frames = null;
  }

  @Override
  // subscriber from the requestChannel method
  public void subscribe(CoreSubscriber actual) {

    long previousState = markSubscribed(STATE, this);
    if (isTerminated(previousState)) {
      Throwable t = Exceptions.terminate(INBOUND_ERROR, this);
      if (t != TERMINATED) {
        //noinspection ConstantConditions
        Operators.error(actual, t);
      } else {
        Operators.error(
            actual,
            new CancellationException("RequestChannelSubscriber has already been terminated"));
      }
      return;
    }

    if (isSubscribed(previousState)) {
      Operators.error(
          actual, new IllegalStateException("RequestChannelSubscriber allows only one Subscriber"));
      return;
    }

    this.inboundSubscriber = actual;
    // sends sender as a subscription since every request|cancel signal should be encoded to
    // requestNFrame|cancelFrame
    actual.onSubscribe(this);
  }

  @Override
  // subscription to the outbound
  public void onSubscribe(Subscription outboundSubscription) {
    if (Operators.validate(this.outboundSubscription, outboundSubscription)) {
      this.outboundSubscription = outboundSubscription;
      outboundSubscription.request(this.firstRequest);
    }
  }

  @Override
  public void request(long n) {
    if (!Operators.validate(n)) {
      return;
    }

    this.requested = Operators.addCap(this.requested, n);

    long previousState = StateUtils.addRequestN(STATE, this, n);
    if (isTerminated(previousState)) {
      // full termination can be the result of both sides completion / cancelFrame / remote or local
      // error
      // therefore, we need to check inbound error value, to see what should be done
      Throwable inboundError = Exceptions.terminate(INBOUND_ERROR, this);
      if (inboundError == TERMINATED) {
        // means inbound was already terminated
        return;
      }

      if (inboundError != null || this.inboundDone) {
        final CoreSubscriber inboundSubscriber = this.inboundSubscriber;

        Payload firstPayload = this.firstPayload;
        if (firstPayload != null) {
          this.firstPayload = null;

          this.produced++;

          inboundSubscriber.onNext(firstPayload);
        }

        if (inboundError != null) {
          inboundSubscriber.onError(inboundError);
        } else {
          inboundSubscriber.onComplete();
        }
      }
      return;
    }

    if (isInboundTerminated(previousState)) {
      // inbound only can be terminated in case of cancellation or complete frame
      if (!hasRequested(previousState) && !isFirstFrameSent(previousState) && this.inboundDone) {
        final CoreSubscriber inboundSubscriber = this.inboundSubscriber;

        final Payload firstPayload = this.firstPayload;
        this.firstPayload = null;

        this.produced++;

        inboundSubscriber.onNext(firstPayload);
        inboundSubscriber.onComplete();

        markFirstFrameSent(STATE, this);
      }
      return;
    }

    if (hasRequested(previousState)) {
      if (isFirstFrameSent(previousState)
          && !isMaxAllowedRequestN(StateUtils.extractRequestN(previousState))) {
        final int streamId = this.streamId;
        final ByteBuf requestNFrame = RequestNFrameCodec.encode(this.allocator, streamId, n);
        this.connection.sendFrame(streamId, requestNFrame);
      }
      return;
    }

    final CoreSubscriber inboundSubscriber = this.inboundSubscriber;

    final Payload firstPayload = this.firstPayload;
    this.firstPayload = null;

    this.produced++;

    inboundSubscriber.onNext(firstPayload);

    previousState = markFirstFrameSent(STATE, this);
    if (isTerminated(previousState)) {
      // full termination can be the result of both sides completion / cancelFrame / remote or local
      // error
      // therefore, we need to check inbound error value, to see what should be done
      Throwable inboundError = Exceptions.terminate(INBOUND_ERROR, this);
      if (inboundError == TERMINATED) {
        // means inbound was already terminated
        return;
      }

      if (inboundError != null) {
        inboundSubscriber.onError(inboundError);
      } else if (this.inboundDone) {
        inboundSubscriber.onComplete();
      }
      return;
    }

    if (isInboundTerminated(previousState)) {
      // inbound only can be terminated in case of cancellation or complete frame
      if (this.inboundDone) {
        inboundSubscriber.onComplete();
      }
      return;
    }

    long requestN = StateUtils.extractRequestN(previousState);
    if (isMaxAllowedRequestN(requestN)) {
      final int streamId = this.streamId;
      final ByteBuf requestNFrame = RequestNFrameCodec.encode(allocator, streamId, requestN);
      this.connection.sendFrame(streamId, requestNFrame);
    } else {
      long firstRequestN = requestN - 1;
      if (firstRequestN > 0) {
        final int streamId = this.streamId;
        final ByteBuf requestNFrame =
            RequestNFrameCodec.encode(this.allocator, streamId, firstRequestN);
        this.connection.sendFrame(streamId, requestNFrame);
      }
    }
  }

  @Override
  // inbound cancellation
  public void cancel() {
    long previousState = markInboundTerminated(STATE, this);
    if (isTerminated(previousState) || isInboundTerminated(previousState)) {
      INBOUND_ERROR.lazySet(this, TERMINATED);
      return;
    }

    if (!isFirstFrameSent(previousState) && !hasRequested(previousState)) {
      final Payload firstPayload = this.firstPayload;
      this.firstPayload = null;
      firstPayload.release();
    }

    final int streamId = this.streamId;

    final boolean isOutboundTerminated = isOutboundTerminated(previousState);
    if (isOutboundTerminated) {
      this.requesterResponderSupport.remove(streamId, this);
    }

    final ByteBuf cancelFrame = CancelFrameCodec.encode(this.allocator, streamId);
    this.connection.sendFrame(streamId, cancelFrame);

    if (isOutboundTerminated) {
      final RequestInterceptor interceptor = requestInterceptor;
      if (interceptor != null) {
        interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, null);
      }
    }
  }

  @Override
  public final void handleCancel() {
    Subscription outboundSubscription = this.outboundSubscription;
    if (outboundSubscription == null) {
      // if subscription is null, it means that streams has not yet reassembled all the fragments
      // and fragmentation of the first frame was cancelled before
      lazyTerminate(STATE, this);

      this.requesterResponderSupport.remove(this.streamId, this);

      final CompositeByteBuf frames = this.frames;
      if (frames != null) {
        this.frames = null;
        frames.release();
      } else {
        final Payload firstPayload = this.firstPayload;
        this.firstPayload = null;
        firstPayload.release();
      }

      final RequestInterceptor interceptor = this.requestInterceptor;
      if (interceptor != null) {
        interceptor.onCancel(this.streamId, FrameType.REQUEST_CHANNEL);
      }
      return;
    }

    long previousState = this.tryTerminate(true);
    if (isTerminated(previousState)) {
      return;
    }

    final RequestInterceptor interceptor = this.requestInterceptor;
    if (interceptor != null) {
      interceptor.onCancel(this.streamId, FrameType.REQUEST_CHANNEL);
    }
  }

  final long tryTerminate(boolean isFromInbound) {
    Exceptions.addThrowable(
        INBOUND_ERROR, this, new CancellationException("Inbound has been canceled"));

    long previousState = markTerminated(STATE, this);
    if (isTerminated(previousState)) {
      return previousState;
    }

    this.requesterResponderSupport.remove(this.streamId, this);

    if (isReassembling(previousState)) {
      final CompositeByteBuf frames = this.frames;
      this.frames = null;
      if (isFromInbound) {
        frames.release();
      } else {
        synchronized (frames) {
          frames.release();
        }
      }
    }

    final Subscription outboundSubscription = this.outboundSubscription;
    if (outboundSubscription == null) {
      return previousState;
    }

    outboundSubscription.cancel();

    if (!isSubscribed(previousState)) {
      final Payload firstPayload = this.firstPayload;
      this.firstPayload = null;
      firstPayload.release();
    } else if (isFirstFrameSent(previousState) && !isInboundTerminated(previousState)) {
      Throwable inboundError = Exceptions.terminate(INBOUND_ERROR, this);
      if (inboundError != TERMINATED) {
        if (isFromInbound) {
          this.inboundDone = true;
          //noinspection ConstantConditions
          this.inboundSubscriber.onError(inboundError);
        } else {
          synchronized (this) {
            this.inboundDone = true;
            //noinspection ConstantConditions
            this.inboundSubscriber.onError(inboundError);
          }
        }
      }
    }

    return previousState;
  }

  final void handlePayload(Payload p) {
    synchronized (this) {
      if (this.inboundDone) {
        // payload from network so it has refCnt > 0
        p.release();
        return;
      }

      final long produced = this.produced;
      if (this.requested == produced) {
        p.release();

        this.inboundDone = true;

        final Throwable cause =
            Exceptions.failWithOverflow(
                "The number of messages received exceeds the number requested");
        boolean wasThrowableAdded = Exceptions.addThrowable(INBOUND_ERROR, this, cause);

        long previousState = markTerminated(STATE, this);
        if (isTerminated(previousState)) {
          if (!wasThrowableAdded) {
            Operators.onErrorDropped(cause, this.inboundSubscriber.currentContext());
          }
          return;
        }

        this.requesterResponderSupport.remove(this.streamId, this);

        this.connection.sendFrame(
            streamId,
            ErrorFrameCodec.encode(
                this.allocator, streamId, new CanceledException(cause.getMessage())));

        if (!isSubscribed(previousState)) {
          final Payload firstPayload = this.firstPayload;
          this.firstPayload = null;
          firstPayload.release();
        } else if (isFirstFrameSent(previousState) && !isInboundTerminated(previousState)) {
          Throwable inboundError = Exceptions.terminate(INBOUND_ERROR, this);
          if (inboundError != TERMINATED) {
            //noinspection ConstantConditions
            this.inboundSubscriber.onError(inboundError);
          }
        }

        // this is downstream subscription so need to cancel it just in case error signal has not
        // reached it
        // needs for disconnected upstream and downstream case
        this.outboundSubscription.cancel();

        final RequestInterceptor interceptor = requestInterceptor;
        if (interceptor != null) {
          interceptor.onTerminate(this.streamId, FrameType.REQUEST_CHANNEL, cause);
        }
        return;
      }

      this.produced = produced + 1;

      this.inboundSubscriber.onNext(p);
    }
  }

  @Override
  public final void handleError(Throwable t) {
    if (this.inboundDone) {
      Operators.onErrorDropped(t, this.inboundSubscriber.currentContext());
      return;
    }

    this.inboundDone = true;
    boolean wasThrowableAdded = Exceptions.addThrowable(INBOUND_ERROR, this, t);

    long previousState = markTerminated(STATE, this);
    if (isTerminated(previousState)) {
      if (!wasThrowableAdded) {
        Operators.onErrorDropped(t, this.inboundSubscriber.currentContext());
      }
      return;
    }

    this.requesterResponderSupport.remove(this.streamId, this);

    if (isReassembling(previousState)) {
      final CompositeByteBuf frames = this.frames;
      this.frames = null;
      frames.release();
    }

    if (!isSubscribed(previousState)) {
      final Payload firstPayload = this.firstPayload;
      this.firstPayload = null;
      firstPayload.release();
    } else if (isFirstFrameSent(previousState) && !isInboundTerminated(previousState)) {
      Throwable inboundError = Exceptions.terminate(INBOUND_ERROR, this);
      if (inboundError != TERMINATED) {
        //noinspection ConstantConditions
        this.inboundSubscriber.onError(inboundError);
      }
    }

    // this is downstream subscription so need to cancel it just in case error signal has not
    // reached it
    // needs for disconnected upstream and downstream case
    this.outboundSubscription.cancel();

    final RequestInterceptor interceptor = requestInterceptor;
    if (interceptor != null) {
      interceptor.onTerminate(this.streamId, FrameType.REQUEST_CHANNEL, t);
    }
  }

  @Override
  public void handleComplete() {
    if (this.inboundDone) {
      return;
    }

    this.inboundDone = true;

    long previousState = markInboundTerminated(STATE, this);

    final boolean isOutboundTerminated = isOutboundTerminated(previousState);
    if (isOutboundTerminated) {
      this.requesterResponderSupport.remove(this.streamId, this);
    }

    if (isFirstFrameSent(previousState)) {
      this.inboundSubscriber.onComplete();
    }

    if (isOutboundTerminated) {
      final RequestInterceptor interceptor = this.requestInterceptor;
      if (interceptor != null) {
        interceptor.onTerminate(this.streamId, FrameType.REQUEST_CHANNEL, null);
      }
    }
  }

  @Override
  public void handleNext(ByteBuf frame, boolean hasFollows, boolean isLastPayload) {
    long state = this.state;
    if (isTerminated(state)) {
      return;
    }

    if (!hasFollows && !isReassembling(state)) {
      Payload payload;
      try {
        payload = this.payloadDecoder.apply(frame);
      } catch (Throwable t) {
        long previousState = this.tryTerminate(true);
        if (isTerminated(previousState)) {
          Operators.onErrorDropped(t, this.inboundSubscriber.currentContext());
          return;
        } else if (isOutboundTerminated(previousState)) {
          final RequestInterceptor interceptor = this.requestInterceptor;
          if (interceptor != null) {
            interceptor.onTerminate(this.streamId, FrameType.REQUEST_CHANNEL, t);
          }

          Operators.onErrorDropped(t, this.inboundSubscriber.currentContext());
          return;
        }

        this.outboundDone = true;
        // send error to terminate interaction
        final int streamId = this.streamId;
        final ByteBuf errorFrame =
            ErrorFrameCodec.encode(this.allocator, streamId, new CanceledException(t.getMessage()));
        this.connection.sendFrame(streamId, errorFrame);

        final RequestInterceptor interceptor = requestInterceptor;
        if (interceptor != null) {
          interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, t);
        }
        return;
      }

      this.handlePayload(payload);
      if (isLastPayload) {
        this.handleComplete();
      }
      return;
    }

    CompositeByteBuf frames = this.frames;
    if (frames == null) {
      frames =
          ReassemblyUtils.addFollowingFrame(
              this.allocator.compositeBuffer(), frame, hasFollows, this.maxInboundPayloadSize);
      this.frames = frames;

      long previousState = markReassembling(STATE, this);
      if (isTerminated(previousState)) {
        this.frames = null;
        frames.release();
        return;
      }
    } else {
      try {
        frames =
            ReassemblyUtils.addFollowingFrame(
                frames, frame, hasFollows, this.maxInboundPayloadSize);
      } catch (IllegalStateException e) {
        if (isTerminated(this.state)) {
          return;
        }

        long previousState = this.tryTerminate(true);
        if (isTerminated(previousState)) {
          Operators.onErrorDropped(e, this.inboundSubscriber.currentContext());
          return;
        } else if (isOutboundTerminated(previousState)) {
          final RequestInterceptor interceptor = this.requestInterceptor;
          if (interceptor != null) {
            interceptor.onTerminate(this.streamId, FrameType.REQUEST_CHANNEL, e);
          }

          Operators.onErrorDropped(e, this.inboundSubscriber.currentContext());
          return;
        }

        this.outboundDone = true;
        // send error to terminate interaction
        final int streamId = this.streamId;
        final ByteBuf errorFrame =
            ErrorFrameCodec.encode(
                this.allocator,
                streamId,
                new CanceledException("Failed to reassemble payload. Cause: " + e.getMessage()));
        this.connection.sendFrame(streamId, errorFrame);

        final RequestInterceptor interceptor = this.requestInterceptor;
        if (interceptor != null) {
          interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, e);
        }

        return;
      }
    }

    if (!hasFollows) {
      long previousState = markReassembled(STATE, this);
      if (isTerminated(previousState)) {
        return;
      }

      this.frames = null;

      Payload payload;
      try {
        payload = this.payloadDecoder.apply(frames);
        frames.release();
      } catch (Throwable t) {
        ReferenceCountUtil.safeRelease(frames);

        previousState = this.tryTerminate(true);
        if (isTerminated(previousState)) {
          Operators.onErrorDropped(t, this.inboundSubscriber.currentContext());
          return;
        } else if (isOutboundTerminated(previousState)) {
          final RequestInterceptor interceptor = this.requestInterceptor;
          if (interceptor != null) {
            interceptor.onTerminate(this.streamId, FrameType.REQUEST_CHANNEL, t);
          }

          Operators.onErrorDropped(t, this.inboundSubscriber.currentContext());
          return;
        }

        // send error to terminate interaction
        final int streamId = this.streamId;
        final ByteBuf errorFrame =
            ErrorFrameCodec.encode(
                this.allocator,
                streamId,
                new CanceledException("Failed to reassemble payload. Cause: " + t.getMessage()));
        this.connection.sendFrame(streamId, errorFrame);

        final RequestInterceptor interceptor = requestInterceptor;
        if (interceptor != null) {
          interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, t);
        }

        return;
      }

      if (this.outboundSubscription == null) {
        this.firstPayload = payload;
        Flux source = this.handler.requestChannel(this);
        source.subscribe(this);
      } else {
        this.handlePayload(payload);
      }

      if (isLastPayload) {
        this.handleComplete();
      }
    }
  }

  @Override
  public void onNext(Payload p) {
    if (this.outboundDone) {
      ReferenceCountUtil.safeRelease(p);
      return;
    }

    final int streamId = this.streamId;
    final DuplexConnection connection = this.connection;
    final ByteBufAllocator allocator = this.allocator;

    final int mtu = this.mtu;
    try {
      if (!isValid(mtu, this.maxFrameLength, p, false)) {
        p.release();

        // FIXME: must be scheduled on the connection event-loop to achieve serial
        //  behaviour on the inbound subscriber
        long previousState = this.tryTerminate(false);
        if (isTerminated(previousState)) {
          Operators.onErrorDropped(
              new IllegalArgumentException(
                  String.format(INVALID_PAYLOAD_ERROR_MESSAGE, this.maxFrameLength)),
              this.inboundSubscriber.currentContext());
          return;
        } else if (isOutboundTerminated(previousState)) {
          final IllegalArgumentException e =
              new IllegalArgumentException(
                  String.format(INVALID_PAYLOAD_ERROR_MESSAGE, this.maxFrameLength));

          final RequestInterceptor interceptor = this.requestInterceptor;
          if (interceptor != null) {
            interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, e);
          }

          Operators.onErrorDropped(e, this.inboundSubscriber.currentContext());
          return;
        }

        final CanceledException e =
            new CanceledException(
                String.format(INVALID_PAYLOAD_ERROR_MESSAGE, this.maxFrameLength));
        final ByteBuf errorFrame = ErrorFrameCodec.encode(allocator, streamId, e);
        connection.sendFrame(streamId, errorFrame);

        final RequestInterceptor interceptor = this.requestInterceptor;
        if (interceptor != null) {
          interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, e);
        }
        return;
      }
    } catch (IllegalReferenceCountException e) {

      // FIXME: must be scheduled on the connection event-loop to achieve serial
      //  behaviour on the inbound subscriber
      long previousState = this.tryTerminate(false);
      if (isTerminated(previousState)) {
        Operators.onErrorDropped(e, this.inboundSubscriber.currentContext());
        return;
      } else if (isOutboundTerminated(previousState)) {
        final RequestInterceptor interceptor = this.requestInterceptor;
        if (interceptor != null) {
          interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, e);
        }

        Operators.onErrorDropped(e, this.inboundSubscriber.currentContext());
        return;
      }

      final ByteBuf errorFrame =
          ErrorFrameCodec.encode(
              allocator,
              streamId,
              new CanceledException("Failed to validate payload. Cause:" + e.getMessage()));
      connection.sendFrame(streamId, errorFrame);

      final RequestInterceptor interceptor = requestInterceptor;
      if (interceptor != null) {
        interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, e);
      }
      return;
    }

    try {
      sendReleasingPayload(streamId, FrameType.NEXT, mtu, p, connection, allocator, false);
    } catch (Throwable t) {
      // FIXME: must be scheduled on the connection event-loop to achieve serial
      //  behaviour on the inbound subscriber
      long previousState = this.tryTerminate(false);
      final RequestInterceptor interceptor = requestInterceptor;
      if (interceptor != null && !isTerminated(previousState)) {
        interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, t);
      }
    }
  }

  @Override
  public void onError(Throwable t) {
    if (this.outboundDone) {
      Operators.onErrorDropped(t, this.inboundSubscriber.currentContext());
      return;
    }

    boolean wasThrowableAdded =
        Exceptions.addThrowable(
            INBOUND_ERROR,
            this,
            new CancellationException("Outbound has terminated with an error"));
    this.outboundDone = true;

    long previousState = markTerminated(STATE, this);
    if (isTerminated(previousState)) {
      Operators.onErrorDropped(t, this.inboundSubscriber.currentContext());
      return;
    }

    final int streamId = this.streamId;

    this.requesterResponderSupport.remove(streamId, this);

    if (isReassembling(previousState)) {
      final CompositeByteBuf frames = this.frames;
      this.frames = null;
      synchronized (frames) {
        frames.release();
      }
    }

    if (!isSubscribed(previousState)) {
      final Payload firstPayload = this.firstPayload;
      this.firstPayload = null;
      firstPayload.release();
    } else if (wasThrowableAdded
        && isFirstFrameSent(previousState)
        && !isInboundTerminated(previousState)) {
      Throwable inboundError = Exceptions.terminate(INBOUND_ERROR, this);
      if (inboundError != TERMINATED) {
        // FIXME: must be scheduled on the connection event-loop to achieve serial
        //  behaviour on the inbound subscriber
        synchronized (this) {
          this.inboundDone = true;
          //noinspection ConstantConditions
          this.inboundSubscriber.onError(inboundError);
        }
      }
    }

    final ByteBuf errorFrame = ErrorFrameCodec.encode(this.allocator, streamId, t);
    this.connection.sendFrame(streamId, errorFrame);

    final RequestInterceptor interceptor = this.requestInterceptor;
    if (interceptor != null) {
      interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, t);
    }
  }

  @Override
  public void onComplete() {
    if (this.outboundDone) {
      return;
    }

    this.outboundDone = true;

    long previousState = markOutboundTerminated(STATE, this, false);
    if (isTerminated(previousState)) {
      return;
    }

    final int streamId = this.streamId;

    final boolean isInboundTerminated = isInboundTerminated(previousState);
    if (isInboundTerminated) {
      this.requesterResponderSupport.remove(streamId, this);
    }

    final ByteBuf completeFrame = PayloadFrameCodec.encodeComplete(this.allocator, streamId);
    this.connection.sendFrame(streamId, completeFrame);

    if (isInboundTerminated) {
      final RequestInterceptor interceptor = this.requestInterceptor;
      if (interceptor != null) {
        interceptor.onTerminate(streamId, FrameType.REQUEST_CHANNEL, null);
      }
    }
  }

  @Override
  public final void handleRequestN(long n) {
    this.outboundSubscription.request(n);
  }

  @Override
  public Context currentContext() {
    return SendUtils.DISCARD_CONTEXT;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy