1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.named.ipc;
20
21 import java.io.BufferedReader;
22 import java.io.DataInputStream;
23 import java.io.DataOutputStream;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.net.SocketAddress;
27 import java.nio.channels.ByteChannel;
28 import java.nio.channels.Channels;
29 import java.nio.channels.ServerSocketChannel;
30 import java.nio.channels.SocketChannel;
31 import java.nio.charset.StandardCharsets;
32 import java.util.ArrayList;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.concurrent.CompletableFuture;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.CopyOnWriteArrayList;
39 import java.util.concurrent.TimeUnit;
40 import java.util.concurrent.atomic.AtomicInteger;
41
42 import org.slf4j.LoggerFactory;
43
44
45
46
47
48
49
50 public class IpcServer {
51 static volatile boolean forkedProcess;
52
53
54
55
56
57
58
59
60 public static final String SYSTEM_PROP_NO_FORK = "aether.named.ipc.nofork";
61
62 public static final boolean DEFAULT_NO_FORK = false;
63
64
65
66
67
68
69
70
71 public static final String SYSTEM_PROP_IDLE_TIMEOUT = "aether.named.ipc.idleTimeout";
72
73 public static final int DEFAULT_IDLE_TIMEOUT = 300;
74
75
76
77
78
79
80
81
82 public static final String SYSTEM_PROP_FAMILY = "aether.named.ipc.family";
83
84 public static final String DEFAULT_FAMILY = "unix";
85
86
87
88
89
90
91
92
93 public static final String SYSTEM_PROP_NO_NATIVE = "aether.named.ipc.nonative";
94
95 public static final boolean DEFAULT_NO_NATIVE = true;
96
97
98
99
100
101
102
103
104 public static final String SYSTEM_PROP_NATIVE_NAME = "aether.named.ipc.nativeName";
105
106 public static final String DEFAULT_NATIVE_NAME = "ipc-sync";
107
108
109
110
111
112
113
114
115 public static final String SYSTEM_PROP_DEBUG = "aether.named.ipc.debug";
116
117 public static final boolean DEFAULT_DEBUG = false;
118
119 private final ServerSocketChannel serverSocket;
120 private final Map<SocketChannel, Thread> clients = new ConcurrentHashMap<>();
121 private final AtomicInteger counter = new AtomicInteger();
122 private final Map<String, Lock> locks = new ConcurrentHashMap<>();
123 private final Map<String, Context> contexts = new ConcurrentHashMap<>();
124 private static final boolean DEBUG =
125 Boolean.parseBoolean(System.getProperty(SYSTEM_PROP_DEBUG, Boolean.toString(DEFAULT_DEBUG)));
126 private final long idleTimeout;
127 private final String bootstrapToken;
128 private volatile long lastUsed;
129 private volatile boolean closing;
130
131
132
133
134
135 @Deprecated
136 public IpcServer(SocketFamily family) throws IOException {
137 this(family, null);
138 }
139
140
141
142
143
144
145
146
147 public IpcServer(SocketFamily family, String bootstrapToken) throws IOException {
148 this.bootstrapToken = bootstrapToken;
149 serverSocket = family.openServerSocket();
150 long timeout = TimeUnit.SECONDS.toNanos(DEFAULT_IDLE_TIMEOUT);
151 String str = System.getProperty(SYSTEM_PROP_IDLE_TIMEOUT);
152 if (str != null) {
153 try {
154 TimeUnit unit = TimeUnit.SECONDS;
155 if (str.endsWith("ms")) {
156 unit = TimeUnit.MILLISECONDS;
157 str = str.substring(0, str.length() - 2);
158 }
159 long dur = Long.parseLong(str);
160 timeout = unit.toNanos(dur);
161 } catch (NumberFormatException e) {
162 error("Property " + SYSTEM_PROP_IDLE_TIMEOUT + " specified with invalid value: " + str, e);
163 }
164 }
165 idleTimeout = timeout;
166 }
167
168 public static void main(String[] args) throws Exception {
169 forkedProcess = true;
170
171
172
173
174
175
176
177
178 try {
179 sun.misc.Signal.handle(new sun.misc.Signal("INT"), sun.misc.SignalHandler.SIG_IGN);
180 if (!IpcClient.IS_WINDOWS) {
181 sun.misc.Signal.handle(new sun.misc.Signal("TSTP"), sun.misc.SignalHandler.SIG_IGN);
182 }
183 } catch (Throwable t) {
184 error("Unable to ignore INT and TSTP signals", t);
185 }
186
187 String family = args[0];
188 String tmpAddress = args[1];
189 String rand = args[2];
190 if ("-".equals(rand)) {
191
192
193 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
194 rand = reader.readLine();
195 if (rand == null || rand.isEmpty()) {
196 throw new IOException("Expected the bootstrap token on standard input");
197 }
198 }
199
200 runServer(SocketFamily.valueOf(family), tmpAddress, rand);
201 }
202
203 static IpcServer runServer(SocketFamily family, String tmpAddress, String rand) throws IOException {
204 IpcServer server = new IpcServer(family, rand);
205 run(server::run, false);
206 String address = SocketFamily.toString(server.getLocalAddress());
207 SocketAddress socketAddress = SocketFamily.fromString(tmpAddress);
208 try (SocketChannel socket = SocketChannel.open(socketAddress)) {
209 try (DataOutputStream dos = new DataOutputStream(Channels.newOutputStream(socket))) {
210 dos.writeUTF(rand);
211 dos.writeUTF(address);
212 dos.flush();
213 }
214 }
215
216 return server;
217 }
218
219 private static void debug(String msg, Object... args) {
220 if (DEBUG) {
221 if (forkedProcess) {
222 System.out.println("[ipc] [debug] " + format(msg, args));
223 } else {
224 LoggerFactory.getLogger(IpcServer.class).debug(msg, args);
225 }
226 }
227 }
228
229 private static void info(String msg, Object... args) {
230 if (forkedProcess) {
231 System.out.println("[ipc] [info] " + format(msg, args));
232 } else {
233 LoggerFactory.getLogger(IpcServer.class).info(msg, args);
234 }
235 }
236
237 private static void error(String msg, Throwable t) {
238 if (forkedProcess) {
239 System.out.println("[ipc] [error] " + msg);
240 t.printStackTrace(System.out);
241 } else {
242 LoggerFactory.getLogger(IpcServer.class).error(msg, t);
243 }
244 }
245
246 private static String format(String msg, Object... args) {
247 return String.format(msg.replace("{}", "%s"), args);
248 }
249
250 private static void run(Runnable runnable, boolean daemon) {
251 Thread thread = new Thread(runnable);
252 if (daemon) {
253 thread.setDaemon(true);
254 }
255 thread.start();
256 }
257
258 public SocketAddress getLocalAddress() throws IOException {
259 return serverSocket.getLocalAddress();
260 }
261
262 public void run() {
263 try {
264 info("IpcServer started at {}", getLocalAddress().toString());
265 use();
266 run(this::expirationCheck, true);
267 while (!closing) {
268 SocketChannel socket = this.serverSocket.accept();
269 run(() -> client(socket), false);
270 }
271 } catch (Throwable t) {
272 if (!closing) {
273 error("Error running sync server loop", t);
274 }
275 } finally {
276 forkedProcess = false;
277 }
278 }
279
280 private void client(SocketChannel socket) {
281 int c;
282 synchronized (clients) {
283 clients.put(socket, Thread.currentThread());
284 c = clients.size();
285 }
286 info("New client connected ({} connected)", c);
287 use();
288 Map<String, Context> clientContexts = new ConcurrentHashMap<>();
289 try {
290 ByteChannel wrapper = new ByteChannelWrapper(socket);
291 DataInputStream input = new DataInputStream(Channels.newInputStream(wrapper));
292 DataOutputStream output = new DataOutputStream(Channels.newOutputStream(wrapper));
293 while (!closing) {
294 int requestId = input.readInt();
295 int sz = input.readInt();
296 if (sz < 0) {
297 throw new IOException("Received invalid request size: " + sz);
298 }
299
300 List<String> request = new ArrayList<>(Math.min(sz, 1024));
301 for (int i = 0; i < sz; i++) {
302 request.add(input.readUTF());
303 }
304 if (request.isEmpty()) {
305 throw new IOException("Received invalid request");
306 }
307 use();
308 String contextId;
309 Context context;
310 String command = request.remove(0);
311 switch (command) {
312 case IpcMessages.REQUEST_CONTEXT:
313 if (request.size() != 1) {
314 throw new IOException("Expected one argument for " + command + " but got " + request);
315 }
316 boolean shared = Boolean.parseBoolean(request.remove(0));
317 context = new Context(shared);
318 contexts.put(context.id, context);
319 clientContexts.put(context.id, context);
320 synchronized (output) {
321 debug("Created context {}", context.id);
322 output.writeInt(requestId);
323 output.writeInt(2);
324 output.writeUTF(IpcMessages.RESPONSE_CONTEXT);
325 output.writeUTF(context.id);
326 output.flush();
327 }
328 break;
329 case IpcMessages.REQUEST_ACQUIRE:
330 if (request.isEmpty()) {
331 throw new IOException(
332 "Expected at least one argument for " + command + " but got " + request);
333 }
334 contextId = request.remove(0);
335
336 context = clientContexts.get(contextId);
337 if (context == null) {
338 throw new IOException(
339 "Unknown context: " + contextId + ". Known contexts = " + clientContexts.keySet());
340 }
341 context.lock(request).thenRun(() -> sendAcquireResponse(output, socket, requestId, context));
342 break;
343 case IpcMessages.REQUEST_CLOSE:
344 if (request.size() != 1) {
345 throw new IOException("Expected one argument for " + command + " but got " + request);
346 }
347 contextId = request.remove(0);
348
349 context = clientContexts.remove(contextId);
350 if (context == null) {
351 throw new IOException(
352 "Unknown context: " + contextId + ". Known contexts = " + clientContexts.keySet());
353 }
354 contexts.remove(contextId);
355 context.unlock();
356 synchronized (output) {
357 debug("Closing context {}", context.id);
358 output.writeInt(requestId);
359 output.writeInt(1);
360 output.writeUTF(IpcMessages.RESPONSE_CLOSE);
361 output.flush();
362 }
363 break;
364 case IpcMessages.REQUEST_STOP:
365 if (request.size() > 1) {
366 throw new IOException(
367 "Expected at most one argument for " + command + " but got " + request);
368 }
369 String stopToken = request.isEmpty() ? null : request.remove(0);
370 if (bootstrapToken == null || !bootstrapToken.equals(stopToken)) {
371
372
373 throw new IOException("Stop request rejected: missing or invalid bootstrap token");
374 }
375 synchronized (output) {
376 debug("Stopping server");
377 output.writeInt(requestId);
378 output.writeInt(1);
379 output.writeUTF(IpcMessages.RESPONSE_STOP);
380 output.flush();
381 }
382 close();
383 break;
384 default:
385 throw new IOException("Unknown request: " + request.get(0));
386 }
387 }
388 } catch (Throwable t) {
389 if (!closing) {
390 error("Error processing request", t);
391 }
392 } finally {
393 if (!closing) {
394 info("Client disconnecting...");
395 }
396 clientContexts.values().forEach(context -> {
397 contexts.remove(context.id);
398 context.unlock();
399 });
400 try {
401 socket.close();
402 } catch (IOException ioException) {
403
404 }
405 synchronized (clients) {
406 clients.remove(socket);
407 c = clients.size();
408 }
409 if (!closing) {
410 info("{} clients remained", c);
411 }
412 }
413 }
414
415 private void sendAcquireResponse(DataOutputStream output, SocketChannel socket, int requestId, Context context) {
416 try {
417 synchronized (output) {
418 debug("Locking in context {}", context.id);
419 output.writeInt(requestId);
420 output.writeInt(1);
421 output.writeUTF(IpcMessages.RESPONSE_ACQUIRE);
422 output.flush();
423 }
424 } catch (IOException e) {
425 try {
426 socket.close();
427 } catch (IOException ioException) {
428 e.addSuppressed(ioException);
429 }
430 error("Error writing lock response", e);
431 }
432 }
433
434 private void use() {
435 lastUsed = System.nanoTime();
436 }
437
438 private void expirationCheck() {
439 while (true) {
440 long current = System.nanoTime();
441 long left = (lastUsed + idleTimeout) - current;
442 if (clients.isEmpty() && left < 0) {
443 info("IpcServer expired, closing");
444 close();
445 break;
446 } else {
447 try {
448 Thread.sleep(Math.max(1, TimeUnit.NANOSECONDS.toMillis(left)));
449 } catch (InterruptedException e) {
450 info("IpcServer expiration check interrupted, closing");
451 close();
452 break;
453 }
454 }
455 }
456 }
457
458 void close() {
459 closing = true;
460 try {
461 serverSocket.close();
462 } catch (IOException e) {
463 error("Error closing server socket", e);
464 }
465 clients.forEach((s, t) -> {
466 try {
467 s.close();
468 } catch (IOException e) {
469
470 }
471 t.interrupt();
472 });
473 }
474
475 static class Waiter {
476 final Context context;
477 final CompletableFuture<Void> future;
478
479 Waiter(Context context, CompletableFuture<Void> future) {
480 this.context = context;
481 this.future = future;
482 }
483 }
484
485 static class Lock {
486
487 final String key;
488
489 List<Context> holders;
490 List<Waiter> waiters;
491
492 Lock(String key) {
493 this.key = key;
494 }
495
496 public synchronized CompletableFuture<Void> lock(Context context) {
497 if (holders == null) {
498 holders = new ArrayList<>();
499 }
500 if (holders.isEmpty() || holders.get(0).shared && context.shared) {
501 holders.add(context);
502 return CompletableFuture.completedFuture(null);
503 }
504 if (waiters == null) {
505 waiters = new ArrayList<>();
506 }
507
508 CompletableFuture<Void> future = new CompletableFuture<>();
509 waiters.add(new Waiter(context, future));
510 return future;
511 }
512
513 public void unlock(Context context) {
514 List<CompletableFuture<Void>> toComplete;
515 synchronized (this) {
516 toComplete = new ArrayList<>();
517 if (holders.remove(context)) {
518 while (waiters != null
519 && !waiters.isEmpty()
520 && (holders.isEmpty() || holders.get(0).shared && waiters.get(0).context.shared)) {
521 Waiter waiter = waiters.remove(0);
522 holders.add(waiter.context);
523 toComplete.add(waiter.future);
524 }
525 } else if (waiters != null) {
526 for (Iterator<Waiter> it = waiters.iterator(); it.hasNext(); ) {
527 Waiter waiter = it.next();
528 if (waiter.context == context) {
529 it.remove();
530 waiter.future.cancel(false);
531 }
532 }
533 }
534 }
535 toComplete.forEach(f -> f.complete(null));
536 }
537
538 public synchronized boolean isEmpty() {
539 return (holders == null || holders.isEmpty()) && (waiters == null || waiters.isEmpty());
540 }
541 }
542
543 class Context {
544
545 final String id;
546 final boolean shared;
547 final List<String> locks = new CopyOnWriteArrayList<>();
548
549 Context(boolean shared) {
550 this.id = String.format("%08x", counter.incrementAndGet());
551 this.shared = shared;
552 }
553
554 public CompletableFuture<?> lock(List<String> keys) {
555 locks.addAll(keys);
556 CompletableFuture<?>[] futures = keys.stream()
557 .map(k -> IpcServer.this.locks.computeIfAbsent(k, Lock::new))
558 .map(l -> l.lock(this))
559 .toArray(CompletableFuture[]::new);
560 return CompletableFuture.allOf(futures);
561 }
562
563 public void unlock() {
564 locks.stream()
565 .map(k -> IpcServer.this.locks.computeIfAbsent(k, Lock::new))
566 .forEach(l -> {
567 l.unlock(this);
568 IpcServer.this.locks.compute(l.key, (k, v) -> (v == l && v.isEmpty()) ? null : v);
569 });
570 }
571 }
572 }