Skip to content

Commit 2dcfe1c

Browse files
committed
RSocketRequester: fix concurrent modification of senders & receivers map on termination
fix non-deterministic errors order on termination Signed-off-by: Maksym Ostroverkhov <[email protected]>
1 parent 33259c2 commit 2dcfe1c

File tree

1 file changed

+94
-99
lines changed

1 file changed

+94
-99
lines changed

rsocket-core/src/main/java/io/rsocket/RSocketRequester.java

Lines changed: 94 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
4141
import java.util.function.Consumer;
4242
import java.util.function.LongConsumer;
43+
import java.util.function.Supplier;
4344
import javax.annotation.Nonnull;
4445
import javax.annotation.Nullable;
4546
import org.reactivestreams.Processor;
@@ -56,6 +57,7 @@ class RSocketRequester implements RSocket {
5657
private static final AtomicReferenceFieldUpdater<RSocketRequester, Throwable> TERMINATION_ERROR =
5758
AtomicReferenceFieldUpdater.newUpdater(
5859
RSocketRequester.class, Throwable.class, "terminationError");
60+
private static final Exception CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException();
5961

6062
private final DuplexConnection connection;
6163
private final PayloadDecoder payloadDecoder;
@@ -91,69 +93,25 @@ class RSocketRequester implements RSocket {
9193
// DO NOT Change the order here. The Send processor must be subscribed to before receiving
9294
this.sendProcessor = new UnboundedProcessor<>();
9395

94-
connection.onClose().doFinally(signalType -> terminate()).subscribe(null, errorConsumer);
9596
connection
96-
.send(sendProcessor)
97-
.doFinally(this::handleSendProcessorCancel)
98-
.subscribe(null, this::handleSendProcessorError);
97+
.onClose()
98+
.doFinally(signalType -> tryTerminateOnConnectionClose())
99+
.subscribe(null, errorConsumer);
100+
connection.send(sendProcessor).subscribe(null, this::handleSendProcessorError);
99101

100102
connection.receive().subscribe(this::handleIncomingFrames, errorConsumer);
101103

102104
if (keepAliveTickPeriod != 0 && keepAliveHandler != null) {
103105
KeepAliveSupport keepAliveSupport =
104106
new ClientKeepAliveSupport(allocator, keepAliveTickPeriod, keepAliveAckTimeout);
105107
this.keepAliveFramesAcceptor =
106-
keepAliveHandler.start(keepAliveSupport, sendProcessor::onNext, this::terminate);
108+
keepAliveHandler.start(
109+
keepAliveSupport, sendProcessor::onNext, this::tryTerminateOnKeepAlive);
107110
} else {
108111
keepAliveFramesAcceptor = null;
109112
}
110113
}
111114

112-
private void terminate(KeepAlive keepAlive) {
113-
String message =
114-
String.format("No keep-alive acks for %d ms", keepAlive.getTimeout().toMillis());
115-
ConnectionErrorException err = new ConnectionErrorException(message);
116-
setTerminationError(err);
117-
errorConsumer.accept(err);
118-
connection.dispose();
119-
}
120-
121-
private void handleSendProcessorError(Throwable t) {
122-
Throwable terminationError = this.terminationError;
123-
Throwable err = terminationError != null ? terminationError : t;
124-
receivers
125-
.values()
126-
.forEach(
127-
subscriber -> {
128-
try {
129-
subscriber.onError(err);
130-
} catch (Throwable e) {
131-
errorConsumer.accept(e);
132-
}
133-
});
134-
135-
senders.values().forEach(RateLimitableRequestPublisher::cancel);
136-
}
137-
138-
private void handleSendProcessorCancel(SignalType t) {
139-
if (SignalType.ON_ERROR == t) {
140-
return;
141-
}
142-
143-
receivers
144-
.values()
145-
.forEach(
146-
subscriber -> {
147-
try {
148-
subscriber.onError(new Throwable("closed connection"));
149-
} catch (Throwable e) {
150-
errorConsumer.accept(e);
151-
}
152-
});
153-
154-
senders.values().forEach(RateLimitableRequestPublisher::cancel);
155-
}
156-
157115
@Override
158116
public Mono<Void> fireAndForget(Payload payload) {
159117
return handleFireAndForget(payload);
@@ -263,8 +221,7 @@ public void acceptOnce(@Nonnull Subscription subscription) {
263221
if (s == SignalType.CANCEL) {
264222
sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId));
265223
}
266-
267-
receivers.remove(streamId);
224+
removeStreamReceiver(streamId);
268225
});
269226
}
270227

@@ -318,7 +275,7 @@ public void accept(long n) {
318275
sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId));
319276
}
320277
})
321-
.doFinally(s -> receivers.remove(streamId));
278+
.doFinally(s -> removeStreamReceiver(streamId));
322279
}
323280

324281
private Flux<Payload> handleChannel(Flux<Payload> request) {
@@ -419,14 +376,7 @@ protected void hookOnError(Throwable t) {
419376
sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId));
420377
}
421378
})
422-
.doFinally(
423-
s -> {
424-
receivers.remove(streamId);
425-
RateLimitableRequestPublisher sender = senders.remove(streamId);
426-
if (sender != null) {
427-
sender.cancel();
428-
}
429-
});
379+
.doFinally(s -> removeStreamReceiverAndSender(streamId));
430380
}
431381

432382
private Mono<Void> handleMetadataPush(Payload payload) {
@@ -472,40 +422,6 @@ private boolean contains(int streamId) {
472422
return receivers.containsKey(streamId);
473423
}
474424

475-
private void terminate() {
476-
setTerminationError(new ClosedChannelException());
477-
leaseHandler.dispose();
478-
try {
479-
receivers.values().forEach(this::cleanUpSubscriber);
480-
senders.values().forEach(this::cleanUpLimitableRequestPublisher);
481-
} finally {
482-
senders.clear();
483-
receivers.clear();
484-
sendProcessor.dispose();
485-
}
486-
}
487-
488-
private void setTerminationError(Throwable error) {
489-
TERMINATION_ERROR.compareAndSet(this, null, error);
490-
}
491-
492-
private synchronized void cleanUpLimitableRequestPublisher(
493-
RateLimitableRequestPublisher<?> limitableRequestPublisher) {
494-
try {
495-
limitableRequestPublisher.cancel();
496-
} catch (Throwable t) {
497-
errorConsumer.accept(t);
498-
}
499-
}
500-
501-
private synchronized void cleanUpSubscriber(Processor subscriber) {
502-
try {
503-
subscriber.onError(terminationError);
504-
} catch (Throwable t) {
505-
errorConsumer.accept(t);
506-
}
507-
}
508-
509425
private void handleIncomingFrames(ByteBuf frame) {
510426
try {
511427
int streamId = FrameHeaderFlyweight.streamId(frame);
@@ -525,10 +441,7 @@ private void handleIncomingFrames(ByteBuf frame) {
525441
private void handleStreamZero(FrameType type, ByteBuf frame) {
526442
switch (type) {
527443
case ERROR:
528-
RuntimeException error = Exceptions.from(frame);
529-
setTerminationError(error);
530-
errorConsumer.accept(error);
531-
connection.dispose();
444+
tryTerminateOnZeroError(frame);
532445
break;
533446
case LEASE:
534447
leaseHandler.receive(frame);
@@ -614,4 +527,86 @@ private void handleMissingResponseProcessor(int streamId, FrameType type, ByteBu
614527
// receiving a frame after a given stream has been cancelled/completed,
615528
// so ignore (cancellation is async so there is a race condition)
616529
}
530+
531+
private void tryTerminateOnKeepAlive(KeepAlive keepAlive) {
532+
tryTerminate(
533+
() ->
534+
new ConnectionErrorException(
535+
String.format("No keep-alive acks for %d ms", keepAlive.getTimeout().toMillis())));
536+
}
537+
538+
private void tryTerminateOnConnectionClose() {
539+
tryTerminate(() -> CLOSED_CHANNEL_EXCEPTION);
540+
}
541+
542+
private void tryTerminateOnZeroError(ByteBuf errorFrame) {
543+
tryTerminate(() -> Exceptions.from(errorFrame));
544+
}
545+
546+
private void tryTerminate(Supplier<Exception> errorSupplier) {
547+
if (terminationError == null) {
548+
Exception e = errorSupplier.get();
549+
if (TERMINATION_ERROR.compareAndSet(this, null, e)) {
550+
terminate(e);
551+
}
552+
}
553+
}
554+
555+
private void terminate(Exception e) {
556+
connection.dispose();
557+
leaseHandler.dispose();
558+
559+
synchronized (receivers) {
560+
receivers
561+
.values()
562+
.forEach(
563+
receiver -> {
564+
try {
565+
receiver.onError(e);
566+
} catch (Throwable t) {
567+
errorConsumer.accept(t);
568+
}
569+
});
570+
}
571+
synchronized (senders) {
572+
senders
573+
.values()
574+
.forEach(
575+
sender -> {
576+
try {
577+
sender.cancel();
578+
} catch (Throwable t) {
579+
errorConsumer.accept(t);
580+
}
581+
});
582+
}
583+
senders.clear();
584+
receivers.clear();
585+
sendProcessor.dispose();
586+
errorConsumer.accept(e);
587+
}
588+
589+
private void removeStreamReceiver(int streamId) {
590+
/*on termination senders & receivers are explicitly cleared to avoid removing from map while iterating over one
591+
of its views*/
592+
if (terminationError == null) {
593+
receivers.remove(streamId);
594+
}
595+
}
596+
597+
private void removeStreamReceiverAndSender(int streamId) {
598+
/*on termination senders & receivers are explicitly cleared to avoid removing from map while iterating over one
599+
of its views*/
600+
if (terminationError == null) {
601+
receivers.remove(streamId);
602+
RateLimitableRequestPublisher<?> sender = senders.remove(streamId);
603+
if (sender != null) {
604+
sender.cancel();
605+
}
606+
}
607+
}
608+
609+
private void handleSendProcessorError(Throwable t) {
610+
connection.dispose();
611+
}
617612
}

0 commit comments

Comments
 (0)