Skip to content

Server memory overflow due to stream receivers retain payloads regardless requestN #887

Description

@mostroverkhov

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)

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions