11package org .ldk .batteries ;
22
3+ import org .ldk .impl .bindings ;
34import org .ldk .structs .*;
45
56import java .io .IOException ;
1011import java .nio .channels .Selector ;
1112import java .nio .channels .ServerSocketChannel ;
1213import java .nio .channels .SocketChannel ;
14+ import java .util .concurrent .Callable ;
1315
1416/**
1517 * A NioPeerHandler maps LDK's PeerHandler to Java's NIO I/O interface. It spawns a single background thread which
@@ -26,6 +28,31 @@ private static class Peer {
2628 SelectionKey key ;
2729 }
2830
31+ // Android's java.nio implementation has a big lock inside the selector, preventing any concurrent access to it.
32+ // This appears to largely defeat the entire purpose of java.nio, but we work around it here by explicitly checking
33+ // for an Android environment and passing any selector access on any thread other than our internal one through
34+ // do_selector_action, which wakes up the selector before accessing it.
35+ private static boolean IS_ANDROID ;
36+ static {
37+ IS_ANDROID = System .getProperty ("java.vendor" ).toLowerCase ().contains ("android" );
38+ }
39+ private boolean wakeup_selector = false ;
40+ private interface SelectorCall {
41+ void meth () throws IOException ;
42+ }
43+ private void do_selector_action (SelectorCall meth ) throws IOException {
44+ if (IS_ANDROID ) {
45+ wakeup_selector = true ;
46+ this .selector .wakeup ();
47+ synchronized (this .selector ) {
48+ meth .meth ();
49+ wakeup_selector = false ;
50+ }
51+ } else {
52+ meth .meth ();
53+ }
54+ }
55+
2956 private Peer setup_socket (SocketChannel chan ) throws IOException {
3057 chan .configureBlocking (false );
3158 // Lightning tends to send a number of small messages back and forth between peers quickly, which Nagle is
@@ -41,15 +68,13 @@ private Peer setup_socket(SocketChannel chan) throws IOException {
4168 SocketDescriptor descriptor = SocketDescriptor .new_impl (new SocketDescriptor .SocketDescriptorInterface () {
4269 @ Override
4370 public long send_data (byte [] data , boolean resume_read ) {
44- if (resume_read ) {
45- peer .key .interestOps (peer .key .interestOps () | SelectionKey .OP_READ );
46- selector .wakeup ();
47- }
4871 try {
72+ if (resume_read ) {
73+ do_selector_action (() -> peer .key .interestOps (peer .key .interestOps () | SelectionKey .OP_READ ));
74+ }
4975 long written = chan .write (ByteBuffer .wrap (data ));
5076 if (written != data .length ) {
51- peer .key .interestOps (peer .key .interestOps () | SelectionKey .OP_WRITE );
52- selector .wakeup ();
77+ do_selector_action (() -> peer .key .interestOps (peer .key .interestOps () | SelectionKey .OP_WRITE ));
5378 }
5479 return written ;
5580 } catch (IOException e ) {
@@ -61,9 +86,10 @@ public long send_data(byte[] data, boolean resume_read) {
6186 @ Override
6287 public void disconnect_socket () {
6388 try {
64- peer .key .cancel ();
65- peer .key .channel ().close ();
66- selector .wakeup ();
89+ do_selector_action (() -> {
90+ peer .key .cancel ();
91+ peer .key .channel ().close ();
92+ });
6793 } catch (IOException ignored ) { }
6894 synchronized (peer ) {
6995 while (peer .block_disconnect_socket ) {
@@ -82,7 +108,7 @@ public void disconnect_socket() {
82108
83109 PeerManager peer_manager ;
84110 Thread io_thread ;
85- Selector selector ;
111+ final Selector selector ;
86112 long socket_id ;
87113 volatile boolean shutdown = false ;
88114
@@ -101,7 +127,18 @@ public NioPeerHandler(PeerManager manager) throws IOException {
101127 long lastTimerTick = System .currentTimeMillis ();
102128 while (true ) {
103129 try {
104- this .selector .select (1000 );
130+ if (IS_ANDROID ) {
131+ while (true ) {
132+ synchronized (this .selector ) {
133+ if (!wakeup_selector ) {
134+ this .selector .select (1000 );
135+ break ;
136+ }
137+ }
138+ }
139+ } else {
140+ this .selector .select (1000 );
141+ }
105142 } catch (IOException ignored ) {
106143 System .err .println ("java.nio threw an unexpected IOException. Stopping PeerHandler thread!" );
107144 return ;
@@ -210,8 +247,7 @@ public void connect(byte[] their_node_id, SocketAddress remote, int timeout_ms)
210247 if (chan .write (ByteBuffer .wrap (initial_bytes )) != initial_bytes .length ) {
211248 throw new IOException ("We assume TCP socket buffer is at least a single packet in length" );
212249 }
213- peer .key = chan .register (this .selector , SelectionKey .OP_READ , peer );
214- this .selector .wakeup ();
250+ do_selector_action (() -> peer .key = chan .register (this .selector , SelectionKey .OP_READ , peer ));
215251 } else {
216252 throw new IOException ("LDK rejected outbound connection. This likely shouldn't ever happen." );
217253 }
@@ -228,8 +264,7 @@ public void bind_listener(SocketAddress socket_address) throws IOException {
228264 ServerSocketChannel listen_channel = ServerSocketChannel .open ();
229265 listen_channel .bind (socket_address );
230266 listen_channel .configureBlocking (false );
231- listen_channel .register (this .selector , SelectionKey .OP_ACCEPT );
232- this .selector .wakeup ();
267+ do_selector_action (() -> listen_channel .register (this .selector , SelectionKey .OP_ACCEPT ));
233268 }
234269
235270 /**
0 commit comments