diff --git a/src/main/java/hudson/remoting/Channel.java b/src/main/java/hudson/remoting/Channel.java index 4eb807e53..702006f4f 100644 --- a/src/main/java/hudson/remoting/Channel.java +++ b/src/main/java/hudson/remoting/Channel.java @@ -40,6 +40,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; import java.io.Closeable; +import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -49,6 +50,7 @@ import java.io.Serializable; import java.lang.ref.WeakReference; import java.net.URL; +import java.nio.channels.ClosedChannelException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Date; @@ -66,6 +68,7 @@ import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import java.util.stream.Stream; /** * Represents a communication channel to the remote peer. @@ -1908,6 +1911,25 @@ public static void dumpDiagnosticsForAll(@NonNull PrintWriter w) { } } + /** + * Checks whether an exception seems to be related to a closed channel. + * Detects {@link ChannelClosedException}, {@link ClosedChannelException}, and {@link EOFException} + * anywhere in the exception or suppressed exception chain. + */ + public static boolean isClosedChannelException(@CheckForNull Throwable t) { + if (t instanceof ClosedChannelException) { + return true; + } else if (t instanceof ChannelClosedException) { + return true; + } else if (t instanceof EOFException) { + return true; + } else if (t == null) { + return false; + } else { + return isClosedChannelException(t.getCause()) || Stream.of(t.getSuppressed()).anyMatch(Channel::isClosedChannelException); + } + } + /** * Notification that {@link Command#readFrom} has succeeded. * @param cmd the resulting command