[ https://issues.apache.org/jira/browse/MRESOLVER-499?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17819812#comment-17819812 ]
ASF GitHub Bot commented on MRESOLVER-499: ------------------------------------------ michael-o commented on code in PR #435: URL: https://github.com/apache/maven-resolver/pull/435#discussion_r1499907387 ########## maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java: ########## @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.named.ipc; + +import java.io.IOException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.nio.channels.ServerSocketChannel; + +/** + * Socket factory. + * + * @since 2.0.0 + */ +public enum SocketFamily { + inet; + + public ServerSocketChannel openServerSocket() throws IOException { + switch (this) { + case inet: + return ServerSocketChannel.open().bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + default: + throw new IllegalStateException(); + } + } + + public static SocketAddress fromString(String str) { + if (str.startsWith("inet:")) { Review Comment: Why this complexity? ########## maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java: ########## @@ -0,0 +1,482 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.named.ipc; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.channels.ByteChannel; +import java.nio.channels.Channels; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Implementation of the server side. + * The server instance is bound to a given maven repository. + * + * @since 2.0.0 + */ +public class IpcServer { + /** + * Should the IPC server not fork? (i.e. for testing purposes) + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_NO_FORK} + */ + public static final String SYSTEM_PROP_NO_FORK = "aether.named.ipc.nofork"; + + public static final boolean DEFAULT_NO_FORK = false; + + /** + * IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Integer} + * @configurationDefaultValue {@link #DEFAULT_IDLE_TIMEOUT} + */ + public static final String SYSTEM_PROP_IDLE_TIMEOUT = "aether.named.ipc.idleTimeout"; + + public static final int DEFAULT_IDLE_TIMEOUT = 60; + + /** + * IPC socket family to use. + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.String} + * @configurationDefaultValue {@link #DEFAULT_FAMILY} + */ + public static final String SYSTEM_PROP_FAMILY = "aether.named.ipc.family"; + + public static final String DEFAULT_FAMILY = "inet"; + + /** + * Should the IPC server not use native executable? + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_NO_NATIVE} + */ + public static final String SYSTEM_PROP_NO_NATIVE = "aether.named.ipc.nonative"; + + public static final boolean DEFAULT_NO_NATIVE = false; + + /** + * Should the IPC server log debug messages? (i.e. for testing purposes) + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_DEBUG} + */ + public static final String SYSTEM_PROP_DEBUG = "aether.named.ipc.debug"; + + public static final boolean DEFAULT_DEBUG = false; + + private final ServerSocketChannel serverSocket; + private final Map<SocketChannel, Thread> clients = new HashMap<>(); + private final AtomicInteger counter = new AtomicInteger(); + private final Map<String, Lock> locks = new ConcurrentHashMap<>(); + private final Map<String, Context> contexts = new ConcurrentHashMap<>(); + private static final boolean DEBUG = + Boolean.parseBoolean(System.getProperty(SYSTEM_PROP_DEBUG, Boolean.toString(DEFAULT_DEBUG))); + private final long idleTimeout; + private volatile long lastUsed; + private volatile boolean closing; + + public IpcServer(SocketFamily family) throws IOException { + serverSocket = family.openServerSocket(); + long timeout = TimeUnit.SECONDS.toNanos(DEFAULT_IDLE_TIMEOUT); + String str = System.getProperty(SYSTEM_PROP_IDLE_TIMEOUT); + if (str != null) { + try { + TimeUnit unit = TimeUnit.SECONDS; + if (str.endsWith("ms")) { + unit = TimeUnit.MILLISECONDS; + str = str.substring(0, str.length() - 2); + } + long dur = Long.parseLong(str); + timeout = unit.toNanos(dur); + } catch (NumberFormatException e) { + error("Property " + SYSTEM_PROP_IDLE_TIMEOUT + " specified with invalid value: " + str, e); + } + } + idleTimeout = timeout; + } + + public static void main(String[] args) throws Exception { + // When spawning a new process, the child process is create within + // the same process group. This means that a few signals are sent + // to the whole group. This is the case for SIGINT (Ctrl-C) and + // SIGTSTP (Ctrl-Z) which are both sent to all the processed in the + // group when initiated from the controlling terminal. + // This is only a problem when the client creates the daemon, but + // without ignoring those signals, a client being interrupted will + // also interrupt and kill the daemon. + try { + sun.misc.Signal.handle(new sun.misc.Signal("INT"), sun.misc.SignalHandler.SIG_IGN); + if (IpcClient.IS_WINDOWS) { + sun.misc.Signal.handle(new sun.misc.Signal("TSTP"), sun.misc.SignalHandler.SIG_IGN); + } + } catch (Throwable t) { + error("Unable to ignore INT and TSTP signals", t); + } + + String family = args[0]; + String tmpAddress = args[1]; + String rand = args[2]; + + runServer(SocketFamily.valueOf(family), tmpAddress, rand); + } + + static IpcServer runServer(SocketFamily family, String tmpAddress, String rand) throws IOException { + IpcServer server = new IpcServer(family); + run(server::run, false); // this is one-off + String address = SocketFamily.toString(server.getLocalAddress()); + SocketAddress socketAddress = SocketFamily.fromString(tmpAddress); + try (SocketChannel socket = SocketChannel.open(socketAddress)) { + try (DataOutputStream dos = new DataOutputStream(Channels.newOutputStream(socket))) { + dos.writeUTF(rand); + dos.writeUTF(address); + dos.flush(); + } + } + + return server; + } + + private static void debug(String msg, Object... args) { + if (DEBUG) { + System.out.printf("[ipc] [debug] " + msg + "\n", args); + } + } + + private static void info(String msg, Object... args) { + System.out.printf("[ipc] [info] " + msg + "\n", args); Review Comment: `%n` ########## maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java: ########## @@ -0,0 +1,417 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.named.ipc; + +import java.io.*; +import java.net.SocketAddress; +import java.nio.channels.ByteChannel; +import java.nio.channels.Channels; +import java.nio.channels.FileLock; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.eclipse.aether.named.ipc.IpcMessages.REQUEST_ACQUIRE; +import static org.eclipse.aether.named.ipc.IpcMessages.REQUEST_CLOSE; +import static org.eclipse.aether.named.ipc.IpcMessages.REQUEST_CONTEXT; +import static org.eclipse.aether.named.ipc.IpcMessages.REQUEST_STOP; +import static org.eclipse.aether.named.ipc.IpcMessages.RESPONSE_ACQUIRE; +import static org.eclipse.aether.named.ipc.IpcMessages.RESPONSE_CLOSE; +import static org.eclipse.aether.named.ipc.IpcMessages.RESPONSE_CONTEXT; +import static org.eclipse.aether.named.ipc.IpcMessages.RESPONSE_STOP; + +/** + * Client side implementation. + * The client instance is bound to a given maven repository. + * + * @since 2.0.0 + */ +public class IpcClient { + + static final boolean IS_WINDOWS = + System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win"); + + volatile boolean initialized; + Path lockPath; + Path logPath; + Path syncPath; + SocketChannel socket; + DataOutputStream output; + DataInputStream input; + Thread receiver; + AtomicInteger requestId = new AtomicInteger(); + Map<Integer, CompletableFuture<List<String>>> responses = new ConcurrentHashMap<>(); + + IpcClient(Path lockPath, Path logPath, Path syncPath) { + this.lockPath = lockPath; + this.logPath = logPath; + this.syncPath = syncPath; + } + + void ensureInitialized() throws IOException { + if (!initialized) { + synchronized (this) { + if (!initialized) { + socket = createClient(); + ByteChannel wrapper = new ByteChannelWrapper(socket); + input = new DataInputStream(Channels.newInputStream(wrapper)); + output = new DataOutputStream(Channels.newOutputStream(wrapper)); + receiver = new Thread(this::receive); + receiver.setDaemon(true); + receiver.start(); + initialized = true; + } + } + } + } + + SocketChannel createClient() throws IOException { + String familyProp = System.getProperty(IpcServer.SYSTEM_PROP_FAMILY, IpcServer.DEFAULT_FAMILY); + SocketFamily family = familyProp != null ? SocketFamily.valueOf(familyProp) : SocketFamily.inet; Review Comment: How can this `null` if you have a default value? ########## maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java: ########## @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.named.ipc; + +import java.io.IOException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.nio.channels.ServerSocketChannel; + +/** + * Socket factory. + * + * @since 2.0.0 + */ +public enum SocketFamily { + inet; Review Comment: Should be uppercase. Akin to `AF_INET`. ########## maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java: ########## @@ -0,0 +1,482 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.named.ipc; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.channels.ByteChannel; +import java.nio.channels.Channels; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Implementation of the server side. + * The server instance is bound to a given maven repository. + * + * @since 2.0.0 + */ +public class IpcServer { + /** + * Should the IPC server not fork? (i.e. for testing purposes) + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_NO_FORK} + */ + public static final String SYSTEM_PROP_NO_FORK = "aether.named.ipc.nofork"; + + public static final boolean DEFAULT_NO_FORK = false; + + /** + * IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Integer} + * @configurationDefaultValue {@link #DEFAULT_IDLE_TIMEOUT} + */ + public static final String SYSTEM_PROP_IDLE_TIMEOUT = "aether.named.ipc.idleTimeout"; + + public static final int DEFAULT_IDLE_TIMEOUT = 60; + + /** + * IPC socket family to use. + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.String} + * @configurationDefaultValue {@link #DEFAULT_FAMILY} + */ + public static final String SYSTEM_PROP_FAMILY = "aether.named.ipc.family"; + + public static final String DEFAULT_FAMILY = "inet"; + + /** + * Should the IPC server not use native executable? + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_NO_NATIVE} + */ + public static final String SYSTEM_PROP_NO_NATIVE = "aether.named.ipc.nonative"; + + public static final boolean DEFAULT_NO_NATIVE = false; + + /** + * Should the IPC server log debug messages? (i.e. for testing purposes) + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_DEBUG} + */ + public static final String SYSTEM_PROP_DEBUG = "aether.named.ipc.debug"; + + public static final boolean DEFAULT_DEBUG = false; + + private final ServerSocketChannel serverSocket; + private final Map<SocketChannel, Thread> clients = new HashMap<>(); + private final AtomicInteger counter = new AtomicInteger(); + private final Map<String, Lock> locks = new ConcurrentHashMap<>(); + private final Map<String, Context> contexts = new ConcurrentHashMap<>(); + private static final boolean DEBUG = + Boolean.parseBoolean(System.getProperty(SYSTEM_PROP_DEBUG, Boolean.toString(DEFAULT_DEBUG))); + private final long idleTimeout; + private volatile long lastUsed; + private volatile boolean closing; + + public IpcServer(SocketFamily family) throws IOException { + serverSocket = family.openServerSocket(); + long timeout = TimeUnit.SECONDS.toNanos(DEFAULT_IDLE_TIMEOUT); + String str = System.getProperty(SYSTEM_PROP_IDLE_TIMEOUT); + if (str != null) { + try { + TimeUnit unit = TimeUnit.SECONDS; + if (str.endsWith("ms")) { + unit = TimeUnit.MILLISECONDS; + str = str.substring(0, str.length() - 2); + } + long dur = Long.parseLong(str); + timeout = unit.toNanos(dur); + } catch (NumberFormatException e) { + error("Property " + SYSTEM_PROP_IDLE_TIMEOUT + " specified with invalid value: " + str, e); + } + } + idleTimeout = timeout; + } + + public static void main(String[] args) throws Exception { + // When spawning a new process, the child process is create within + // the same process group. This means that a few signals are sent + // to the whole group. This is the case for SIGINT (Ctrl-C) and + // SIGTSTP (Ctrl-Z) which are both sent to all the processed in the + // group when initiated from the controlling terminal. + // This is only a problem when the client creates the daemon, but + // without ignoring those signals, a client being interrupted will + // also interrupt and kill the daemon. + try { + sun.misc.Signal.handle(new sun.misc.Signal("INT"), sun.misc.SignalHandler.SIG_IGN); + if (IpcClient.IS_WINDOWS) { + sun.misc.Signal.handle(new sun.misc.Signal("TSTP"), sun.misc.SignalHandler.SIG_IGN); Review Comment: I don't understand this because Windows does not use signals at all. ########## maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java: ########## @@ -0,0 +1,417 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.named.ipc; + +import java.io.*; +import java.net.SocketAddress; +import java.nio.channels.ByteChannel; +import java.nio.channels.Channels; +import java.nio.channels.FileLock; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.eclipse.aether.named.ipc.IpcMessages.REQUEST_ACQUIRE; +import static org.eclipse.aether.named.ipc.IpcMessages.REQUEST_CLOSE; +import static org.eclipse.aether.named.ipc.IpcMessages.REQUEST_CONTEXT; +import static org.eclipse.aether.named.ipc.IpcMessages.REQUEST_STOP; +import static org.eclipse.aether.named.ipc.IpcMessages.RESPONSE_ACQUIRE; +import static org.eclipse.aether.named.ipc.IpcMessages.RESPONSE_CLOSE; +import static org.eclipse.aether.named.ipc.IpcMessages.RESPONSE_CONTEXT; +import static org.eclipse.aether.named.ipc.IpcMessages.RESPONSE_STOP; + +/** + * Client side implementation. + * The client instance is bound to a given maven repository. + * + * @since 2.0.0 + */ +public class IpcClient { + + static final boolean IS_WINDOWS = + System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win"); + + volatile boolean initialized; + Path lockPath; + Path logPath; + Path syncPath; + SocketChannel socket; + DataOutputStream output; + DataInputStream input; + Thread receiver; + AtomicInteger requestId = new AtomicInteger(); + Map<Integer, CompletableFuture<List<String>>> responses = new ConcurrentHashMap<>(); + + IpcClient(Path lockPath, Path logPath, Path syncPath) { + this.lockPath = lockPath; + this.logPath = logPath; + this.syncPath = syncPath; + } + + void ensureInitialized() throws IOException { + if (!initialized) { + synchronized (this) { + if (!initialized) { + socket = createClient(); + ByteChannel wrapper = new ByteChannelWrapper(socket); + input = new DataInputStream(Channels.newInputStream(wrapper)); + output = new DataOutputStream(Channels.newOutputStream(wrapper)); + receiver = new Thread(this::receive); + receiver.setDaemon(true); + receiver.start(); + initialized = true; + } + } + } + } + + SocketChannel createClient() throws IOException { + String familyProp = System.getProperty(IpcServer.SYSTEM_PROP_FAMILY, IpcServer.DEFAULT_FAMILY); + SocketFamily family = familyProp != null ? SocketFamily.valueOf(familyProp) : SocketFamily.inet; + + Path lockPath = this.lockPath.toAbsolutePath().normalize(); + Path lockFile = + lockPath.resolve(".maven-resolver-ipc-lock-" + family.name().toLowerCase()); Review Comment: `Locale.ROOT` ########## maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcMessages.java: ########## @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.named.ipc; + +/** + * Constants used for the inter-process communication protocol. + * + * @since 2.0.0 + */ +public class IpcMessages { + + public static final String REQUEST_CONTEXT = "request-context"; + public static final String REQUEST_ACQUIRE = "request-acquire"; + public static final String REQUEST_CLOSE = "request-close"; + public static final String REQUEST_STOP = "request-stop"; + public static final String RESPONSE_CONTEXT = "response-context"; + public static final String RESPONSE_ACQUIRE = "response-acquire"; + public static final String RESPONSE_CLOSE = "response-close"; + public static final String RESPONSE_STOP = "response-stop"; +} Review Comment: I wonder wether an enum would be better?! ########## maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java: ########## @@ -0,0 +1,482 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.named.ipc; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.channels.ByteChannel; +import java.nio.channels.Channels; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Implementation of the server side. + * The server instance is bound to a given maven repository. + * + * @since 2.0.0 + */ +public class IpcServer { + /** + * Should the IPC server not fork? (i.e. for testing purposes) + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_NO_FORK} + */ + public static final String SYSTEM_PROP_NO_FORK = "aether.named.ipc.nofork"; + + public static final boolean DEFAULT_NO_FORK = false; + + /** + * IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Integer} + * @configurationDefaultValue {@link #DEFAULT_IDLE_TIMEOUT} + */ + public static final String SYSTEM_PROP_IDLE_TIMEOUT = "aether.named.ipc.idleTimeout"; + + public static final int DEFAULT_IDLE_TIMEOUT = 60; + + /** + * IPC socket family to use. + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.String} + * @configurationDefaultValue {@link #DEFAULT_FAMILY} + */ + public static final String SYSTEM_PROP_FAMILY = "aether.named.ipc.family"; + + public static final String DEFAULT_FAMILY = "inet"; + + /** + * Should the IPC server not use native executable? + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_NO_NATIVE} + */ + public static final String SYSTEM_PROP_NO_NATIVE = "aether.named.ipc.nonative"; + + public static final boolean DEFAULT_NO_NATIVE = false; + + /** + * Should the IPC server log debug messages? (i.e. for testing purposes) + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_DEBUG} + */ + public static final String SYSTEM_PROP_DEBUG = "aether.named.ipc.debug"; + + public static final boolean DEFAULT_DEBUG = false; + + private final ServerSocketChannel serverSocket; + private final Map<SocketChannel, Thread> clients = new HashMap<>(); + private final AtomicInteger counter = new AtomicInteger(); + private final Map<String, Lock> locks = new ConcurrentHashMap<>(); + private final Map<String, Context> contexts = new ConcurrentHashMap<>(); + private static final boolean DEBUG = + Boolean.parseBoolean(System.getProperty(SYSTEM_PROP_DEBUG, Boolean.toString(DEFAULT_DEBUG))); + private final long idleTimeout; + private volatile long lastUsed; + private volatile boolean closing; + + public IpcServer(SocketFamily family) throws IOException { + serverSocket = family.openServerSocket(); + long timeout = TimeUnit.SECONDS.toNanos(DEFAULT_IDLE_TIMEOUT); + String str = System.getProperty(SYSTEM_PROP_IDLE_TIMEOUT); + if (str != null) { + try { + TimeUnit unit = TimeUnit.SECONDS; + if (str.endsWith("ms")) { + unit = TimeUnit.MILLISECONDS; + str = str.substring(0, str.length() - 2); + } + long dur = Long.parseLong(str); + timeout = unit.toNanos(dur); + } catch (NumberFormatException e) { + error("Property " + SYSTEM_PROP_IDLE_TIMEOUT + " specified with invalid value: " + str, e); + } + } + idleTimeout = timeout; + } + + public static void main(String[] args) throws Exception { + // When spawning a new process, the child process is create within + // the same process group. This means that a few signals are sent + // to the whole group. This is the case for SIGINT (Ctrl-C) and + // SIGTSTP (Ctrl-Z) which are both sent to all the processed in the + // group when initiated from the controlling terminal. + // This is only a problem when the client creates the daemon, but + // without ignoring those signals, a client being interrupted will + // also interrupt and kill the daemon. + try { + sun.misc.Signal.handle(new sun.misc.Signal("INT"), sun.misc.SignalHandler.SIG_IGN); + if (IpcClient.IS_WINDOWS) { + sun.misc.Signal.handle(new sun.misc.Signal("TSTP"), sun.misc.SignalHandler.SIG_IGN); + } + } catch (Throwable t) { + error("Unable to ignore INT and TSTP signals", t); + } + + String family = args[0]; + String tmpAddress = args[1]; + String rand = args[2]; + + runServer(SocketFamily.valueOf(family), tmpAddress, rand); + } + + static IpcServer runServer(SocketFamily family, String tmpAddress, String rand) throws IOException { + IpcServer server = new IpcServer(family); + run(server::run, false); // this is one-off + String address = SocketFamily.toString(server.getLocalAddress()); + SocketAddress socketAddress = SocketFamily.fromString(tmpAddress); + try (SocketChannel socket = SocketChannel.open(socketAddress)) { + try (DataOutputStream dos = new DataOutputStream(Channels.newOutputStream(socket))) { + dos.writeUTF(rand); + dos.writeUTF(address); + dos.flush(); + } + } + + return server; + } + + private static void debug(String msg, Object... args) { + if (DEBUG) { + System.out.printf("[ipc] [debug] " + msg + "\n", args); Review Comment: `%n` > IPC Named Locks > --------------- > > Key: MRESOLVER-499 > URL: https://issues.apache.org/jira/browse/MRESOLVER-499 > Project: Maven Resolver > Issue Type: New Feature > Components: Resolver > Reporter: Tamas Cservenak > Assignee: Tamas Cservenak > Priority: Major > Fix For: 2.0.0, 2.0.0-alpha-9 > > > Create IPC named locks implementation. Depends on MRESOLVER-421. -- This message was sent by Atlassian Jira (v8.20.10#820010)