[Java] More carefully ensure sockets are closed in `NioPeerHandler`
authorMatt Corallo <git@bluematt.me>
Thu, 27 Jan 2022 18:39:52 +0000 (18:39 +0000)
committerMatt Corallo <git@bluematt.me>
Wed, 2 Feb 2022 19:30:30 +0000 (19:30 +0000)
There is no indication that the sockets are leaking in the handling
loop, but its good to be careful and ensure we always both close
the SocketChannel and cancel the key registration.

During connection, however, it appears we may leak a SocketChannel
if the connection times out, leaking a file descriptor at least
until the GC kicks in and cleans up after us.  Here we are more
careful.

src/main/java/org/ldk/batteries/NioPeerHandler.java

index e0dd83e9edc570b542716ae88caacb4e45b58940..2ab48d627a67c0405d985cd6fdaf78969b276ff1 100644 (file)
@@ -87,7 +87,7 @@ public class NioPeerHandler {
                                                        (peer.key.interestOps() | SelectionKey.OP_READ) & (~SelectionKey.OP_WRITE)));
                                        }
                     return written;
-                } catch (IOException e) {
+                } catch (IOException|CancelledKeyException ignored) {
                     // Most likely the socket is disconnected, let the background thread handle it.
                     return 0;
                 }
@@ -97,7 +97,7 @@ public class NioPeerHandler {
             public void disconnect_socket() {
                 try {
                     do_selector_action(() -> {
-                        peer.key.cancel();
+                        try { peer.key.cancel(); } catch (CancelledKeyException ignored) {}
                         peer.key.channel().close();
                     });
                 } catch (IOException ignored) { }
@@ -189,8 +189,8 @@ public class NioPeerHandler {
                             if (key.isValid() && (key.interestOps() & SelectionKey.OP_WRITE) != 0 && key.isWritable()) {
                                 Result_NonePeerHandleErrorZ res = this.peer_manager.write_buffer_space_avail(peer.descriptor);
                                 if (res instanceof Result_NonePeerHandleErrorZ.Result_NonePeerHandleErrorZ_Err) {
-                                    key.channel().close();
                                     key.cancel();
+                                    key.channel().close();
                                 }
                             }
                             if (key.isValid() && (key.interestOps() & SelectionKey.OP_READ) != 0 && key.isReadable()) {
@@ -199,6 +199,7 @@ public class NioPeerHandler {
                                 if (read == -1) {
                                     this.peer_manager.socket_disconnected(peer.descriptor);
                                     key.cancel();
+                                    key.channel().close(); // This may throw, we read -1 so the channel should already be closed, but do this to be safe
                                 } else if (read > 0) {
                                     ((Buffer)buf).flip();
                                     // This code is quite hot during initial network graph sync, so we go a ways out of
@@ -223,15 +224,15 @@ public class NioPeerHandler {
                                             key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
                                         }
                                     } else {
-                                        key.channel().close();
                                         key.cancel();
+                                        key.channel().close();
                                     }
                                     bindings.CResult_boolPeerHandleErrorZ_free(read_result_pointer);
                                 }
                             }
                         } catch (IOException ignored) {
-                            try { key.channel().close(); } catch (IOException ignored2) { }
                             key.cancel();
+                            try { key.channel().close(); } catch (IOException ignored2) { }
                             peer_manager.socket_disconnected(peer.descriptor);
                         }
                     } catch (CancelledKeyException e) {
@@ -265,13 +266,21 @@ public class NioPeerHandler {
      */
     public void connect(byte[] their_node_id, SocketAddress remote, int timeout_ms) throws IOException {
         SocketChannel chan = SocketChannel.open();
-        chan.configureBlocking(false);
-        Selector open_selector = Selector.open();
-        chan.register(open_selector, SelectionKey.OP_CONNECT);
-        if (!chan.connect(remote)) {
-            open_selector.select(timeout_ms);
+        boolean connected;
+        try {
+            chan.configureBlocking(false);
+            Selector open_selector = Selector.open();
+            chan.register(open_selector, SelectionKey.OP_CONNECT);
+            if (!chan.connect(remote)) {
+                open_selector.select(timeout_ms);
+            }
+            connected = chan.finishConnect();
+        } catch (IOException e) {
+            try { chan.close(); } catch (IOException _e) { }
+            throw e;
         }
-        if (!chan.finishConnect()) { // Note that this may throw its own IOException if we failed for another reason
+        if (!connected) {
+            try { chan.close(); } catch (IOException _e) { }
             throw new IOException("Timed out");
         }
         Peer peer = setup_socket(chan);