Skip to content

Commit c9f99c9

Browse files
committed
plumb disconnect error
1 parent 5bde922 commit c9f99c9

27 files changed

+491
-151
lines changed

core/src/main/java/io/grpc/internal/DelayedClientTransport.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ public ListenableFuture<SocketStats> getStats() {
201201
}
202202

203203
/**
204-
* Prevents creating any new streams. Buffered streams are not failed and may still proceed
205-
* when {@link #reprocess} is called. The delayed transport will be terminated when there is no
204+
* Prevents creating any new streams. Buffered streams are not failed and may still proceed
205+
* when {@link #reprocess} is called. The delayed transport will be terminated when there is no
206206
* more buffered streams.
207207
*/
208208
@Override
@@ -215,7 +215,7 @@ public final void shutdown(final Status status) {
215215
syncContext.executeLater(new Runnable() {
216216
@Override
217217
public void run() {
218-
listener.transportShutdown(status);
218+
listener.transportShutdown(status, SimpleDisconnectError.SUBCHANNEL_SHUTDOWN);
219219
}
220220
});
221221
if (!hasPendingStreams() && reportTransportTerminated != null) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2025 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.internal;
18+
19+
import javax.annotation.concurrent.Immutable;
20+
21+
/**
22+
* Represents the reason for a subchannel disconnection.
23+
* Implementations are either the SimpleDisconnectError enum or the GoAwayDisconnectError class for
24+
* dynamic ones.
25+
*/
26+
@Immutable
27+
public interface DisconnectError {
28+
/**
29+
* Returns the string representation suitable for use as an error tag.
30+
*
31+
* @return The formatted error tag string.
32+
*/
33+
String toErrorString();
34+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2025 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.internal;
18+
19+
20+
import javax.annotation.concurrent.Immutable;
21+
22+
/**
23+
* Represents a dynamic disconnection due to an HTTP/2 GOAWAY frame.
24+
* This class is immutable and holds the specific error code from the frame.
25+
*/
26+
@Immutable
27+
public final class GoAwayDisconnectError implements DisconnectError {
28+
private static final String ERROR_TAG = "GOAWAY";
29+
private final GrpcUtil.Http2Error errorCode;
30+
31+
/**
32+
* Creates a GoAway reason.
33+
*
34+
* @param errorCode The specific, non-null HTTP/2 error code (e.g., "NO_ERROR").
35+
*/
36+
public GoAwayDisconnectError(GrpcUtil.Http2Error errorCode) {
37+
if (errorCode == null) {
38+
throw new NullPointerException("Http2Error cannot be null for GOAWAY");
39+
}
40+
this.errorCode = errorCode;
41+
}
42+
43+
@Override
44+
public String toErrorString() {
45+
return ERROR_TAG + " " + errorCode.name();
46+
}
47+
48+
@Override
49+
public boolean equals(Object o) {
50+
if (this == o) {
51+
return true;
52+
}
53+
if (o == null || getClass() != o.getClass()) {
54+
return false;
55+
}
56+
GoAwayDisconnectError goAwayDisconnectError = (GoAwayDisconnectError) o;
57+
return errorCode == goAwayDisconnectError.errorCode;
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
return errorCode.hashCode();
63+
}
64+
}

core/src/main/java/io/grpc/internal/InternalSubchannel.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ public void run() {
326326
}
327327

328328
/**
329-
* Immediately attempt to reconnect if the current state is TRANSIENT_FAILURE. Otherwise this
329+
* Immediately attempt to reconnect if the current state is TRANSIENT_FAILURE. Otherwise, this
330330
* method has no effect.
331331
*/
332332
void resetConnectBackoff() {
@@ -620,7 +620,7 @@ public void transportInUse(boolean inUse) {
620620
}
621621

622622
@Override
623-
public void transportShutdown(final Status s) {
623+
public void transportShutdown(final Status s, final DisconnectError disconnectError) {
624624
channelLogger.log(
625625
ChannelLogLevel.INFO, "{0} SHUTDOWN with {1}", transport.getLogId(), printShortStatus(s));
626626
shutdownInitiated = true;
@@ -639,8 +639,7 @@ public void run() {
639639
NameResolver.ATTR_BACKEND_SERVICE),
640640
/* locality= */ getAttributeOrDefault(addressIndex.getCurrentEagAttributes(),
641641
EquivalentAddressGroup.ATTR_LOCALITY_NAME),
642-
/* disconnectError= */ SubchannelMetrics.DisconnectError.UNKNOWN
643-
.getErrorString(null),
642+
/* disconnectError= */ disconnectError.toErrorString(),
644643
/* securityLevel= */ extractSecurityLevel(addressIndex.getCurrentEagAttributes()
645644
.get(GrpcAttributes.ATTR_SECURITY_LEVEL)));
646645
} else if (pendingTransport == transport) {

core/src/main/java/io/grpc/internal/KeepAliveManager.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,9 @@ public interface KeepAlivePinger {
262262
* Default client side {@link KeepAlivePinger}.
263263
*/
264264
public static final class ClientKeepAlivePinger implements KeepAlivePinger {
265-
private final ConnectionClientTransport transport;
265+
private final ManagedClientDisconnectTransport transport;
266266

267-
public ClientKeepAlivePinger(ConnectionClientTransport transport) {
267+
public ClientKeepAlivePinger(ManagedClientDisconnectTransport transport) {
268268
this.transport = transport;
269269
}
270270

@@ -277,15 +277,17 @@ public void onSuccess(long roundTripTimeNanos) {}
277277
@Override
278278
public void onFailure(Status cause) {
279279
transport.shutdownNow(Status.UNAVAILABLE.withDescription(
280-
"Keepalive failed. The connection is likely gone"));
280+
"Keepalive failed. The connection is likely gone"),
281+
SimpleDisconnectError.CONNECTION_TIMED_OUT);
281282
}
282283
}, MoreExecutors.directExecutor());
283284
}
284285

285286
@Override
286287
public void onPingTimeout() {
287288
transport.shutdownNow(Status.UNAVAILABLE.withDescription(
288-
"Keepalive failed. The connection is likely gone"));
289+
"Keepalive failed. The connection is likely gone"),
290+
SimpleDisconnectError.CONNECTION_TIMED_OUT);
289291
}
290292
}
291293
}

core/src/main/java/io/grpc/internal/ManagedChannelImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2056,7 +2056,7 @@ public String toString() {
20562056
*/
20572057
private final class DelayedTransportListener implements ManagedClientTransport.Listener {
20582058
@Override
2059-
public void transportShutdown(Status s) {
2059+
public void transportShutdown(Status s, DisconnectError e) {
20602060
checkState(shutdown.get(), "Channel must have been shut down");
20612061
}
20622062

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2016 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.internal;
18+
19+
import io.grpc.Status;
20+
import javax.annotation.concurrent.ThreadSafe;
21+
22+
/**
23+
* A {@link ClientTransport} that has life-cycle management.
24+
*
25+
*/
26+
@ThreadSafe
27+
public interface ManagedClientDisconnectTransport extends ClientTransport {
28+
29+
/**
30+
* Initiates a forceful shutdown in which preexisting and new calls are closed. Existing calls
31+
* should be closed with the provided {@code reason}.
32+
*/
33+
void shutdownNow(Status reason, DisconnectError disconnectError);
34+
}

core/src/main/java/io/grpc/internal/ManagedClientTransport.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ interface Listener {
7777
* <p>This is called exactly once, and must be called prior to {@link #transportTerminated}.
7878
*
7979
* @param s the reason for the shutdown.
80+
* @param e the disconnect error.
8081
*/
81-
void transportShutdown(Status s);
82+
void transportShutdown(Status s, DisconnectError e);
8283

8384
/**
8485
* The transport completed shutting down. All resources have been released. All streams have

core/src/main/java/io/grpc/internal/OobChannel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public ClientStream newStream(MethodDescriptor<?, ?> method,
117117
this.channelz = Preconditions.checkNotNull(channelz);
118118
this.delayedTransport.start(new ManagedClientTransport.Listener() {
119119
@Override
120-
public void transportShutdown(Status s) {
120+
public void transportShutdown(Status s, DisconnectError e) {
121121
// Don't care
122122
}
123123

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.internal;
18+
19+
import javax.annotation.concurrent.Immutable;
20+
21+
/**
22+
* Represents a fixed, static reason for disconnection.
23+
*/
24+
@Immutable
25+
public enum SimpleDisconnectError implements DisconnectError {
26+
/**
27+
* The subchannel was shut down for various reasons like parent channel shutdown,
28+
* idleness, or load balancing policy changes.
29+
*/
30+
SUBCHANNEL_SHUTDOWN("subchannel shutdown"),
31+
32+
/**
33+
* Connection was reset (e.g., ECONNRESET, WSAECONNERESET).
34+
*/
35+
CONNECTION_RESET("connection reset"),
36+
37+
/**
38+
* Connection timed out (e.g., ETIMEDOUT, WSAETIMEDOUT), including closures
39+
* from gRPC keepalives.
40+
*/
41+
CONNECTION_TIMED_OUT("connection timed out"),
42+
43+
/**
44+
* Connection was aborted (e.g., ECONNABORTED, WSAECONNABORTED).
45+
*/
46+
CONNECTION_ABORTED("connection aborted"),
47+
48+
/**
49+
* Any socket error not covered by other specific disconnect errors.
50+
*/
51+
SOCKET_ERROR("socket error"),
52+
53+
/**
54+
* A catch-all for any other unclassified reason.
55+
*/
56+
UNKNOWN("unknown");
57+
58+
private final String errorTag;
59+
60+
SimpleDisconnectError(String errorTag) {
61+
this.errorTag = errorTag;
62+
}
63+
64+
@Override
65+
public String toErrorString() {
66+
return this.errorTag;
67+
}
68+
}

0 commit comments

Comments
 (0)