1010import java .nio .channels .SelectionKey ;
1111import java .nio .channels .Selector ;
1212import java .util .Iterator ;
13+ import java .util .function .Consumer ;
1314import lombok .AccessLevel ;
1415import lombok .NoArgsConstructor ;
1516import lombok .extern .slf4j .Slf4j ;
2425 * <p>The following configuration parameter is available:
2526 *
2627 * <dl>
27- * <dt>dnsjava.nio.selector_timeout
28+ * <dt>{@value SELECTOR_TIMEOUT_PROPERTY}
2829 * <dd>Set selector timeout in milliseconds. Default/Max 1000, Min 1.
29- * <dt>dnsjava.nio.register_shutdown_hook
30+ * <dt>{@value REGISTER_SHUTDOWN_HOOK_PROPERTY}
3031 * <dd>Register Shutdown Hook termination of NIO. Default True.
3132 * </dl>
3233 *
3536@ Slf4j
3637@ NoArgsConstructor (access = AccessLevel .NONE )
3738public abstract class NioClient {
39+ static final String SELECTOR_TIMEOUT_PROPERTY = "dnsjava.nio.selector_timeout" ;
40+ static final String REGISTER_SHUTDOWN_HOOK_PROPERTY = "dnsjava.nio.register_shutdown_hook" ;
41+ private static final Object NIO_CLIENT_LOCK = new Object ();
42+
3843 /** Packet logger, if available. */
3944 private static PacketLogger packetLogger = null ;
4045
4146 private static final Runnable [] TIMEOUT_TASKS = new Runnable [2 ];
42- private static final Runnable [] REGISTRATIONS_TASKS = new Runnable [2 ];
47+
48+ @ SuppressWarnings ("unchecked" )
49+ private static final Consumer <Selector >[] REGISTRATIONS_TASKS = new Consumer [2 ];
50+
4351 private static final Runnable [] CLOSE_TASKS = new Runnable [2 ];
4452 private static Thread selectorThread ;
4553 private static Thread closeThread ;
4654 private static volatile Selector selector ;
4755 private static volatile boolean run ;
56+ private static volatile boolean closeDone ;
4857
4958 interface KeyProcessor {
5059 void processReadyKey (SelectionKey key );
5160 }
5261
5362 static Selector selector () throws IOException {
5463 if (selector == null ) {
55- synchronized (NioClient . class ) {
64+ synchronized (NIO_CLIENT_LOCK ) {
5665 if (selector == null ) {
5766 selector = Selector .open ();
5867 log .debug ("Starting dnsjava NIO selector thread" );
@@ -63,8 +72,7 @@ static Selector selector() throws IOException {
6372 selectorThread .start ();
6473 closeThread = new Thread (() -> close (true ));
6574 closeThread .setName ("dnsjava NIO shutdown hook" );
66- if (Boolean .parseBoolean (
67- System .getProperty ("dnsjava.nio.register_shutdown_hook" , "true" ))) {
75+ if (Boolean .parseBoolean (System .getProperty (REGISTER_SHUTDOWN_HOOK_PROPERTY , "true" ))) {
6876 Runtime .getRuntime ().addShutdownHook (closeThread );
6977 }
7078 }
@@ -74,13 +82,23 @@ static Selector selector() throws IOException {
7482 return selector ;
7583 }
7684
77- /** Shutdown the network I/O used by the {@link SimpleResolver}. */
85+ /**
86+ * Shutdown the network I/O used by the {@link SimpleResolver}.
87+ *
88+ * @implNote Does not wait until the selector thread has stopped. But users may immediately start
89+ * using the {@link NioClient} again.
90+ * @since 3.4.0
91+ */
7892 public static void close () {
7993 close (false );
8094 }
8195
8296 private static void close (boolean fromHook ) {
8397 run = false ;
98+ Selector localSelector = selector ;
99+ if (localSelector != null ) {
100+ selector .wakeup ();
101+ }
84102
85103 if (!fromHook ) {
86104 try {
@@ -90,40 +108,26 @@ private static void close(boolean fromHook) {
90108 }
91109 }
92110
93- try {
94- runTasks (CLOSE_TASKS );
95- } catch (Exception e ) {
96- log .warn ("Failed to execute shutdown task, ignoring and continuing close" , e );
97- }
98-
99- Selector localSelector = selector ;
100- Thread localSelectorThread = selectorThread ;
101- synchronized (NioClient .class ) {
102- selector = null ;
103- selectorThread = null ;
104- closeThread = null ;
111+ if (localSelector == null ) {
112+ // Prevent hanging when close() was called without starting
113+ return ;
105114 }
106115
107- if (localSelector != null ) {
108- localSelector .wakeup ();
116+ synchronized (NIO_CLIENT_LOCK ) {
109117 try {
110- localSelector .close ();
111- } catch (IOException e ) {
112- log .warn ("Failed to properly close selector, ignoring and continuing close" , e );
113- }
114- }
115-
116- if (localSelectorThread != null ) {
117- try {
118- localSelectorThread .join ();
118+ while (!closeDone ) {
119+ NIO_CLIENT_LOCK .wait ();
120+ }
119121 } catch (InterruptedException e ) {
120122 Thread .currentThread ().interrupt ();
123+ } finally {
124+ closeDone = false ;
121125 }
122126 }
123127 }
124128
125129 static void runSelector () {
126- int timeout = Integer .getInteger ("dnsjava.nio.selector_timeout" , 1000 );
130+ int timeout = Integer .getInteger (SELECTOR_TIMEOUT_PROPERTY , 1000 );
127131
128132 if (timeout <= 0 || timeout > 1000 ) {
129133 throw new IllegalArgumentException ("Invalid selector_timeout, must be between 1 and 1000" );
@@ -136,7 +140,7 @@ static void runSelector() {
136140 }
137141
138142 if (run ) {
139- runTasks ( REGISTRATIONS_TASKS );
143+ runRegistrationTasks ( );
140144 processReadyKeys ();
141145 }
142146 } catch (IOException e ) {
@@ -145,30 +149,74 @@ static void runSelector() {
145149 // ignore
146150 }
147151 }
152+
153+ runClose ();
148154 log .debug ("dnsjava NIO selector thread stopped" );
149155 }
150156
151- static synchronized void setTimeoutTask (Runnable r , boolean isTcpClient ) {
157+ private static void runClose () {
158+ try {
159+ runTasks (CLOSE_TASKS );
160+ } catch (Exception e ) {
161+ log .warn ("Failed to execute shutdown task, ignoring and continuing close" , e );
162+ }
163+
164+ Selector localSelector = selector ;
165+ Thread localSelectorThread = selectorThread ;
166+ synchronized (NIO_CLIENT_LOCK ) {
167+ selector = null ;
168+ selectorThread = null ;
169+ closeThread = null ;
170+ closeDone = true ;
171+ NIO_CLIENT_LOCK .notifyAll ();
172+ }
173+
174+ if (localSelector != null ) {
175+ try {
176+ localSelector .close ();
177+ } catch (IOException e ) {
178+ log .warn ("Failed to properly close selector, ignoring and continuing close" , e );
179+ }
180+ }
181+
182+ if (localSelectorThread != null ) {
183+ try {
184+ localSelectorThread .join ();
185+ } catch (InterruptedException e ) {
186+ Thread .currentThread ().interrupt ();
187+ }
188+ }
189+ }
190+
191+ static void setTimeoutTask (Runnable r , boolean isTcpClient ) {
152192 addTask (TIMEOUT_TASKS , r , isTcpClient );
153193 }
154194
155- static synchronized void setRegistrationsTask (Runnable r , boolean isTcpClient ) {
156- addTask ( REGISTRATIONS_TASKS , r , isTcpClient );
195+ static void setRegistrationsTask (Consumer < Selector > r , boolean isTcpClient ) {
196+ addRegistrationTask ( r , isTcpClient );
157197 }
158198
159- static synchronized void setCloseTask (Runnable r , boolean isTcpClient ) {
199+ static void setCloseTask (Runnable r , boolean isTcpClient ) {
160200 addTask (CLOSE_TASKS , r , isTcpClient );
161201 }
162202
163- private static void addTask (Runnable [] closeTasks , Runnable r , boolean isTcpClient ) {
203+ private static void addTask (Runnable [] tasks , Runnable r , boolean isTcpClient ) {
164204 if (isTcpClient ) {
165- closeTasks [0 ] = r ;
205+ tasks [0 ] = r ;
166206 } else {
167- closeTasks [1 ] = r ;
207+ tasks [1 ] = r ;
168208 }
169209 }
170210
171- private static synchronized void runTasks (Runnable [] runnables ) {
211+ private static void addRegistrationTask (Consumer <Selector > r , boolean isTcpClient ) {
212+ if (isTcpClient ) {
213+ REGISTRATIONS_TASKS [0 ] = r ;
214+ } else {
215+ REGISTRATIONS_TASKS [1 ] = r ;
216+ }
217+ }
218+
219+ private static void runTasks (Runnable [] runnables ) {
172220 Runnable r0 = runnables [0 ];
173221 if (r0 != null ) {
174222 r0 .run ();
@@ -179,6 +227,17 @@ private static synchronized void runTasks(Runnable[] runnables) {
179227 }
180228 }
181229
230+ private static void runRegistrationTasks () {
231+ Consumer <Selector > r0 = REGISTRATIONS_TASKS [0 ];
232+ if (r0 != null ) {
233+ r0 .accept (selector );
234+ }
235+ Consumer <Selector > r1 = REGISTRATIONS_TASKS [1 ];
236+ if (r1 != null ) {
237+ r1 .accept (selector );
238+ }
239+ }
240+
182241 private static void processReadyKeys () {
183242 Iterator <SelectionKey > it = selector .selectedKeys ().iterator ();
184243 while (it .hasNext ()) {
0 commit comments