Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements dedicated Publisher/Subscriber for each request type #761 #761

Merged
merged 1 commit into from
Jul 29, 2020

Conversation

OlegDokuka
Copy link
Member

@OlegDokuka OlegDokuka commented Mar 22, 2020

This PR provides fully reworked internals for all possible interactions for Rsocket requester and responder

fixes #742 #641 #613 #641 #760

@OlegDokuka
Copy link
Member Author

Benches

Base Line RC7-SNAPSHOT

Benchmark                                            Mode  Cnt        Score       Error  Units
RSocketPerf.fireAndForget                           thrpt   10  1919644.508 ± 55411.004  ops/s
RSocketPerf.requestChannelWithRequestAllStrategy    thrpt   10        7.325 ±     0.031  ops/s
RSocketPerf.requestChannelWithRequestByOneStrategy  thrpt   10        4.909 ±     0.051  ops/s
RSocketPerf.requestResponse                         thrpt   10   855283.194 ±  6451.949  ops/s
RSocketPerf.requestStreamWithRequestAllStrategy     thrpt   10       18.015 ±     0.599  ops/s
RSocketPerf.requestStreamWithRequestByOneStrategy   thrpt   10       11.161 ±     0.114  ops/s

Current Branch

Benchmark                                            Mode  Cnt        Score       Error  Units
RSocketPerf.fireAndForget                           thrpt   10  2125869.835 ± 46295.155  ops/s
RSocketPerf.requestChannelWithRequestAllStrategy    thrpt   10        9.901 ±     0.089  ops/s
RSocketPerf.requestChannelWithRequestByOneStrategy  thrpt   10        5.727 ±     0.025  ops/s
RSocketPerf.requestResponse                         thrpt   10   937050.386 ± 22547.203  ops/s
RSocketPerf.requestStreamWithRequestAllStrategy     thrpt   10       21.247 ±     0.560  ops/s
RSocketPerf.requestStreamWithRequestByOneStrategy   thrpt   10       12.417 ±     0.184  ops/s

@OlegDokuka OlegDokuka added this to the 1.1 milestone Apr 5, 2020
@OlegDokuka OlegDokuka changed the title Bugfix/rework rsocket internals [Rework] RSocket Interactions Reimplementation Apr 8, 2020
@OlegDokuka OlegDokuka force-pushed the bugfix/rework-rsocket-internals branch from 231cae8 to a78a14d Compare April 8, 2020 12:08
@OlegDokuka
Copy link
Member Author

OlegDokuka commented Apr 8, 2020

Testing Matrix Checklist

Generic

Interaction\Cases State Machine Transition First Frame Sent Condition Initial RequestN Frame RequestN Frame Cancel Request/Cancel Frames Serialisation Reassembly Support Fragmentation Support RefCnt Validation (during subscribe) Payload Size Validation (during subscribe) Terminates onComplete/onError/cancel
FireAndForgetMono
RequestResponseMono
RequestStreamFlux
RequestChannelFlux

Racing Cases

@OlegDokuka OlegDokuka force-pushed the bugfix/rework-rsocket-internals branch from a78a14d to e7efe6d Compare April 8, 2020 12:49
@OlegDokuka OlegDokuka force-pushed the bugfix/rework-rsocket-internals branch 3 times, most recently from 5bd5fcb to 2d6c698 Compare July 15, 2020 07:35
@OlegDokuka OlegDokuka removed the bug label Jul 15, 2020
Copy link
Contributor

@rstoyanchev rstoyanchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StreamManager provides a way for requester Mono's and responder Subscriber's to access something back from RSocketRequester and RSocketResponder. This can be taken further to avoid passing other fields as well.

For example RSocketRequester and RSocketResponder could extend this (replacing StreamManager and AbstractStreamManager):

class RequesterResponderSupport {

  private final int mtu;
  private final int maxFrameLength;
  private final int maxInboundPayloadSize;
  private final PayloadDecoder payloadDecoder;
  private final ByteBufAllocator allocator;

  @Nullable
  final StreamIdSupplier streamIdSupplier;
  final IntObjectMap<FrameHandler> activeStreams;

  private final UnboundedProcessor<ByteBuf> sendProcessor;

  public RequesterResponderSupport(
    int mtu,
    int maxFrameLength,
    int maxInboundPayloadSize,
    PayloadDecoder payloadDecoder,
    ByteBufAllocator allocator,
    @Nullable StreamIdSupplier streamIdSupplier,
    IntObjectMap<FrameHandler> activeStreams) {

    this.activeStreams = activeStreams;
    this.mtu = mtu;
    this.maxFrameLength = maxFrameLength;
    this.maxInboundPayloadSize = maxInboundPayloadSize;
    this.payloadDecoder = payloadDecoder;
    this.allocator = allocator;
    this.streamIdSupplier = streamIdSupplier;
    this.sendProcessor = new UnboundedProcessor<>();
  }

  public int getMtu() {
    return mtu;
  }

  public int getMaxFrameLength() {
    return maxFrameLength;
  }

  public int getMaxInboundPayloadSize() {
    return maxInboundPayloadSize;
  }

  public PayloadDecoder getPayloadDecoder() {
    return payloadDecoder;
  }

  public ByteBufAllocator getAllocator() {
    return allocator;
  }

  public UnboundedProcessor<ByteBuf> getSendProcessor() {
    return sendProcessor;
  }

  public synchronized int getNextId() {
    if (this.streamIdSupplier != null) {
      return this.streamIdSupplier.nextStreamId(this.activeStreams);
    }
    else {
      throw new UnsupportedOperationException("Responder can not issue id");
    }
  }

  public synchronized int addAndGetNextId(FrameHandler frameHandler) {
    if (this.streamIdSupplier != null) {
      final IntObjectMap<FrameHandler> activeStreams = this.activeStreams;
      final int streamId = this.streamIdSupplier.nextStreamId(activeStreams);

      activeStreams.put(streamId, frameHandler);

      return streamId;
    }
    else {
      throw new UnsupportedOperationException("Responder can not issue id");
    }
  }

  public FrameHandler get(int streamId) {
    return this.activeStreams.get(streamId);
  }

  public boolean remove(int streamId, FrameHandler frameHandler) {
    return this.activeStreams.remove(streamId, frameHandler);
  }
}

Now the requester Mono's and Flux's can be created more easily:

  @Override
  public Flux<Payload> requestStream(Payload payload) {
    return new RequestStreamFlux(payload, this);
  }

Likewise for responder Subscriber's:

RequestResponseSubscriber subscriber = new RequestResponseSubscriber(streamId, frame, this);

There might be other opportunities for re-use as well through such a common base class.

@OlegDokuka OlegDokuka force-pushed the bugfix/rework-rsocket-internals branch 2 times, most recently from f86226d to eaeb099 Compare July 20, 2020 20:04
AtomicLongFieldUpdater<T> updater,
T instance,
ReassembledFramesHolder reassembledFramesHolder,
Subscription subscription,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subscription is the RequesterFrameHandler instance. Why don't we add cancel() to RequesterFrameHandler or have it extend Subscription so that only RequesterFrameHandler is passed in, making it easier to understand where cancel() is handled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to avoid mixing RSocket spec frame handling with the reactive streams. First of all, it makes it impossible to understand from where method was invoked, especially when it comes to requestChannel case. Initially, it was reactive streams interfaces, but after lots of issues with understanding where and how should I handle every call (depends on the inbound vs outbound) I decided to get rid of that idea and make things fully separate

Comment on lines +103 to +102
final Payload p = this.payload;
try {
if (!isValid(this.mtu, this.maxFrameLength, p, false)) {
lazyTerminate(STATE, this);
Operators.error(
actual,
new IllegalArgumentException(
String.format(INVALID_PAYLOAD_ERROR_MESSAGE, this.maxFrameLength)));
p.release();
return;
}
} catch (IllegalReferenceCountException e) {
lazyTerminate(STATE, this);
Operators.error(actual, e);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't the payload be validated before subscription, basically as soon as it is provided? Maybe RSocketRequester could hold this logic and make the check before creating a requester Mono/Flux, thus also re-using the logic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, it is going to be code duplication. The only different - that code would be in a different place. Thus, once something needed to be adjusted - we would need to look at more places and not only to Requester / Responder operators

rsocket-core/src/main/java/io/rsocket/core/StateUtils.java Outdated Show resolved Hide resolved
Copy link
Contributor

@simonbasle simonbasle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't dig far deeper after yesterday's live review. it seems @rstoyanchev has already spotted quite a few elements to improve. overall I'd try to add more comments and documentations, especially on utils classes (like what's been done in StateUtils 👍)

@OlegDokuka
Copy link
Member Author

StreamManager provides a way for requester Mono's and responder Subscriber's to access something back from RSocketRequester and RSocketResponder. This can be taken further to avoid passing other fields as well.

For example RSocketRequester and RSocketResponder could extend this (replacing StreamManager and AbstractStreamManager):

class RequesterResponderSupport {

  private final int mtu;
  private final int maxFrameLength;
  private final int maxInboundPayloadSize;
  private final PayloadDecoder payloadDecoder;
  private final ByteBufAllocator allocator;

  @Nullable
  final StreamIdSupplier streamIdSupplier;
  final IntObjectMap<FrameHandler> activeStreams;

  private final UnboundedProcessor<ByteBuf> sendProcessor;

  public RequesterResponderSupport(
    int mtu,
    int maxFrameLength,
    int maxInboundPayloadSize,
    PayloadDecoder payloadDecoder,
    ByteBufAllocator allocator,
    @Nullable StreamIdSupplier streamIdSupplier,
    IntObjectMap<FrameHandler> activeStreams) {

    this.activeStreams = activeStreams;
    this.mtu = mtu;
    this.maxFrameLength = maxFrameLength;
    this.maxInboundPayloadSize = maxInboundPayloadSize;
    this.payloadDecoder = payloadDecoder;
    this.allocator = allocator;
    this.streamIdSupplier = streamIdSupplier;
    this.sendProcessor = new UnboundedProcessor<>();
  }

  public int getMtu() {
    return mtu;
  }

  public int getMaxFrameLength() {
    return maxFrameLength;
  }

  public int getMaxInboundPayloadSize() {
    return maxInboundPayloadSize;
  }

  public PayloadDecoder getPayloadDecoder() {
    return payloadDecoder;
  }

  public ByteBufAllocator getAllocator() {
    return allocator;
  }

  public UnboundedProcessor<ByteBuf> getSendProcessor() {
    return sendProcessor;
  }

  public synchronized int getNextId() {
    if (this.streamIdSupplier != null) {
      return this.streamIdSupplier.nextStreamId(this.activeStreams);
    }
    else {
      throw new UnsupportedOperationException("Responder can not issue id");
    }
  }

  public synchronized int addAndGetNextId(FrameHandler frameHandler) {
    if (this.streamIdSupplier != null) {
      final IntObjectMap<FrameHandler> activeStreams = this.activeStreams;
      final int streamId = this.streamIdSupplier.nextStreamId(activeStreams);

      activeStreams.put(streamId, frameHandler);

      return streamId;
    }
    else {
      throw new UnsupportedOperationException("Responder can not issue id");
    }
  }

  public FrameHandler get(int streamId) {
    return this.activeStreams.get(streamId);
  }

  public boolean remove(int streamId, FrameHandler frameHandler) {
    return this.activeStreams.remove(streamId, frameHandler);
  }
}

Now the requester Mono's and Flux's can be created more easily:

  @Override
  public Flux<Payload> requestStream(Payload payload) {
    return new RequestStreamFlux(payload, this);
  }

Likewise for responder Subscriber's:

RequestResponseSubscriber subscriber = new RequestResponseSubscriber(streamId, frame, this);

There might be other opportunities for re-use as well through such a common base class.

Done. Ready for another round of review

@OlegDokuka OlegDokuka force-pushed the bugfix/rework-rsocket-internals branch 4 times, most recently from c531667 to 0b63bbf Compare July 29, 2020 20:50
@OlegDokuka
Copy link
Member Author

Applied most of requested changes. Will be merging this one. More polishing can be done in the followups to this PR

Signed-off-by: Oleh Dokuka <shadowgun@i.ua>
@OlegDokuka OlegDokuka force-pushed the bugfix/rework-rsocket-internals branch from 0b63bbf to 8be980e Compare July 29, 2020 21:10
@OlegDokuka OlegDokuka changed the title [Rework] RSocket Interactions Reimplementation Implements dedicated Request Publisher/Subscriber for each interaction type Jul 29, 2020
@OlegDokuka OlegDokuka changed the title Implements dedicated Request Publisher/Subscriber for each interaction type Implements dedicated Publisher/Subscriber for each request type Jul 29, 2020
@OlegDokuka OlegDokuka changed the title Implements dedicated Publisher/Subscriber for each request type Implements dedicated Publisher/Subscriber for each request type #761 Jul 29, 2020
@OlegDokuka OlegDokuka merged commit 5fea76d into master Jul 29, 2020
@OlegDokuka OlegDokuka deleted the bugfix/rework-rsocket-internals branch July 29, 2020 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants