Skip to content

Commit 662b3d9

Browse files
authored
Merge pull request #817 from scalecube/add-connection-reset-handler
Made smarter error handler for aborted connection case
2 parents 28fbeb9 + 73ef688 commit 662b3d9

File tree

7 files changed

+101
-24
lines changed

7 files changed

+101
-24
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.scalecube.services.exceptions;
2+
3+
import java.util.regex.Pattern;
4+
5+
public class ConnectionClosedException extends InternalServiceException {
6+
7+
private static final Pattern GENERIC_CONNECTION_CLOSED =
8+
Pattern.compile(
9+
"^.*(?:connection.*(?:reset|closed|abort|broken)|broken.*pipe).*$",
10+
Pattern.CASE_INSENSITIVE);
11+
12+
public ConnectionClosedException() {
13+
super("Connection closed");
14+
}
15+
16+
public ConnectionClosedException(Throwable cause) {
17+
super(cause);
18+
}
19+
20+
public ConnectionClosedException(String message) {
21+
super(message);
22+
}
23+
24+
/**
25+
* Returns {@code true} if connection has been aborted on a tcp level by verifying error message
26+
* and matching it against predefined pattern.
27+
*
28+
* @param th error
29+
* @return {@code true} if connection has been aborted on a tcp level
30+
*/
31+
public static boolean isConnectionClosed(Throwable th) {
32+
if (th instanceof ConnectionClosedException) {
33+
return true;
34+
}
35+
36+
final String message = th != null ? th.getMessage() : null;
37+
38+
return message != null && GENERIC_CONNECTION_CLOSED.matcher(message).matches();
39+
}
40+
}

services-api/src/main/java/io/scalecube/services/exceptions/InternalServiceException.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,12 @@ public InternalServiceException(int errorCode, String message) {
1111
public InternalServiceException(Throwable cause) {
1212
super(ERROR_TYPE, cause);
1313
}
14+
15+
public InternalServiceException(String message) {
16+
super(ERROR_TYPE, message);
17+
}
18+
19+
public InternalServiceException(String message, Throwable cause) {
20+
super(ERROR_TYPE, message, cause);
21+
}
1422
}
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
package io.scalecube.services.exceptions;
22

3-
public class MessageCodecException extends RuntimeException {
3+
public class MessageCodecException extends InternalServiceException {
44

55
public MessageCodecException(String message, Throwable cause) {
66
super(message, cause);
77
}
8-
9-
@Override
10-
public synchronized Throwable fillInStackTrace() {
11-
return this;
12-
}
13-
14-
@Override
15-
public String toString() {
16-
return getClass().getSimpleName() + "{errorMessage=" + getMessage() + '}';
17-
}
188
}

services-api/src/main/java/io/scalecube/services/exceptions/ServiceException.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.scalecube.services.exceptions;
22

3+
import java.util.StringJoiner;
4+
35
public abstract class ServiceException extends RuntimeException {
46

57
private final int errorCode;
@@ -14,6 +16,11 @@ public ServiceException(int errorCode, Throwable cause) {
1416
this.errorCode = errorCode;
1517
}
1618

19+
public ServiceException(int errorCode, String message, Throwable cause) {
20+
super(message, cause);
21+
this.errorCode = errorCode;
22+
}
23+
1724
@Override
1825
public synchronized Throwable fillInStackTrace() {
1926
return this;
@@ -25,11 +32,9 @@ public int errorCode() {
2532

2633
@Override
2734
public String toString() {
28-
return getClass().getSimpleName()
29-
+ "{errorCode="
30-
+ errorCode
31-
+ ", errorMessage="
32-
+ getMessage()
33-
+ '}';
35+
return new StringJoiner(", ", getClass().getSimpleName() + "[", "]")
36+
.add("errorCode=" + errorCode)
37+
.add("errorMessage='" + getMessage() + "'")
38+
.toString();
3439
}
3540
}

services-transport-parent/services-transport-rsocket/src/main/java/io/scalecube/services/transport/rsocket/RSocketClientChannel.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44
import io.rsocket.RSocket;
55
import io.rsocket.util.ByteBufPayload;
66
import io.scalecube.services.api.ServiceMessage;
7+
import io.scalecube.services.exceptions.ConnectionClosedException;
78
import io.scalecube.services.transport.api.ClientChannel;
89
import io.scalecube.services.transport.api.ServiceMessageCodec;
910
import java.lang.reflect.Type;
1011
import org.reactivestreams.Publisher;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
1114
import reactor.core.publisher.Flux;
1215
import reactor.core.publisher.Mono;
16+
import reactor.netty.channel.AbortedException;
1317

1418
public class RSocketClientChannel implements ClientChannel {
1519

20+
private static final Logger LOGGER = LoggerFactory.getLogger(RSocketClientChannel.class);
21+
1622
private final Mono<RSocket> rsocket;
1723
private final ServiceMessageCodec messageCodec;
1824

@@ -26,15 +32,17 @@ public Mono<ServiceMessage> requestResponse(ServiceMessage message, Type respons
2632
return rsocket
2733
.flatMap(rsocket -> rsocket.requestResponse(toPayload(message)))
2834
.map(this::toMessage)
29-
.map(msg -> ServiceMessageCodec.decodeData(msg, responseType));
35+
.map(msg -> ServiceMessageCodec.decodeData(msg, responseType))
36+
.onErrorMap(RSocketClientChannel::mapConnectionAborted);
3037
}
3138

3239
@Override
3340
public Flux<ServiceMessage> requestStream(ServiceMessage message, Type responseType) {
3441
return rsocket
3542
.flatMapMany(rsocket -> rsocket.requestStream(toPayload(message)))
3643
.map(this::toMessage)
37-
.map(msg -> ServiceMessageCodec.decodeData(msg, responseType));
44+
.map(msg -> ServiceMessageCodec.decodeData(msg, responseType))
45+
.onErrorMap(RSocketClientChannel::mapConnectionAborted);
3846
}
3947

4048
@Override
@@ -43,7 +51,8 @@ public Flux<ServiceMessage> requestChannel(
4351
return rsocket
4452
.flatMapMany(rsocket -> rsocket.requestChannel(Flux.from(publisher).map(this::toPayload)))
4553
.map(this::toMessage)
46-
.map(msg -> ServiceMessageCodec.decodeData(msg, responseType));
54+
.map(msg -> ServiceMessageCodec.decodeData(msg, responseType))
55+
.onErrorMap(RSocketClientChannel::mapConnectionAborted);
4756
}
4857

4958
private Payload toPayload(ServiceMessage request) {
@@ -57,4 +66,10 @@ private ServiceMessage toMessage(Payload payload) {
5766
payload.release();
5867
}
5968
}
69+
70+
private static Throwable mapConnectionAborted(Throwable t) {
71+
return AbortedException.isConnectionReset(t) || ConnectionClosedException.isConnectionClosed(t)
72+
? new ConnectionClosedException(t)
73+
: t;
74+
}
6075
}

services-transport-parent/services-transport-rsocket/src/main/java/io/scalecube/services/transport/rsocket/RSocketServiceTransport.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.netty.util.concurrent.Future;
99
import io.scalecube.services.auth.Authenticator;
1010
import io.scalecube.services.auth.CredentialsSupplier;
11+
import io.scalecube.services.exceptions.ConnectionClosedException;
1112
import io.scalecube.services.methods.ServiceMethodRegistry;
1213
import io.scalecube.services.transport.api.ClientTransport;
1314
import io.scalecube.services.transport.api.DataCodec;
@@ -18,13 +19,31 @@
1819
import java.util.StringJoiner;
1920
import java.util.concurrent.ThreadFactory;
2021
import java.util.function.Function;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
2124
import reactor.core.publisher.Flux;
25+
import reactor.core.publisher.Hooks;
2226
import reactor.core.publisher.Mono;
2327
import reactor.netty.FutureMono;
28+
import reactor.netty.channel.AbortedException;
2429
import reactor.netty.resources.LoopResources;
2530

2631
public class RSocketServiceTransport implements ServiceTransport {
2732

33+
public static final Logger LOGGER = LoggerFactory.getLogger(RSocketServiceTransport.class);
34+
35+
static {
36+
Hooks.onErrorDropped(
37+
t -> {
38+
if (AbortedException.isConnectionReset(t)
39+
|| ConnectionClosedException.isConnectionClosed(t)) {
40+
if (LOGGER.isDebugEnabled()) {
41+
LOGGER.debug("Connection aborted: {}", t.toString());
42+
}
43+
}
44+
});
45+
}
46+
2847
private int numOfWorkers = Runtime.getRuntime().availableProcessors();
2948

3049
private HeadersCodec headersCodec = HeadersCodec.DEFAULT_INSTANCE;

services/src/test/java/io/scalecube/services/transport/rsocket/RSocketServiceTransportTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import io.scalecube.services.api.ServiceMessage;
1111
import io.scalecube.services.discovery.ScalecubeServiceDiscovery;
1212
import io.scalecube.services.discovery.api.ServiceDiscoveryEvent;
13+
import io.scalecube.services.exceptions.ConnectionClosedException;
1314
import io.scalecube.services.sut.QuoteService;
1415
import io.scalecube.services.sut.SimpleQuoteService;
15-
import java.nio.channels.ClosedChannelException;
1616
import java.time.Duration;
1717
import java.util.Optional;
1818
import java.util.concurrent.CountDownLatch;
@@ -94,7 +94,7 @@ public void test_remote_node_died_mono_never() throws Exception {
9494
TimeUnit.MILLISECONDS.sleep(100);
9595

9696
assertEquals(0, latch1.getCount());
97-
assertEquals(ClosedChannelException.class, exceptionHolder.get().getClass());
97+
assertEquals(ConnectionClosedException.class, exceptionHolder.get().getClass());
9898
assertTrue(sub1.get().isDisposed());
9999
}
100100

@@ -122,7 +122,7 @@ public void test_remote_node_died_many_never() throws Exception {
122122
TimeUnit.MILLISECONDS.sleep(100);
123123

124124
assertEquals(0, latch1.getCount());
125-
assertEquals(ClosedChannelException.class, exceptionHolder.get().getClass());
125+
assertEquals(ConnectionClosedException.class, exceptionHolder.get().getClass());
126126
assertTrue(sub1.get().isDisposed());
127127
}
128128

@@ -154,7 +154,7 @@ public void test_remote_node_died_many_then_never() throws Exception {
154154
TimeUnit.MILLISECONDS.sleep(100);
155155

156156
assertEquals(0, latch1.getCount());
157-
assertEquals(ClosedChannelException.class, exceptionHolder.get().getClass());
157+
assertEquals(ConnectionClosedException.class, exceptionHolder.get().getClass());
158158
assertTrue(sub1.get().isDisposed());
159159
}
160160
}

0 commit comments

Comments
 (0)