11package org .ldk .batteries ;
22
3+ import org .ldk .impl .bindings ;
34import org .ldk .structs .*;
45
56import java .io .IOException ;
7+ import java .lang .reflect .Field ;
68import java .util .LinkedList ;
79import java .net .SocketAddress ;
810import java .net .StandardSocketOptions ;
1719public class NioPeerHandler {
1820 private static class Peer {
1921 SocketDescriptor descriptor ;
22+ long descriptor_raw_pointer ;
2023 SelectionKey key ;
2124 }
2225
@@ -45,6 +48,19 @@ private void do_selector_action(SelectorCall meth) throws IOException {
4548 }
4649 }
4750
51+ static private Field CommonBasePointer ;
52+ static {
53+ try {
54+ Class c = PeerManager .class .getSuperclass ();
55+ CommonBasePointer = c .getDeclaredField ("ptr" );
56+ CommonBasePointer .setAccessible (true );
57+ long _dummy_check = CommonBasePointer .getLong (Ping .of ((short )0 , (short )0 ));
58+ } catch (NoSuchFieldException | IllegalAccessException e ) {
59+ throw new IllegalArgumentException (
60+ "We currently use reflection to access protected fields as Java has no reasonable access controls" , e );
61+ }
62+ }
63+
4864 private Peer setup_socket (SocketChannel chan ) throws IOException {
4965 chan .configureBlocking (false );
5066 // Lightning tends to send a number of small messages back and forth between peers quickly, which Nagle is
@@ -61,13 +77,14 @@ private Peer setup_socket(SocketChannel chan) throws IOException {
6177 @ Override
6278 public long send_data (byte [] data , boolean resume_read ) {
6379 try {
64- if (resume_read ) {
65- do_selector_action (() -> peer .key .interestOps (peer .key .interestOps () | SelectionKey .OP_READ ));
66- }
6780 long written = chan .write (ByteBuffer .wrap (data ));
6881 if (written != data .length ) {
69- do_selector_action (() -> peer .key .interestOps (peer .key .interestOps () | SelectionKey .OP_WRITE ));
70- }
82+ do_selector_action (() -> peer .key .interestOps (
83+ (peer .key .interestOps () | SelectionKey .OP_WRITE ) & (~SelectionKey .OP_READ )));
84+ } else if (resume_read ) {
85+ do_selector_action (() -> peer .key .interestOps (
86+ (peer .key .interestOps () | SelectionKey .OP_READ ) & (~SelectionKey .OP_WRITE )));
87+ }
7188 return written ;
7289 } catch (IOException e ) {
7390 // Most likely the socket is disconnected, let the background thread handle it.
@@ -88,6 +105,12 @@ public void disconnect_socket() {
88105 @ Override public long hash () { return our_id ; }
89106 });
90107 peer .descriptor = descriptor ;
108+ try {
109+ peer .descriptor_raw_pointer = CommonBasePointer .getLong (descriptor );
110+ } catch (IllegalAccessException e ) {
111+ throw new IllegalArgumentException (
112+ "We currently use reflection to access protected fields as Java has no reasonable access controls" , e );
113+ }
91114 return peer ;
92115 }
93116
@@ -107,7 +130,16 @@ public NioPeerHandler(PeerManager manager) throws IOException {
107130 this .peer_manager = manager ;
108131 this .selector = Selector .open ();
109132 io_thread = new Thread (() -> {
110- ByteBuffer buf = ByteBuffer .allocate (8192 );
133+ int BUF_SZ = 16 * 1024 ;
134+ byte [] max_buf_byte_object = new byte [BUF_SZ ];
135+ ByteBuffer buf = ByteBuffer .allocate (BUF_SZ );
136+
137+ long peer_manager_raw_pointer ;
138+ try {
139+ peer_manager_raw_pointer = CommonBasePointer .getLong (this .peer_manager );
140+ } catch (IllegalAccessException e ) {
141+ throw new RuntimeException (e );
142+ }
111143 while (true ) {
112144 try {
113145 if (IS_ANDROID ) {
@@ -167,17 +199,32 @@ public NioPeerHandler(PeerManager manager) throws IOException {
167199 key .cancel ();
168200 } else if (read > 0 ) {
169201 ((Buffer )buf ).flip ();
170- byte [] read_bytes = new byte [read ];
202+ // This code is quite hot during initial network graph sync, so we go a ways out of
203+ // our way to avoid object allocations that'll make the GC sweat later -
204+ // * when we're hot, we'll likely often be reading the full buffer, so we keep
205+ // around a full-buffer-sized byte array to reuse across reads,
206+ // * We use the manual memory management call logic directly in bindings instead of
207+ // the nice "human-readable" wrappers. This puts us at risk of memory issues,
208+ // so we indirectly ensure compile fails if the types change by writing the
209+ // "human-readable" form of the same code in the dummy function below.
210+ byte [] read_bytes ;
211+ if (read == BUF_SZ ) {
212+ read_bytes = max_buf_byte_object ;
213+ } else {
214+ read_bytes = new byte [read ];
215+ }
171216 buf .get (read_bytes , 0 , read );
172- Result_boolPeerHandleErrorZ res = this .peer_manager .read_event (peer .descriptor , read_bytes );
173- if (res instanceof Result_boolPeerHandleErrorZ .Result_boolPeerHandleErrorZ_OK ) {
174- if (((Result_boolPeerHandleErrorZ .Result_boolPeerHandleErrorZ_OK ) res ).res ) {
217+ long read_result_pointer = bindings .PeerManager_read_event (
218+ peer_manager_raw_pointer , peer .descriptor_raw_pointer , read_bytes );
219+ if (bindings .LDKCResult_boolPeerHandleErrorZ_result_ok (read_result_pointer )) {
220+ if (bindings .LDKCResult_boolPeerHandleErrorZ_get_ok (read_result_pointer )) {
175221 key .interestOps (key .interestOps () & (~SelectionKey .OP_READ ));
176222 }
177223 } else {
178224 key .channel ().close ();
179225 key .cancel ();
180226 }
227+ bindings .CResult_boolPeerHandleErrorZ_free (read_result_pointer );
181228 }
182229 }
183230 } catch (IOException ignored ) {
@@ -197,6 +244,13 @@ public NioPeerHandler(PeerManager manager) throws IOException {
197244 io_thread .start ();
198245 }
199246
247+ // Ensure the types used in the above manual code match what they were when the code was written.
248+ // Ensure the above manual bindings.* code changes if this fails to compile.
249+ private void dummy_check_return_type_matches_manual_memory_code_above (Peer peer ) {
250+ byte [] read_bytes = new byte [32 ];
251+ Result_boolPeerHandleErrorZ res = this .peer_manager .read_event (peer .descriptor , read_bytes );
252+ }
253+
200254 /**
201255 * Connect to a peer given their node id and socket address. Blocks until a connection is established (or returns
202256 * IOException) and then the connection handling runs in the background.
0 commit comments