1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.newsclub.net.unix.server;
19
20 import java.io.IOException;
21 import java.io.InterruptedIOException;
22 import java.net.ServerSocket;
23 import java.net.Socket;
24 import java.net.SocketAddress;
25 import java.net.SocketException;
26 import java.net.SocketTimeoutException;
27 import java.util.Objects;
28 import java.util.concurrent.Callable;
29 import java.util.concurrent.ExecutorService;
30 import java.util.concurrent.Executors;
31 import java.util.concurrent.ForkJoinPool;
32 import java.util.concurrent.Future;
33 import java.util.concurrent.ScheduledExecutorService;
34 import java.util.concurrent.ScheduledFuture;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.atomic.AtomicBoolean;
37 import java.util.concurrent.atomic.AtomicInteger;
38
39 import org.eclipse.jdt.annotation.NonNull;
40 import org.newsclub.net.unix.AFServerSocket;
41 import org.newsclub.net.unix.AFSocketAddress;
42
43 import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
44 import com.kohlschutter.annotations.compiletime.SuppressLint;
45
46
47
48
49
50
51
52
53
54
55 public abstract class SocketServer<A extends SocketAddress, S extends Socket, V extends ServerSocket> {
56 private static final ScheduledExecutorService TIMEOUTS = Executors.newScheduledThreadPool(1);
57
58 private final @NonNull A listenAddress;
59
60 private int maxConcurrentConnections = Runtime.getRuntime().availableProcessors();
61 private int serverTimeout = 0;
62 private final AtomicInteger socketTimeout = new AtomicInteger((int) TimeUnit.SECONDS.toMillis(
63 60));
64 private final AtomicInteger serverBusyTimeout = new AtomicInteger((int) TimeUnit.SECONDS.toMillis(
65 1));
66
67 private Thread listenThread = null;
68 private V serverSocket;
69 private final AtomicBoolean stopRequested = new AtomicBoolean(false);
70 private final AtomicBoolean ready = new AtomicBoolean(false);
71
72 private final Object connectionsMonitor = new Object();
73 private ForkJoinPool connectionPool;
74
75 private ScheduledFuture<IOException> timeoutFuture;
76 private final V reuseSocket;
77
78
79
80
81
82
83 @SuppressWarnings("all")
84 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
85 public SocketServer(V serverSocket) {
86 this((A) Objects.requireNonNull(serverSocket).getLocalSocketAddress(), serverSocket);
87 }
88
89
90
91
92
93
94 @SuppressWarnings("null")
95 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
96 public SocketServer(A listenAddress) {
97 this(listenAddress, null);
98 }
99
100 @SuppressWarnings("null")
101 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
102 private SocketServer(A listenAddress, V preboundSocket) {
103 Objects.requireNonNull(listenAddress, "listenAddress");
104 this.reuseSocket = preboundSocket;
105
106 this.listenAddress = listenAddress;
107 }
108
109
110
111
112
113
114 public int getMaxConcurrentConnections() {
115 return maxConcurrentConnections;
116 }
117
118
119
120
121
122
123 @SuppressFBWarnings("AT_STALE_THREAD_WRITE_OF_PRIMITIVE")
124 public void setMaxConcurrentConnections(int maxConcurrentConnections) {
125 if (isRunning()) {
126 throw new IllegalStateException("Already configured");
127 }
128 this.maxConcurrentConnections = maxConcurrentConnections;
129 }
130
131
132
133
134
135
136 public int getServerTimeout() {
137 return serverTimeout;
138 }
139
140
141
142
143
144
145 @SuppressFBWarnings("AT_STALE_THREAD_WRITE_OF_PRIMITIVE")
146 public void setServerTimeout(int timeout) {
147 if (isRunning()) {
148 throw new IllegalStateException("Already configured");
149 }
150 this.serverTimeout = timeout;
151 }
152
153
154
155
156
157
158 public int getSocketTimeout() {
159 return socketTimeout.get();
160 }
161
162
163
164
165
166
167 public void setSocketTimeout(int timeout) {
168 this.socketTimeout.set(timeout);
169 }
170
171
172
173
174
175
176 public int getServerBusyTimeout() {
177 return serverBusyTimeout.get();
178 }
179
180
181
182
183
184
185 public void setServerBusyTimeout(int timeout) {
186 this.serverBusyTimeout.set(timeout);
187 }
188
189
190
191
192
193
194 public boolean isRunning() {
195 synchronized (this) {
196 return (listenThread != null && listenThread.isAlive());
197 }
198 }
199
200
201
202
203
204
205 public boolean isReady() {
206 return ready.get() && !stopRequested.get() && isRunning();
207 }
208
209
210
211
212
213
214 public void start() {
215 synchronized (this) {
216 if (isRunning()) {
217 return;
218 }
219 if (connectionPool == null) {
220 connectionPool = new ForkJoinPool(maxConcurrentConnections,
221 ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
222 }
223
224 @SuppressWarnings("deprecation")
225 Thread t = new Thread(() -> {
226 try {
227 listen();
228 } catch (Exception e) {
229 onListenException(e);
230 } catch (Throwable e) {
231 onListenException(e);
232 }
233 }, SocketServer.this.toString() + " listening thread");
234 t.start();
235
236 listenThread = t;
237 }
238
239 }
240
241
242
243
244
245
246 public void startAndWaitToBecomeReady() throws InterruptedException {
247 synchronized (this) {
248 start();
249 while (!ready.get() && !stopRequested.get()) {
250 this.wait(1000);
251 }
252 }
253 }
254
255
256
257
258
259
260
261
262
263 public boolean startAndWaitToBecomeReady(long duration, TimeUnit unit)
264 throws InterruptedException {
265 synchronized (this) {
266 start();
267 long timeStart = System.currentTimeMillis();
268 while (duration > 0) {
269 if (isReady()) {
270 return true;
271 }
272 this.wait(unit.toMillis(duration));
273 duration -= (System.currentTimeMillis() - timeStart);
274 }
275 return isReady();
276 }
277 }
278
279
280
281
282
283
284
285
286 protected abstract V newServerSocket() throws IOException;
287
288 @SuppressWarnings("null")
289 private void listen() throws IOException {
290 V server = null;
291 try {
292 synchronized (this) {
293 if (reuseSocket != null) {
294 server = reuseSocket;
295 } else {
296 server = null;
297 }
298 }
299 if (server == null) {
300 server = newServerSocket();
301 }
302 synchronized (this) {
303 if (serverSocket != null) {
304 throw new IllegalStateException("The server is already listening");
305 }
306 serverSocket = server;
307 }
308 onServerStarting();
309
310 if (!server.isBound()) {
311 server.bind(listenAddress);
312 onServerBound(listenAddress);
313 }
314 server.setSoTimeout(serverTimeout);
315
316 acceptLoop(server);
317 } catch (SocketException e) {
318 onSocketExceptionDuringAccept(e);
319 } finally {
320 stop();
321 onServerStopped(server);
322 }
323 }
324
325 @SuppressWarnings("PMD.CognitiveComplexity")
326 @SuppressFBWarnings("NN_NAKED_NOTIFY")
327 @SuppressLint("RESOURCE_LEAK")
328 private void acceptLoop(V server) throws IOException {
329 long busyStartTime = 0;
330 acceptLoop : while (!stopRequested.get() && !Thread.interrupted()) {
331 try {
332 while (!stopRequested.get() && connectionPool
333 .getActiveThreadCount() >= maxConcurrentConnections) {
334 if (busyStartTime == 0) {
335 busyStartTime = System.currentTimeMillis();
336 }
337 onServerBusy(busyStartTime);
338
339 synchronized (connectionsMonitor) {
340 try {
341 connectionsMonitor.wait(getServerBusyTimeout());
342 } catch (InterruptedException e) {
343 throw (InterruptedIOException) new InterruptedIOException(
344 "Interrupted while waiting on server resources").initCause(e);
345 }
346 }
347 }
348 busyStartTime = 0;
349
350 if (stopRequested.get() || server == null) {
351 break;
352 }
353
354 synchronized (SocketServer.this) {
355 SocketServer.this.notifyAll();
356 }
357 ready.set(true);
358 onServerReady(connectionPool.getActiveThreadCount());
359
360 final S socket;
361 try {
362 @SuppressWarnings("unchecked")
363 S theSocket = (S) server.accept();
364 socket = theSocket;
365 } catch (SocketException e) {
366 if (server.isClosed()) {
367
368 break acceptLoop;
369 } else {
370 throw e;
371 }
372 }
373 try {
374 socket.setSoTimeout(getSocketTimeout());
375 } catch (SocketException e) {
376
377 onSocketExceptionAfterAccept(socket, e);
378 socket.close();
379
380 continue acceptLoop;
381 }
382
383 onSubmitted(socket, submit(socket, connectionPool));
384 } catch (SocketTimeoutException e) {
385 if (!connectionPool.isQuiescent()) {
386 continue acceptLoop;
387 } else {
388 onServerShuttingDown();
389 connectionPool.shutdown();
390 break acceptLoop;
391 }
392 }
393 }
394 }
395
396
397
398
399
400
401 @SuppressWarnings("null")
402 public void stop() throws IOException {
403 stopRequested.set(true);
404 ready.set(false);
405
406 synchronized (this) {
407 V theServerSocket = serverSocket;
408 serverSocket = null;
409 try {
410 if (theServerSocket == null) {
411 return;
412 }
413 ScheduledFuture<IOException> future = this.timeoutFuture;
414 if (future != null) {
415 future.cancel(false);
416 this.timeoutFuture = null;
417 }
418
419 theServerSocket.close();
420 } finally {
421 SocketServer.this.notifyAll();
422 }
423 }
424 }
425
426 private Future<?> submit(final S socket, ExecutorService executor) {
427 Objects.requireNonNull(socket);
428 return executor.submit(new Runnable() {
429 @SuppressWarnings("deprecation")
430 @SuppressFBWarnings("NN_NAKED_NOTIFY")
431 @Override
432 public void run() {
433 onBeforeServingSocket(socket);
434
435 try {
436 doServeSocket(socket);
437 } catch (Exception e) {
438 onServingException(socket, e);
439 } catch (Throwable t) {
440 onServingException(socket, t);
441 } finally {
442
443 synchronized (connectionsMonitor) {
444 connectionsMonitor.notifyAll();
445 }
446
447 doSocketClose(socket);
448 onAfterServingSocket(socket);
449 }
450 }
451 });
452 }
453
454
455
456
457
458
459
460
461
462 @SuppressWarnings("null")
463 protected void doSocketClose(S socket) {
464 try {
465 socket.close();
466 } catch (IOException e) {
467
468 }
469 }
470
471
472
473
474
475
476
477
478
479
480
481 public ScheduledFuture<IOException> startThenStopAfter(long delay, TimeUnit unit) {
482 if (stopRequested.get()) {
483 return null;
484 }
485 synchronized (this) {
486 start();
487 ScheduledFuture<?> existingFuture = this.timeoutFuture;
488 if (existingFuture != null) {
489 existingFuture.cancel(false);
490 }
491
492 return (this.timeoutFuture = TIMEOUTS.schedule(new Callable<IOException>() {
493 @Override
494 public IOException call() throws Exception {
495 try {
496 stop();
497 return null;
498 } catch (IOException e) {
499 return e;
500 }
501 }
502 }, delay, unit));
503 }
504 }
505
506
507
508
509
510
511
512 protected abstract void doServeSocket(S socket) throws IOException;
513
514
515
516
517 protected void onServerStarting() {
518 }
519
520
521
522
523
524
525
526
527 protected void onServerBound(A address) {
528 }
529
530
531
532
533
534
535 protected void onServerReady(int activeCount) {
536 }
537
538
539
540
541
542
543
544
545
546 protected void onServerBusy(long busyStartTime) {
547 }
548
549
550
551
552
553
554 protected void onServerStopped(V socket) {
555 }
556
557
558
559
560
561
562
563
564 protected void onSubmitted(S socket, Future<?> submission) {
565 }
566
567
568
569
570 protected void onServerShuttingDown() {
571 }
572
573
574
575
576
577
578 protected void onSocketExceptionDuringAccept(SocketException e) {
579 }
580
581
582
583
584
585
586
587 protected void onSocketExceptionAfterAccept(S socket, SocketException e) {
588 }
589
590
591
592
593
594
595 protected void onBeforeServingSocket(S socket) {
596 }
597
598
599
600
601
602
603
604
605
606 @Deprecated
607 protected void onServingException(S socket, Exception e) {
608 onServingException(socket, (Throwable) e);
609 }
610
611
612
613
614
615
616
617 protected void onServingException(S socket, Throwable t) {
618 }
619
620
621
622
623
624
625 protected void onAfterServingSocket(S socket) {
626 }
627
628
629
630
631
632
633
634
635 @Deprecated
636 protected void onListenException(Exception e) {
637 onListenException((Throwable) e);
638 }
639
640
641
642
643
644
645 protected void onListenException(Throwable t) {
646 }
647
648
649
650
651
652
653 protected @NonNull A getListenAddress() {
654 return listenAddress;
655 }
656 }