Rogue clients may not respect requestN from server, send frames firehose which are queued by receivers - UnicastProcessors, backed by unbounded queue.
Affects server Responder channel; also Requester stream & channel - if server sends requests to client.
Reproducible with test below, It roughly models system that processes batches of messages asynchronously, calls new batch with requestN once current one is completed
@TestvoidserverOom() {
EmitterProcessor<Void> completed = EmitterProcessor.create();
TestServerTransporttestTransport = newTestServerTransport();
Closeableserver =
RSocketServer.create()
.acceptor(
(setup, sendingSocket) ->
Mono.just(
newRSocket() {
@OverridepublicFlux<Payload> requestChannel(Publisher<Payload> payloads) {
Flux.from(payloads)
.doFinally(s -> completed.onComplete())
.subscribe(newBatchProcessingSubscriber());
returncompleted.cast(Payload.class);
}
}))
.bind(testTransport)
.block();
byte[] bytes = bytes(1_000_000);
TestDuplexConnectiontestDuplexConnection = testTransport.connect();
/*setup*/testDuplexConnection.addToReceivedBuffer(
SetupFrameCodec.encode(
ByteBufAllocator.DEFAULT,
false,
1111111,
1111111,
Unpooled.EMPTY_BUFFER,
"application/binary",
"application/binary",
EmptyPayload.INSTANCE));
/*request-channel which sends many frames regardless received requestN*/testDuplexConnection.addToReceivedBuffer(
RequestChannelFrameCodec.encode(
ByteBufAllocator.DEFAULT,
1,
false,
false,
1111111,
Unpooled.EMPTY_BUFFER,
Unpooled.EMPTY_BUFFER));
intframesCount = 8000;
for (inti = 0; i < framesCount; i++) {
booleancomplete = i == framesCount - 1;
testDuplexConnection.addToReceivedBuffer(
PayloadFrameCodec.encode(
ByteBufAllocator.DEFAULT,
1,
false,
complete,
true,
Unpooled.EMPTY_BUFFER,
Unpooled.wrappedBuffer(bytes)));
}
completed
.mergeWith(testDuplexConnection.onClose())
.as(StepVerifier::create)
.expectComplete()
.verify(Duration.ofSeconds(30));
}
privatestaticfinalRandomrandom = newRandom();
staticbyte[] bytes(intsize) {
byte[] data = newbyte[size];
random.nextBytes(data);
returndata;
}
privatestaticclassBatchProcessingSubscriberimplementsSubscriber<Payload>, Runnable {
staticfinalintBATCH_SIZE = 8;
finalScheduledExecutorServiceexecutorService = Executors.newSingleThreadScheduledExecutor();
volatileSubscriptions;
intreceived;
@OverridepublicvoidonSubscribe(Subscriptions) {
this.s = s;
/*request initial batch*/s.request(BATCH_SIZE);
}
@OverridepublicvoidonNext(Payloadpayload) {
/*emulate batch processing: request next batch once current is finished*/payload.release();
if (++received == BATCH_SIZE) {
received = 0;
executorService.schedule(this, 1_000, TimeUnit.MILLISECONDS);
}
}
@OverridepublicvoidonError(Throwablet) {}
@OverridepublicvoidonComplete() {}
@Overridepublicvoidrun() {
s.request(BATCH_SIZE);
}
}Test fails with error
Caused by: java.lang.OutOfMemoryError: Direct buffer memory at
java.base/java.nio.Bits.reserveMemory(Bits.java:175) at
java.base/java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:118) at
java.base/java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:317) at
io.rsocket.frame.decoder.DefaultPayloadDecoder.apply(DefaultPayloadDecoder.java:55) at
io.rsocket.frame.decoder.DefaultPayloadDecoder.apply(DefaultPayloadDecoder.java:18) at
io.rsocket.core.RSocketResponder.handleFrame(RSocketResponder.java:332) at
reactor.core.publisher.LambdaSubscriber.onNext(LambdaSubscriber.java:160) at
reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:242) at
reactor.core.publisher.FluxGroupBy$UnicastGroupedFlux.drainRegular(FluxGroupBy.java:554) at
reactor.core.publisher.FluxGroupBy$UnicastGroupedFlux.drain(FluxGroupBy.java:630) at
reactor.core.publisher.FluxGroupBy$UnicastGroupedFlux.onNext(FluxGroupBy.java:670) at
reactor.core.publisher.FluxGroupBy$GroupByMain.onNext(FluxGroupBy.java:205) at
reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:112) at
reactor.core.publisher.DirectProcessor$DirectInner.onNext(DirectProcessor.java:333) at
reactor.core.publisher.DirectProcessor.onNext(DirectProcessor.java:142) at
reactor.core.publisher.FluxCreate$IgnoreSink.next(FluxCreate.java:618) at
reactor.core.publisher.FluxCreate$SerializedSink.next(FluxCreate.java:153) at
io.rsocket.test.util.TestDuplexConnection.addToReceivedBuffer(TestDuplexConnection.java:132) at
io.rsocket.ServerOomTest.serverOom(ServerOomTest.java:86)
Rogue clients may not respect requestN from server, send frames firehose which are queued by receivers - UnicastProcessors, backed by unbounded queue.
Affects server
Responderchannel; alsoRequesterstream & channel - if server sends requests to client.Reproducible with test below, It roughly models system that processes batches of messages asynchronously, calls new batch with requestN once current one is completed
Test fails with error