View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix;
19  
20  import java.io.IOException;
21  import java.nio.channels.AsynchronousCloseException;
22  import java.nio.channels.ClosedByInterruptException;
23  import java.nio.channels.ClosedChannelException;
24  import java.nio.channels.NotYetBoundException;
25  import java.nio.channels.NotYetConnectedException;
26  import java.nio.channels.spi.AbstractInterruptibleChannel;
27  import java.util.Objects;
28  
29  /**
30   * Helper methods when working with {@link AbstractInterruptibleChannel} subclasses.
31   *
32   * @author Christian Kohlschütter
33   */
34  final class InterruptibleChannelUtil {
35    /**
36     * Reference to the protected {@code AbstractInterruptibleChannel#end(boolean)} method.
37     */
38    @FunctionalInterface
39    interface EndMethod {
40      void end(boolean completed) throws AsynchronousCloseException;
41    }
42  
43    /**
44     * Wrapper method that calls {@code AbstractInterruptibleChannel#end(boolean)}, making sure the
45     * socket is closed and the {@link Thread#interrupted()} state is set correctly upon error.
46     *
47     * @param channel The channel.
48     * @param end The reference to the protected {@code AbstractInterruptibleChannel#end(boolean)}
49     *          method.
50     * @param complete {@code true} if the block started with {@code begin} succeeded without an
51     *          exception.
52     * @param exception An optional exception that was caught in the try-catch-finally block.
53     * @throws AsynchronousCloseException on error.
54     */
55    static void endInterruptable(AFSomeSocketChannel channel, EndMethod end, boolean complete,
56        Exception exception) throws AsynchronousCloseException {
57      if (!complete) {
58        if (exception instanceof ClosedChannelException) {
59          // we already have caught a valid exception; we don't need to throw one from within "end"
60          complete = true;
61        }
62      }
63      try {
64        end.end(complete);
65      } catch (AsynchronousCloseException e) {
66        throw closeAndThrow(channel, e);
67      }
68    }
69  
70    private static <T extends Exception> T closeAndThrow(AFSomeSocketChannel channel, T exc) {
71      Objects.requireNonNull(exc);
72      if (channel.isOpen()) {
73        try {
74          channel.close();
75        } catch (IOException e2) {
76          exc.addSuppressed(e2);
77        }
78      }
79      return exc;
80    }
81  
82    static IOException ioExceptionOrThrowRuntimeException(Exception exception) {
83      if (exception instanceof IOException) {
84        return (IOException) exception;
85      } else if (exception instanceof RuntimeException) {
86        throw (RuntimeException) exception;
87      } else {
88        throw new IllegalStateException(exception);
89      }
90    }
91  
92    /**
93     * Makes sure that upon an exception that is documented to have the channel be closed the channel
94     * is indeed closed before throwing that exception. If the exception is also documented to have
95     * the "Thread interrupted" state be set, make sure that this state is actually set as well.
96     *
97     * @param channel The channel to work with.
98     * @param e The exception
99     * @return The exception.
100    */
101   @SuppressWarnings("PMD.CognitiveComplexity")
102   static Exception handleException(AFSomeSocketChannel channel, IOException e) {
103     if (e instanceof NotConnectedSocketException) {
104       return (NotYetConnectedException) new NotYetConnectedException().initCause(e);
105     } else if (e instanceof NotBoundSocketException) {
106       return (NotYetBoundException) new NotYetBoundException().initCause(e);
107     }
108 
109     if (e instanceof InvalidArgumentSocketException) {
110       if (channel instanceof AFServerSocketChannel<?>) {
111         AFServerSocketChannel<?> sc = (AFServerSocketChannel<?>) channel;
112         if (!sc.socket().isBound()) {
113           return (NotYetBoundException) new NotYetBoundException().initCause(e);
114         }
115       } else if (channel instanceof AFSocketChannel<?>) {
116         if (!((AFSocketChannel<?>) channel).socket().isConnected()) {
117           return (NotYetConnectedException) new NotYetConnectedException().initCause(e);
118         }
119       }
120     }
121 
122     if (e instanceof SocketClosedException || e instanceof ClosedChannelException
123         || e instanceof BrokenPipeSocketException) {
124       Thread t = Thread.currentThread();
125 
126       if (e instanceof SocketClosedByInterruptException
127           || e instanceof ClosedByInterruptException) {
128         if (!t.isInterrupted()) {
129           t.interrupt();
130         }
131       }
132 
133       if (!(e instanceof ClosedChannelException)) {
134         // Make sure the caught exception is transformed into the expected exception
135         if (t.isInterrupted()) {
136           e = (ClosedByInterruptException) new ClosedByInterruptException().initCause(e);
137         } else if (e instanceof BrokenPipeSocketException) {
138           e = (AsynchronousCloseException) new AsynchronousCloseException().initCause(e);
139         } else {
140           e = (ClosedChannelException) new ClosedChannelException().initCause(e);
141         }
142       }
143 
144       return closeAndThrow(channel, e);
145     } else {
146       return e;
147     }
148   }
149 }