Skip to content

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

Merged
OlegDokuka merged 1 commit into
masterfrom
bugfix/rework-rsocket-internals
Jul 29, 2020
Merged

Implements dedicated Publisher/Subscriber for each request type #761#761
OlegDokuka merged 1 commit into
masterfrom
bugfix/rework-rsocket-internals

Conversation

@OlegDokuka

@OlegDokukaOlegDokuka commented Mar 22, 2020

Copy link
Copy Markdown
Member

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

fixes#742#641#613#641#760

@OlegDokuka

Copy link
Copy Markdown
MemberAuthor

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

@OlegDokukaOlegDokuka mentioned this pull request Apr 3, 2020
This was linked to issues Apr 3, 2020
@OlegDokukaOlegDokuka added this to the 1.1 milestone Apr 5, 2020
@OlegDokukaOlegDokuka changed the title Bugfix/rework rsocket internals[Rework] RSocket Interactions Reimplementation Apr 8, 2020
@OlegDokuka
OlegDokukaforce-pushed the bugfix/rework-rsocket-internals branch from 231cae8 to a78a14dCompareApril 8, 2020 12:08
@OlegDokuka

OlegDokuka commented Apr 8, 2020

Copy link
Copy Markdown
MemberAuthor

Testing Matrix Checklist

Generic

Interaction\CasesState Machine TransitionFirst Frame Sent ConditionInitial RequestNFrame RequestNFrame CancelRequest/Cancel Frames SerialisationReassembly SupportFragmentation SupportRefCnt Validation (during subscribe)Payload Size Validation (during subscribe)Terminates onComplete/onError/cancel
FireAndForgetMono
RequestResponseMono
RequestStreamFlux
RequestChannelFlux

Racing Cases

@OlegDokuka
OlegDokukaforce-pushed the bugfix/rework-rsocket-internals branch from a78a14d to e7efe6dCompareApril 8, 2020 12:49
@OlegDokuka
OlegDokukaforce-pushed the bugfix/rework-rsocket-internals branch 4 times, most recently from 5bd5fcb to 2d6c698CompareJuly 15, 2020 07:35
@OlegDokukaOlegDokuka removed the bug label Jul 15, 2020

@rstoyanchevrstoyanchev left a comment

Copy link
Copy Markdown
Contributor

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):

classRequesterResponderSupport {
privatefinalintmtu;
privatefinalintmaxFrameLength;
privatefinalintmaxInboundPayloadSize;
privatefinalPayloadDecoderpayloadDecoder;
privatefinalByteBufAllocatorallocator;
@NullablefinalStreamIdSupplierstreamIdSupplier;
finalIntObjectMap<FrameHandler> activeStreams;
privatefinalUnboundedProcessor<ByteBuf> sendProcessor;
publicRequesterResponderSupport(
intmtu,
intmaxFrameLength,
intmaxInboundPayloadSize,
PayloadDecoderpayloadDecoder,
ByteBufAllocatorallocator,
@NullableStreamIdSupplierstreamIdSupplier,
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 = newUnboundedProcessor<>();
}
publicintgetMtu() {
returnmtu;
}
publicintgetMaxFrameLength() {
returnmaxFrameLength;
}
publicintgetMaxInboundPayloadSize() {
returnmaxInboundPayloadSize;
}
publicPayloadDecodergetPayloadDecoder() {
returnpayloadDecoder;
}
publicByteBufAllocatorgetAllocator() {
returnallocator;
}
publicUnboundedProcessor<ByteBuf> getSendProcessor() {
returnsendProcessor;
}
publicsynchronizedintgetNextId() {
if (this.streamIdSupplier != null) {
returnthis.streamIdSupplier.nextStreamId(this.activeStreams);
}
else {
thrownewUnsupportedOperationException("Responder can not issue id");
}
}
publicsynchronizedintaddAndGetNextId(FrameHandlerframeHandler) {
if (this.streamIdSupplier != null) {
finalIntObjectMap<FrameHandler> activeStreams = this.activeStreams;
finalintstreamId = this.streamIdSupplier.nextStreamId(activeStreams);
activeStreams.put(streamId, frameHandler);
returnstreamId;
}
else {
thrownewUnsupportedOperationException("Responder can not issue id");
}
}
publicFrameHandlerget(intstreamId) {
returnthis.activeStreams.get(streamId);
}
publicbooleanremove(intstreamId, FrameHandlerframeHandler) {
returnthis.activeStreams.remove(streamId, frameHandler);
}
}

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

@OverridepublicFlux<Payload> requestStream(Payloadpayload) {
returnnewRequestStreamFlux(payload, this);
}

Likewise for responder Subscriber's:

RequestResponseSubscribersubscriber = newRequestResponseSubscriber(streamId, frame, this);

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

@OlegDokuka
OlegDokukaforce-pushed the bugfix/rework-rsocket-internals branch 2 times, most recently from f86226d to eaeb099CompareJuly 20, 2020 20:04
Comment threadrsocket-core/src/test/java/io/rsocket/core/StateMachineAssert.java Outdated
AtomicLongFieldUpdater<T> updater,
T instance,
ReassembledFramesHolder reassembledFramesHolder,
Subscription subscription,

Copy link
Copy Markdown
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
Copy Markdown
MemberAuthor

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 threadrsocket-core/src/main/java/io/rsocket/core/ReassemblyUtils.java Outdated
Comment threadrsocket-core/src/main/java/io/rsocket/core/RequestChannelRequesterFlux.java Outdated
Comment threadrsocket-core/src/main/java/io/rsocket/core/RequestChannelRequesterFlux.java Outdated
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
Copy Markdown
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
Copy Markdown
MemberAuthor

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

Comment threadrsocket-core/src/main/java/io/rsocket/core/StateUtils.java Outdated

@simonbaslesimonbasle left a comment

Copy link
Copy Markdown
Contributor

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 👍)

Comment threadrsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java Outdated
@OlegDokuka
OlegDokukaforce-pushed the bugfix/rework-rsocket-internals branch from eaeb099 to e3b7998CompareJuly 24, 2020 08:37
@OlegDokuka
OlegDokukaforce-pushed the bugfix/rework-rsocket-internals branch 3 times, most recently from 93f9d5c to 2854e04CompareJuly 29, 2020 09:29
@OlegDokuka

Copy link
Copy Markdown
MemberAuthor

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):

classRequesterResponderSupport {
privatefinalintmtu;
privatefinalintmaxFrameLength;
privatefinalintmaxInboundPayloadSize;
privatefinalPayloadDecoderpayloadDecoder;
privatefinalByteBufAllocatorallocator;
@NullablefinalStreamIdSupplierstreamIdSupplier;
finalIntObjectMap<FrameHandler> activeStreams;
privatefinalUnboundedProcessor<ByteBuf> sendProcessor;
publicRequesterResponderSupport(
intmtu,
intmaxFrameLength,
intmaxInboundPayloadSize,
PayloadDecoderpayloadDecoder,
ByteBufAllocatorallocator,
@NullableStreamIdSupplierstreamIdSupplier,
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 = newUnboundedProcessor<>();
}
publicintgetMtu() {
returnmtu;
}
publicintgetMaxFrameLength() {
returnmaxFrameLength;
}
publicintgetMaxInboundPayloadSize() {
returnmaxInboundPayloadSize;
}
publicPayloadDecodergetPayloadDecoder() {
returnpayloadDecoder;
}
publicByteBufAllocatorgetAllocator() {
returnallocator;
}
publicUnboundedProcessor<ByteBuf> getSendProcessor() {
returnsendProcessor;
}
publicsynchronizedintgetNextId() {
if (this.streamIdSupplier != null) {
returnthis.streamIdSupplier.nextStreamId(this.activeStreams);
}
else {
thrownewUnsupportedOperationException("Responder can not issue id");
}
}
publicsynchronizedintaddAndGetNextId(FrameHandlerframeHandler) {
if (this.streamIdSupplier != null) {
finalIntObjectMap<FrameHandler> activeStreams = this.activeStreams;
finalintstreamId = this.streamIdSupplier.nextStreamId(activeStreams);
activeStreams.put(streamId, frameHandler);
returnstreamId;
}
else {
thrownewUnsupportedOperationException("Responder can not issue id");
}
}
publicFrameHandlerget(intstreamId) {
returnthis.activeStreams.get(streamId);
}
publicbooleanremove(intstreamId, FrameHandlerframeHandler) {
returnthis.activeStreams.remove(streamId, frameHandler);
}
}

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

@OverridepublicFlux<Payload> requestStream(Payloadpayload) {
returnnewRequestStreamFlux(payload, this);
}

Likewise for responder Subscriber's:

RequestResponseSubscribersubscriber = newRequestResponseSubscriber(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
OlegDokukaforce-pushed the bugfix/rework-rsocket-internals branch 4 times, most recently from c531667 to 0b63bbfCompareJuly 29, 2020 20:50
@OlegDokuka

Copy link
Copy Markdown
MemberAuthor

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
OlegDokukaforce-pushed the bugfix/rework-rsocket-internals branch from 0b63bbf to 8be980eCompareJuly 29, 2020 21:10
@OlegDokukaOlegDokuka changed the title [Rework] RSocket Interactions Reimplementation Implements dedicated Request Publisher/Subscriber for each interaction typeJul 29, 2020
@OlegDokukaOlegDokuka changed the title Implements dedicated Request Publisher/Subscriber for each interaction typeImplements dedicated Publisher/Subscriber for each request typeJul 29, 2020
@OlegDokukaOlegDokuka changed the title Implements dedicated Publisher/Subscriber for each request typeImplements dedicated Publisher/Subscriber for each request type #761Jul 29, 2020
@OlegDokuka
OlegDokuka merged commit 5fea76d into masterJul 29, 2020
@OlegDokuka
OlegDokuka deleted the bugfix/rework-rsocket-internals branch July 29, 2020 21:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

6 participants

@OlegDokuka@yschimke@linux-china@rstoyanchev@jjeffcaii@simonbasle