From 814b6732a49447281ef085de3ef8542c45508371 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 22 Feb 2024 16:51:43 +0100 Subject: [PATCH 01/11] [MRESOLVER-499] IPC Named locks Implementation of IPC Named Locks. Code originates from mvnd, this was the original locking it used, adapted to NamedLocks, that now became possible. --- https://issues.apache.org/jira/browse/MRESOLVER-499 --- maven-resolver-named-locks-ipc/pom.xml | 118 +++++ .../aether/named/ipc/ByteChannelWrapper.java | 58 +++ .../eclipse/aether/named/ipc/IpcClient.java | 417 +++++++++++++++ .../eclipse/aether/named/ipc/IpcMessages.java | 36 ++ .../aether/named/ipc/IpcNamedLock.java | 109 ++++ .../aether/named/ipc/IpcNamedLockFactory.java | 90 ++++ .../eclipse/aether/named/ipc/IpcServer.java | 482 ++++++++++++++++++ .../aether/named/ipc/SocketFamily.java | 111 ++++ .../src/site/markdown/index.md.vm | 22 + .../src/site/site.xml | 37 ++ .../aether/named/ipc/IpcAdapterIT.java | 36 ++ .../named/ipc/IpcNamedLockFactoryIT.java | 37 ++ .../NamedLockFactoryAdapterTestSupport.java | 244 +++++++++ .../ipc/NamedLockFactoryTestSupport.java | 210 ++++++++ pom.xml | 1 + src/site/markdown/configuration.md | 125 ++--- 16 files changed, 2073 insertions(+), 60 deletions(-) create mode 100644 maven-resolver-named-locks-ipc/pom.xml create mode 100644 maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/ByteChannelWrapper.java create mode 100644 maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java create mode 100644 maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcMessages.java create mode 100644 maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java create mode 100644 maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java create mode 100644 maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java create mode 100644 maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java create mode 100644 maven-resolver-named-locks-ipc/src/site/markdown/index.md.vm create mode 100644 maven-resolver-named-locks-ipc/src/site/site.xml create mode 100644 maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.java create mode 100644 maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java create mode 100644 maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/NamedLockFactoryAdapterTestSupport.java create mode 100644 maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/NamedLockFactoryTestSupport.java diff --git a/maven-resolver-named-locks-ipc/pom.xml b/maven-resolver-named-locks-ipc/pom.xml new file mode 100644 index 000000000..8a6adb023 --- /dev/null +++ b/maven-resolver-named-locks-ipc/pom.xml @@ -0,0 +1,118 @@ + + + + 4.0.0 + + + org.apache.maven.resolver + maven-resolver + 2.0.0-SNAPSHOT + + + maven-resolver-named-locks-ipc + + Maven Artifact Resolver Named Locks using IPC + A synchronization utility implementation using IPC. + + + 9 + org.apache.maven.resolver.named.ipc + ${Automatic-Module-Name} + + + + + org.apache.maven.resolver + maven-resolver-named-locks + provided + + + org.apache.maven.resolver + maven-resolver-util + provided + + + org.slf4j + slf4j-api + provided + + + javax.inject + javax.inject + provided + + + org.junit.jupiter + junit-jupiter-api + test + + + org.mockito + mockito-core + test + + + org.slf4j + slf4j-simple + test + + + org.apache.maven.resolver + maven-resolver-impl + test + + + + + + + org.eclipse.sisu + sisu-maven-plugin + + + biz.aQute.bnd + bnd-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + + + + run-its + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/ByteChannelWrapper.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/ByteChannelWrapper.java new file mode 100644 index 000000000..3b3c978e2 --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/ByteChannelWrapper.java @@ -0,0 +1,58 @@ +/* + * 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.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +/** + * Trivial ByteChannel wrapper to avoid the read/write synchronization which + * happens when the channel implements SelectableChannel. + * + * @since 2.0.0 + */ +public class ByteChannelWrapper implements ByteChannel { + + private final ByteChannel socket; + + public ByteChannelWrapper(ByteChannel socket) { + this.socket = socket; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return socket.read(dst); + } + + @Override + public int write(ByteBuffer src) throws IOException { + return socket.write(src); + } + + @Override + public boolean isOpen() { + return socket.isOpen(); + } + + @Override + public void close() throws IOException { + socket.close(); + } +} diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java new file mode 100644 index 000000000..8e5bae0af --- /dev/null +++ b/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>> 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()); + if (!Files.isRegularFile(lockFile)) { + if (!Files.isDirectory(lockFile.getParent())) { + Files.createDirectories(lockFile.getParent()); + } + } + + try (RandomAccessFile raf = new RandomAccessFile(lockFile.toFile(), "rw")) { + try (FileLock lock = raf.getChannel().lock()) { + String line = raf.readLine(); + if (line != null) { + try { + SocketAddress address = SocketFamily.fromString(line); + return SocketChannel.open(address); + } catch (IOException e) { + // ignore + } + } + + ServerSocketChannel ss = family.openServerSocket(); + String tmpaddr = SocketFamily.toString(ss.getLocalAddress()); + String rand = Long.toHexString(new Random().nextLong()); + String syncCmd = IS_WINDOWS ? "mvnd-sync.exe" : "mvnd-sync"; + + boolean debug = Boolean.parseBoolean( + System.getProperty(IpcServer.SYSTEM_PROP_DEBUG, Boolean.toString(IpcServer.DEFAULT_DEBUG))); + boolean noNative = Boolean.parseBoolean(System.getProperty( + IpcServer.SYSTEM_PROP_NO_NATIVE, Boolean.toString(IpcServer.DEFAULT_NO_NATIVE))); + if (!noNative) { + noNative = !Files.isExecutable(syncPath.resolve(syncCmd)); + } + Closeable close; + Path logFile = logPath.resolve("resolver-ipcsync-" + rand + ".log"); + List args = new ArrayList<>(); + if (noNative) { + boolean noFork = Boolean.parseBoolean(System.getProperty( + IpcServer.SYSTEM_PROP_NO_FORK, Boolean.toString(IpcServer.DEFAULT_NO_FORK))); + if (noFork) { + IpcServer server = IpcServer.runServer(family, tmpaddr, rand); + close = server::close; + } else { + String javaHome = System.getenv("JAVA_HOME"); + if (javaHome == null) { + javaHome = System.getProperty("java.home"); + } + String javaCmd = IS_WINDOWS ? "bin\\java.exe" : "bin/java"; + String java = Paths.get(javaHome) + .resolve(javaCmd) + .toAbsolutePath() + .toString(); + args.add(java); + String classpath = getJarPath(getClass()) + File.pathSeparator + getJarPath(IpcServer.class); + args.add("-cp"); + args.add(classpath); + String timeout = System.getProperty(IpcServer.SYSTEM_PROP_IDLE_TIMEOUT); + if (timeout != null) { + args.add("-D" + IpcServer.SYSTEM_PROP_IDLE_TIMEOUT + "=" + timeout); + } + args.add("-D" + IpcServer.SYSTEM_PROP_DEBUG + "=" + debug); + args.add(IpcServer.class.getName()); + args.add(family.name()); + args.add(tmpaddr); + args.add(rand); + ProcessBuilder processBuilder = new ProcessBuilder(); + ProcessBuilder.Redirect discard = ProcessBuilder.Redirect.to(logFile.toFile()); + Files.createDirectories(logPath); + Process process = processBuilder + .directory(lockFile.getParent().toFile()) + .command(args) + .redirectOutput(discard) + .redirectError(discard) + .start(); + close = process::destroyForcibly; + } + } else { + args.add(syncPath.resolve(syncCmd).toString()); + String timeout = System.getProperty(IpcServer.SYSTEM_PROP_IDLE_TIMEOUT); + if (timeout != null) { + args.add("-D" + IpcServer.SYSTEM_PROP_IDLE_TIMEOUT + "=" + timeout); + } + args.add("-D" + IpcServer.SYSTEM_PROP_DEBUG + "=" + debug); + args.add(family.name()); + args.add(tmpaddr); + args.add(rand); + ProcessBuilder processBuilder = new ProcessBuilder(); + ProcessBuilder.Redirect discard = ProcessBuilder.Redirect.to(logFile.toFile()); + Files.createDirectories(logPath); + Process process = processBuilder + .directory(lockFile.getParent().toFile()) + .command(args) + .redirectOutput(discard) + .redirectError(discard) + .start(); + close = process::destroyForcibly; + } + + ExecutorService es = Executors.newSingleThreadExecutor(); + Future future = es.submit(() -> { + SocketChannel s = ss.accept(); + DataInputStream dis = new DataInputStream(Channels.newInputStream(s)); + String rand2 = dis.readUTF(); + String addr2 = dis.readUTF(); + return new String[] {rand2, addr2}; + }); + String[] res; + try { + res = future.get(5, TimeUnit.SECONDS); + } catch (Exception e) { + try (PrintWriter writer = new PrintWriter(new FileWriter(logFile.toFile(), true))) { + writer.println("Arguments:"); + args.forEach(writer::println); + writer.println(); + writer.println("Exception:"); + e.printStackTrace(writer); + } + close.close(); + throw e; + } finally { + es.shutdownNow(); + ss.close(); + } + if (!Objects.equals(rand, res[0])) { + close.close(); + throw new IllegalStateException("IpcServer did not respond with the correct random"); + } + + SocketAddress addr = SocketFamily.fromString(res[1]); + SocketChannel socket = SocketChannel.open(addr); + + raf.seek(0); + raf.writeBytes(res[1] + "\n"); + return socket; + } catch (Exception e) { + throw new RuntimeException("Unable to create and connect to lock server", e); + } + } + } + + private String getJarPath(Class clazz) { + String classpath; + String className = clazz.getName().replace('.', '/') + ".class"; + String url = clazz.getClassLoader().getResource(className).toString(); + if (url.startsWith("jar:")) { + url = url.substring("jar:".length(), url.indexOf("!/")); + if (url.startsWith("file:")) { + classpath = url.substring("file:".length()); + } else { + throw new IllegalStateException(); + } + } else if (url.startsWith("file:")) { + classpath = url.substring("file:".length(), url.indexOf(className)); + } else { + throw new IllegalStateException(); + } + if (IS_WINDOWS) { + if (classpath.startsWith("/")) { + classpath = classpath.substring(1); + } + classpath = classpath.replace('/', '\\'); + } + + return classpath; + } + + void receive() { + try { + while (true) { + int id = input.readInt(); + int sz = input.readInt(); + List s = new ArrayList<>(sz); + for (int i = 0; i < sz; i++) { + s.add(input.readUTF()); + } + CompletableFuture> f = responses.remove(id); + if (f == null || s.isEmpty()) { + throw new IllegalStateException("Protocol error"); + } + f.complete(s); + } + } catch (EOFException e) { + // server is stopped; just quit + } catch (Exception e) { + close(e); + } + } + + List send(List request, long time, TimeUnit unit) throws TimeoutException, IOException { + ensureInitialized(); + int id = requestId.incrementAndGet(); + CompletableFuture> response = new CompletableFuture<>(); + responses.put(id, response); + synchronized (output) { + output.writeInt(id); + output.writeInt(request.size()); + for (String s : request) { + output.writeUTF(s); + } + output.flush(); + } + try { + return response.get(time, unit); + } catch (InterruptedException e) { + throw (IOException) new InterruptedIOException("Interrupted").initCause(e); + } catch (ExecutionException e) { + throw new IOException("Execution error", e); + } + } + + void close() { + close(new IOException("Closing")); + } + + synchronized void close(Throwable e) { + if (socket != null) { + try { + socket.close(); + } catch (IOException t) { + e.addSuppressed(t); + } + socket = null; + input = null; + output = null; + } + if (receiver != null) { + receiver.interrupt(); + try { + receiver.join(1000); + } catch (InterruptedException t) { + e.addSuppressed(t); + } + } + responses.values().forEach(f -> f.completeExceptionally(e)); + responses.clear(); + } + + String newContext(boolean shared, long time, TimeUnit unit) throws TimeoutException { + RuntimeException error = new RuntimeException("Unable to create new sync context"); + for (int i = 0; i < 2; i++) { + try { + List response = send(Arrays.asList(REQUEST_CONTEXT, Boolean.toString(shared)), time, unit); + if (response.size() != 2 || !RESPONSE_CONTEXT.equals(response.get(0))) { + throw new IOException("Unexpected response: " + response); + } + return response.get(1); + } catch (TimeoutException e) { + throw e; + } catch (Exception e) { + close(e); + error.addSuppressed(e); + } + } + throw error; + } + + void lock(String contextId, Collection keys, long time, TimeUnit unit) throws TimeoutException { + try { + List req = new ArrayList<>(keys.size() + 2); + req.add(REQUEST_ACQUIRE); + req.add(contextId); + req.addAll(keys); + List response = send(req, time, unit); + if (response.size() != 1 || !RESPONSE_ACQUIRE.equals(response.get(0))) { + throw new IOException("Unexpected response: " + response); + } + } catch (TimeoutException e) { + throw e; + } catch (Exception e) { + close(e); + throw new RuntimeException("Unable to perform lock (contextId = " + contextId + ")", e); + } + } + + void unlock(String contextId) { + try { + List response = + send(Arrays.asList(REQUEST_CLOSE, contextId), Long.MAX_VALUE, TimeUnit.MILLISECONDS); + if (response.size() != 1 || !RESPONSE_CLOSE.equals(response.get(0))) { + throw new IOException("Unexpected response: " + response); + } + } catch (Exception e) { + close(e); + throw new RuntimeException("Unable to unlock (contextId = " + contextId + ")", e); + } + } + + /** + * To be used in tests to stop server immediately. Should not be used outside of tests. + */ + void stopServer() { + try { + List response = send(Arrays.asList(REQUEST_STOP), Long.MAX_VALUE, TimeUnit.MILLISECONDS); + if (response.size() != 1 || !RESPONSE_STOP.equals(response.get(0))) { + throw new IOException("Unexpected response: " + response); + } + } catch (Exception e) { + close(e); + throw new RuntimeException("Unable to stop server", e); + } + } + + @Override + public String toString() { + return "IpcClient{" + + "lockPath=" + lockPath + "," + + "syncServerPath=" + syncPath + "," + + "address='" + getAddress() + "'}"; + } + + private String getAddress() { + try { + return SocketFamily.toString(socket.getLocalAddress()); + } catch (IOException e) { + return "[not bound]"; + } + } +} diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcMessages.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcMessages.java new file mode 100644 index 000000000..c17fe1298 --- /dev/null +++ b/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"; +} diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java new file mode 100644 index 000000000..01107f96c --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java @@ -0,0 +1,109 @@ +/* + * 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.util.ArrayDeque; +import java.util.Collection; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.aether.named.NamedLockKey; +import org.eclipse.aether.named.support.LockUpgradeNotSupportedException; +import org.eclipse.aether.named.support.NamedLockSupport; + +/** + * IPC named locks. + * + * @since 2.0.0 + */ +class IpcNamedLock extends NamedLockSupport { + + final IpcClient client; + final Collection keys; + final ThreadLocal> contexts; + + IpcNamedLock(NamedLockKey key, IpcNamedLockFactory factory, IpcClient client, Collection keys) { + super(key, factory); + this.client = client; + this.keys = keys; + this.contexts = ThreadLocal.withInitial(ArrayDeque::new); + } + + @Override + public boolean doLockShared(long time, TimeUnit unit) { + ArrayDeque contexts = this.contexts.get(); + if (!contexts.isEmpty()) { + contexts.push(new Ctx(false, null, true)); + return true; + } + try { + String contextId = client.newContext(true, time, unit); + client.lock(Objects.requireNonNull(contextId), keys, time, unit); + contexts.push(new Ctx(true, contextId, true)); + return true; + } catch (TimeoutException e) { + return false; + } + } + + @Override + public boolean doLockExclusively(long time, TimeUnit unit) { + ArrayDeque contexts = this.contexts.get(); + if (contexts.stream().anyMatch(c -> c.shared)) { + throw new LockUpgradeNotSupportedException(this); + } + if (!contexts.isEmpty()) { + contexts.push(new Ctx(false, null, false)); + return true; + } + try { + String contextId = client.newContext(false, time, unit); + client.lock(Objects.requireNonNull(contextId), keys, time, unit); + contexts.push(new Ctx(true, contextId, false)); + return true; + } catch (TimeoutException e) { + return false; + } + } + + @Override + public void doUnlock() { + ArrayDeque contexts = this.contexts.get(); + if (contexts.isEmpty()) { + throw new IllegalStateException("improper boxing"); + } + Ctx ctx = contexts.pop(); + if (ctx.acted) { + client.unlock(ctx.contextId); + } + } + + private static final class Ctx { + private final boolean acted; + private final String contextId; + private final boolean shared; + + private Ctx(boolean acted, String contextId, boolean shared) { + this.acted = acted; + this.contextId = contextId; + this.shared = shared; + } + } +} diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java new file mode 100644 index 000000000..276b740f0 --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java @@ -0,0 +1,90 @@ +/* + * 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 javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.stream.Collectors; + +import org.eclipse.aether.named.NamedLock; +import org.eclipse.aether.named.NamedLockKey; +import org.eclipse.aether.named.support.NamedLockFactorySupport; +import org.eclipse.aether.named.support.NamedLockSupport; +import org.eclipse.aether.util.StringDigestUtil; + +/** + * IPC named locks factory. + * + * @since 2.0.0 + */ +@Singleton +@Named(IpcNamedLockFactory.NAME) +public class IpcNamedLockFactory extends NamedLockFactorySupport { + public static final String NAME = "ipc"; + + private final Path ipcHome; + + private final Path repository; + + private final Path logPath; + + private final Path syncPath; + + protected final IpcClient client; + + @Inject + public IpcNamedLockFactory() { + this.ipcHome = Paths.get(System.getProperty("user.home")).resolve(".ipc-sync"); + this.repository = ipcHome.resolve("repository"); + this.logPath = ipcHome.resolve("log"); + this.syncPath = null; + this.client = new IpcClient(repository, logPath, syncPath); + } + + @Override + protected NamedLock doGetLock(Collection keys) { + StringDigestUtil sha1 = StringDigestUtil.sha1(); + keys.forEach(k -> sha1.update(k.name())); + NamedLockKey key = NamedLockKey.of( + sha1.digest(), + keys.stream() + .map(NamedLockKey::resources) + .flatMap(Collection::stream) + .collect(Collectors.toList())); + return getLockAndRefTrack( + key, + () -> new IpcNamedLock( + key, this, client, keys.stream().map(NamedLockKey::name).collect(Collectors.toList()))); + } + + @Override + protected NamedLockSupport createLock(NamedLockKey key) { + throw new IllegalStateException("should not get here"); + } + + @Override + protected void doShutdown() { + client.close(); + } +} diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java new file mode 100644 index 000000000..74be3555f --- /dev/null +++ b/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 clients = new HashMap<>(); + private final AtomicInteger counter = new AtomicInteger(); + private final Map locks = new ConcurrentHashMap<>(); + private final Map 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); + } + + private static void error(String msg, Throwable t) { + System.out.println("[ipc] [error] " + msg); + t.printStackTrace(System.out); + } + + private static void run(Runnable runnable, boolean daemon) { + Thread thread = new Thread(runnable); + if (daemon) { + thread.setDaemon(true); + } + thread.start(); + } + + public SocketAddress getLocalAddress() throws IOException { + return serverSocket.getLocalAddress(); + } + + public void run() { + try { + info("IpcServer started at %s", getLocalAddress().toString()); + use(); + run(this::expirationCheck, true); + while (!closing) { + SocketChannel socket = this.serverSocket.accept(); + run(() -> client(socket), false); + } + } catch (Throwable t) { + if (!closing) { + error("Error running sync server loop", t); + } + } + } + + private void client(SocketChannel socket) { + int c; + synchronized (clients) { + clients.put(socket, Thread.currentThread()); + c = clients.size(); + } + info("New client connected (%d connected)", c); + use(); + Map clientContexts = new ConcurrentHashMap<>(); + try { + ByteChannel wrapper = new ByteChannelWrapper(socket); + DataInputStream input = new DataInputStream(Channels.newInputStream(wrapper)); + DataOutputStream output = new DataOutputStream(Channels.newOutputStream(wrapper)); + while (!closing) { + int requestId = input.readInt(); + int sz = input.readInt(); + List request = new ArrayList<>(sz); + for (int i = 0; i < sz; i++) { + request.add(input.readUTF()); + } + if (request.isEmpty()) { + throw new IOException("Received invalid request"); + } + use(); + String contextId; + Context context; + String command = request.remove(0); + switch (command) { + case IpcMessages.REQUEST_CONTEXT: + if (request.size() != 1) { + throw new IOException("Expected one argument for " + command + " but got " + request); + } + boolean shared = Boolean.parseBoolean(request.remove(0)); + context = new Context(shared); + contexts.put(context.id, context); + clientContexts.put(context.id, context); + synchronized (output) { + debug("Created context %s", context.id); + output.writeInt(requestId); + output.writeInt(2); + output.writeUTF(IpcMessages.RESPONSE_CONTEXT); + output.writeUTF(context.id); + output.flush(); + } + break; + case IpcMessages.REQUEST_ACQUIRE: + if (request.size() < 1) { + throw new IOException( + "Expected at least one argument for " + command + " but got " + request); + } + contextId = request.remove(0); + context = contexts.get(contextId); + if (context == null) { + throw new IOException( + "Unknown context: " + contextId + ". Known contexts = " + contexts.keySet()); + } + context.lock(request).thenRun(() -> { + try { + synchronized (output) { + debug("Locking in context %s", context.id); + output.writeInt(requestId); + output.writeInt(1); + output.writeUTF(IpcMessages.RESPONSE_ACQUIRE); + output.flush(); + } + } catch (IOException e) { + try { + socket.close(); + } catch (IOException ioException) { + e.addSuppressed(ioException); + } + error("Error writing lock response", e); + } + }); + break; + case IpcMessages.REQUEST_CLOSE: + if (request.size() != 1) { + throw new IOException("Expected one argument for " + command + " but got " + request); + } + contextId = request.remove(0); + context = contexts.remove(contextId); + clientContexts.remove(contextId); + if (context == null) { + throw new IOException( + "Unknown context: " + contextId + ". Known contexts = " + contexts.keySet()); + } + context.unlock(); + synchronized (output) { + debug("Closing context %s", context.id); + output.writeInt(requestId); + output.writeInt(1); + output.writeUTF(IpcMessages.RESPONSE_CLOSE); + output.flush(); + } + break; + case IpcMessages.REQUEST_STOP: + if (request.size() != 0) { + throw new IOException("Expected zero argument for " + command + " but got " + request); + } + synchronized (output) { + debug("Stopping server"); + output.writeInt(requestId); + output.writeInt(1); + output.writeUTF(IpcMessages.RESPONSE_STOP); + output.flush(); + } + close(); + break; + default: + throw new IOException("Unknown request: " + request.get(0)); + } + } + } catch (Throwable t) { + if (!closing) { + error("Error processing request", t); + } + } finally { + if (!closing) { + info("Client disconnecting..."); + } + clientContexts.values().forEach(context -> { + contexts.remove(context.id); + context.unlock(); + }); + try { + socket.close(); + } catch (IOException ioException) { + // ignore + } + synchronized (clients) { + clients.remove(socket); + c = clients.size(); + } + if (!closing) { + info("%d clients left", c); + } + } + } + + private void use() { + lastUsed = System.nanoTime(); + } + + private void expirationCheck() { + while (true) { + long current = System.nanoTime(); + long left = (lastUsed + idleTimeout) - current; + if (left < 0) { + info("IpcServer expired, closing"); + close(); + break; + } else { + try { + Thread.sleep(TimeUnit.NANOSECONDS.toMillis(left)); + } catch (InterruptedException e) { + info("IpcServer expiration check interrupted, closing"); + close(); + break; + } + } + } + } + + void close() { + closing = true; + try { + serverSocket.close(); + } catch (IOException e) { + error("Error closing server socket", e); + } + clients.forEach((s, t) -> { + try { + s.close(); + } catch (IOException e) { + // ignore + } + t.interrupt(); + }); + } + + static class Waiter { + final Context context; + final CompletableFuture future; + + Waiter(Context context, CompletableFuture future) { + this.context = context; + this.future = future; + } + } + + static class Lock { + + final String key; + + List holders; + List waiters; + + Lock(String key) { + this.key = key; + } + + public synchronized CompletableFuture lock(Context context) { + if (holders == null) { + holders = new ArrayList<>(); + } + if (holders.isEmpty() || holders.get(0).shared && context.shared) { + holders.add(context); + return CompletableFuture.completedFuture(null); + } + if (waiters == null) { + waiters = new ArrayList<>(); + } + + CompletableFuture future = new CompletableFuture<>(); + waiters.add(new Waiter(context, future)); + return future; + } + + public synchronized void unlock(Context context) { + if (holders.remove(context)) { + while (waiters != null + && !waiters.isEmpty() + && (holders.isEmpty() || holders.get(0).shared && waiters.get(0).context.shared)) { + Waiter waiter = waiters.remove(0); + holders.add(waiter.context); + waiter.future.complete(null); + } + } else if (waiters != null) { + for (Iterator it = waiters.iterator(); it.hasNext(); ) { + Waiter waiter = it.next(); + if (waiter.context == context) { + it.remove(); + waiter.future.cancel(false); + } + } + } + } + } + + class Context { + + final String id; + final boolean shared; + final List locks = new CopyOnWriteArrayList<>(); + + Context(boolean shared) { + this.id = String.format("%08x", counter.incrementAndGet()); + this.shared = shared; + } + + public CompletableFuture lock(List keys) { + locks.addAll(keys); + CompletableFuture[] futures = keys.stream() + .map(k -> IpcServer.this.locks.computeIfAbsent(k, Lock::new)) + .map(l -> l.lock(this)) + .toArray(CompletableFuture[]::new); + return CompletableFuture.allOf(futures); + } + + public void unlock() { + locks.stream() + .map(k -> IpcServer.this.locks.computeIfAbsent(k, Lock::new)) + .forEach(l -> l.unlock(this)); + } + } +} diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java new file mode 100644 index 000000000..88d61aad1 --- /dev/null +++ b/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:")) { + String s = str.substring("inet:".length()); + int ic = s.lastIndexOf(':'); + String ia = s.substring(0, ic); + int is = ia.indexOf('/'); + String h = ia.substring(0, is); + String a = ia.substring(is + 1); + String p = s.substring(ic + 1); + InetAddress addr; + if ("".equals(a)) { + return InetSocketAddress.createUnresolved(h, Integer.parseInt(p)); + } else { + if (a.indexOf('.') > 0) { + String[] as = a.split("\\."); + if (as.length != 4) { + throw new IllegalArgumentException("Unsupported socket address: '" + str + "'"); + } + byte[] ab = new byte[4]; + for (int i = 0; i < 4; i++) { + ab[i] = (byte) Integer.parseInt(as[i]); + } + try { + addr = InetAddress.getByAddress(h.isEmpty() ? null : h, ab); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Unsupported address: " + str, e); + } + } else { + throw new IllegalArgumentException("Unsupported address: " + str); + } + return new InetSocketAddress(addr, Integer.parseInt(p)); + } + } else { + throw new IllegalArgumentException("Unsupported socket address: '" + str + "'"); + } + } + + public static String toString(SocketAddress address) { + if (familyOf(address) == SocketFamily.inet) { + InetSocketAddress isa = (InetSocketAddress) address; + String host = isa.getHostString(); + InetAddress addr = isa.getAddress(); + int port = isa.getPort(); + String formatted; + if (addr == null) { + formatted = host + "/"; + } else { + formatted = addr.toString(); + if (addr instanceof Inet6Address) { + int i = formatted.lastIndexOf("/"); + formatted = formatted.substring(0, i + 1) + "[" + formatted.substring(i + 1) + "]"; + } + } + return "inet:" + formatted + ":" + port; + } + throw new IllegalArgumentException("Unsupported socket address: '" + address + "'"); + } + + public static SocketFamily familyOf(SocketAddress address) { + if (address instanceof InetSocketAddress) { + return SocketFamily.inet; + } else { + throw new IllegalArgumentException("Unsupported socket address '" + address + "'"); + } + } +} diff --git a/maven-resolver-named-locks-ipc/src/site/markdown/index.md.vm b/maven-resolver-named-locks-ipc/src/site/markdown/index.md.vm new file mode 100644 index 000000000..8a608cc7e --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/site/markdown/index.md.vm @@ -0,0 +1,22 @@ +${esc.hash} Named Locks using IPC + + + +This module implement named locks using IPC. diff --git a/maven-resolver-named-locks-ipc/src/site/site.xml b/maven-resolver-named-locks-ipc/src/site/site.xml new file mode 100644 index 000000000..e443fb0ad --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/site/site.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + diff --git a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.java b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.java new file mode 100644 index 000000000..a17b63012 --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.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; + +import org.junit.jupiter.api.BeforeAll; + +public class IpcAdapterIT extends NamedLockFactoryAdapterTestSupport { + @BeforeAll + static void createNamedLockFactory() { + System.setProperty(IpcServer.SYSTEM_PROP_DEBUG, Boolean.TRUE.toString()); + System.setProperty(IpcServer.SYSTEM_PROP_NO_NATIVE, Boolean.TRUE.toString()); + setNamedLockFactory(new IpcNamedLockFactory() { + @Override + protected void doShutdown() { + client.stopServer(); + super.doShutdown(); + } + }); + } +} diff --git a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java new file mode 100644 index 000000000..bee8e354c --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java @@ -0,0 +1,37 @@ +/* + * 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 org.junit.jupiter.api.BeforeAll; + +public class IpcNamedLockFactoryIT extends NamedLockFactoryTestSupport { + @BeforeAll + static void createNamedLockFactory() { + System.setProperty(IpcServer.SYSTEM_PROP_DEBUG, Boolean.TRUE.toString()); + System.setProperty(IpcServer.SYSTEM_PROP_NO_NATIVE, Boolean.TRUE.toString()); + namedLockFactory = new IpcNamedLockFactory() { + @Override + protected void doShutdown() { + client.stopServer(); + super.doShutdown(); + } + }; + ; + } +} diff --git a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/NamedLockFactoryAdapterTestSupport.java b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/NamedLockFactoryAdapterTestSupport.java new file mode 100644 index 000000000..d5212f612 --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/NamedLockFactoryAdapterTestSupport.java @@ -0,0 +1,244 @@ +/* + * 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.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.SyncContext; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.internal.impl.synccontext.named.DiscriminatingNameMapper; +import org.eclipse.aether.internal.impl.synccontext.named.GAVNameMapper; +import org.eclipse.aether.internal.impl.synccontext.named.NamedLockFactoryAdapter; +import org.eclipse.aether.named.NamedLockFactory; +import org.eclipse.aether.named.support.LockUpgradeNotSupportedException; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.spi.synccontext.SyncContextFactory; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * UT support for {@link SyncContextFactory}. + */ +public abstract class NamedLockFactoryAdapterTestSupport { + private static final long ADAPTER_TIME = 100L; + + private static final TimeUnit ADAPTER_TIME_UNIT = TimeUnit.MILLISECONDS; + + /** + * Subclass should populate this field, using {@link #setNamedLockFactory(NamedLockFactory)}, but subclass + * must take care of proper cleanup as well, if needed! + */ + private static NamedLockFactoryAdapter adapter; + + private RepositorySystemSession session; + + protected static void setNamedLockFactory(final NamedLockFactory namedLockFactory) { + adapter = new NamedLockFactoryAdapter(new DiscriminatingNameMapper(GAVNameMapper.gav()), namedLockFactory); + } + + @AfterAll + static void cleanup() { + if (adapter != null) { + adapter.getNamedLockFactory().shutdown(); + } + } + + @BeforeEach + void before() throws IOException { + Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir"))); // hack for Surefire + LocalRepository localRepository = + new LocalRepository(Files.createTempDirectory("test").toFile()); + session = mock(RepositorySystemSession.class); + when(session.getLocalRepository()).thenReturn(localRepository); + HashMap config = new HashMap<>(); + config.put(NamedLockFactoryAdapter.CONFIG_PROP_TIME, String.valueOf(ADAPTER_TIME)); + config.put(NamedLockFactoryAdapter.CONFIG_PROP_TIME_UNIT, ADAPTER_TIME_UNIT.name()); + when(session.getConfigProperties()).thenReturn(config); + } + + @Test + void justCreateAndClose() { + adapter.newInstance(session, false).close(); + } + + @Test + void justAcquire() { + try (SyncContext syncContext = adapter.newInstance(session, false)) { + syncContext.acquire( + Arrays.asList( + new DefaultArtifact("groupId:artifactId:1.0"), + new DefaultArtifact("groupId:artifactId:1.1")), + null); + } + } + + @Test + @Timeout(5) + public void sharedAccess() throws InterruptedException { + CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners + CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers + Thread t1 = new Thread(new Access(true, winners, losers, adapter, session, null)); + Thread t2 = new Thread(new Access(true, winners, losers, adapter, session, null)); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + winners.await(); + losers.await(); + } + + @Test + @Timeout(5) + public void exclusiveAccess() throws InterruptedException { + CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner + CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser + Thread t1 = new Thread(new Access(false, winners, losers, adapter, session, null)); + Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null)); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + winners.await(); + losers.await(); + } + + @Test + @Timeout(5) + public void mixedAccess() throws InterruptedException { + CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner + CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser + Thread t1 = new Thread(new Access(true, winners, losers, adapter, session, null)); + Thread t2 = new Thread(new Access(false, winners, losers, adapter, session, null)); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + winners.await(); + losers.await(); + } + + @Test + @Timeout(5) + public void nestedSharedShared() throws InterruptedException { + CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners + CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers + Thread t1 = new Thread(new Access( + true, winners, losers, adapter, session, new Access(true, winners, losers, adapter, session, null))); + t1.start(); + t1.join(); + winners.await(); + losers.await(); + } + + @Test + @Timeout(5) + public void nestedExclusiveShared() throws InterruptedException { + CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners + CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers + Thread t1 = new Thread(new Access( + false, winners, losers, adapter, session, new Access(true, winners, losers, adapter, session, null))); + t1.start(); + t1.join(); + winners.await(); + losers.await(); + } + + @Test + @Timeout(5) + public void nestedExclusiveExclusive() throws InterruptedException { + CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners + CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers + Thread t1 = new Thread(new Access( + false, winners, losers, adapter, session, new Access(false, winners, losers, adapter, session, null))); + t1.start(); + t1.join(); + winners.await(); + losers.await(); + } + + @Test + @Timeout(5) + public void nestedSharedExclusive() throws InterruptedException { + CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner (outer) + CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser (inner) + Thread t1 = new Thread(new Access( + true, winners, losers, adapter, session, new Access(false, winners, losers, adapter, session, null))); + t1.start(); + t1.join(); + winners.await(); + losers.await(); + } + + private static class Access implements Runnable { + final boolean shared; + final CountDownLatch winner; + final CountDownLatch loser; + final NamedLockFactoryAdapter adapter; + final RepositorySystemSession session; + final Access chained; + + public Access( + boolean shared, + CountDownLatch winner, + CountDownLatch loser, + NamedLockFactoryAdapter adapter, + RepositorySystemSession session, + Access chained) { + this.shared = shared; + this.winner = winner; + this.loser = loser; + this.adapter = adapter; + this.session = session; + this.chained = chained; + } + + @Override + public void run() { + try { + try (SyncContext syncContext = adapter.newInstance(session, shared)) { + syncContext.acquire( + Arrays.asList( + new DefaultArtifact("groupId:artifactId:1.0"), + new DefaultArtifact("groupId:artifactId:1.1")), + null); + winner.countDown(); + if (chained != null) { + chained.run(); + } + loser.await(); + } catch (IllegalStateException | LockUpgradeNotSupportedException e) { + loser.countDown(); + winner.await(); + } + } catch (InterruptedException e) { + fail("interrupted"); + } + } + } +} diff --git a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/NamedLockFactoryTestSupport.java b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/NamedLockFactoryTestSupport.java new file mode 100644 index 000000000..a1ff63408 --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/NamedLockFactoryTestSupport.java @@ -0,0 +1,210 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.aether.named.NamedLock; +import org.eclipse.aether.named.NamedLockFactory; +import org.eclipse.aether.named.NamedLockKey; +import org.eclipse.aether.named.support.LockUpgradeNotSupportedException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * UT support for {@link NamedLockFactory}. + */ +public abstract class NamedLockFactoryTestSupport { + protected static NamedLockFactory namedLockFactory; + + @AfterAll + static void cleanup() { + if (namedLockFactory != null) { + namedLockFactory.shutdown(); + } + } + + @Test + void refCounting(TestInfo testInfo) { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + try (NamedLock one = namedLockFactory.getLock(keys); + NamedLock two = namedLockFactory.getLock(keys)) { + assertSame(one, two); + one.close(); + two.close(); + + try (NamedLock three = namedLockFactory.getLock(keys)) { + assertNotSame(three, two); + } + } + } + + @Test + void unlockWoLock(TestInfo testInfo) { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + try (NamedLock one = namedLockFactory.getLock(keys)) { + assertThrows(IllegalStateException.class, one::unlock); + } + } + + @Test + void wwBoxing(TestInfo testInfo) throws InterruptedException { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + try (NamedLock one = namedLockFactory.getLock(keys)) { + assertTrue(one.lockExclusively(100L, TimeUnit.MILLISECONDS)); + assertTrue(one.lockExclusively(100L, TimeUnit.MILLISECONDS)); + one.unlock(); + one.unlock(); + } + } + + @Test + void rrBoxing(TestInfo testInfo) throws InterruptedException { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + try (NamedLock one = namedLockFactory.getLock(keys)) { + assertTrue(one.lockShared(100L, TimeUnit.MILLISECONDS)); + assertTrue(one.lockShared(100L, TimeUnit.MILLISECONDS)); + one.unlock(); + one.unlock(); + } + } + + @Test + void wrBoxing(TestInfo testInfo) throws InterruptedException { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + try (NamedLock one = namedLockFactory.getLock(keys)) { + assertTrue(one.lockExclusively(100L, TimeUnit.MILLISECONDS)); + assertTrue(one.lockShared(100L, TimeUnit.MILLISECONDS)); + one.unlock(); + one.unlock(); + } + } + + @Test + void rwBoxing(TestInfo testInfo) throws InterruptedException { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + try (NamedLock one = namedLockFactory.getLock(keys)) { + assertTrue(one.lockShared(100L, TimeUnit.MILLISECONDS)); + try { + one.lockExclusively(100L, TimeUnit.MILLISECONDS); + fail("lock upgrade should be not supported"); + } catch (LockUpgradeNotSupportedException e) { + // good + } + one.unlock(); + } + } + + @Test + @Timeout(5) + public void sharedAccess(TestInfo testInfo) throws InterruptedException { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + CountDownLatch winners = new CountDownLatch(2); // we expect 2 winner + CountDownLatch losers = new CountDownLatch(0); // we expect 0 loser + Thread t1 = new Thread(new Access(namedLockFactory, keys, true, winners, losers)); + Thread t2 = new Thread(new Access(namedLockFactory, keys, true, winners, losers)); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + winners.await(); + losers.await(); + } + + @Test + @Timeout(5) + public void exclusiveAccess(TestInfo testInfo) throws InterruptedException { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner + CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser + Thread t1 = new Thread(new Access(namedLockFactory, keys, false, winners, losers)); + Thread t2 = new Thread(new Access(namedLockFactory, keys, false, winners, losers)); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + winners.await(); + losers.await(); + } + + @Test + @Timeout(5) + public void mixedAccess(TestInfo testInfo) throws InterruptedException { + final Collection keys = Collections.singleton(NamedLockKey.of(testInfo.getDisplayName())); + CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner + CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser + Thread t1 = new Thread(new Access(namedLockFactory, keys, true, winners, losers)); + Thread t2 = new Thread(new Access(namedLockFactory, keys, false, winners, losers)); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + winners.await(); + losers.await(); + } + + private static class Access implements Runnable { + final NamedLockFactory namedLockFactory; + final Collection keys; + final boolean shared; + final CountDownLatch winner; + final CountDownLatch loser; + + public Access( + NamedLockFactory namedLockFactory, + Collection keys, + boolean shared, + CountDownLatch winner, + CountDownLatch loser) { + this.namedLockFactory = namedLockFactory; + this.keys = keys; + this.shared = shared; + this.winner = winner; + this.loser = loser; + } + + @Override + public void run() { + try (NamedLock lock = namedLockFactory.getLock(keys)) { + if (shared + ? lock.lockShared(100L, TimeUnit.MILLISECONDS) + : lock.lockExclusively(100L, TimeUnit.MILLISECONDS)) { + try { + winner.countDown(); + loser.await(); + } finally { + lock.unlock(); + } + } else { + loser.countDown(); + winner.await(); + } + } catch (InterruptedException e) { + fail(e.getMessage()); + } + } + } +} diff --git a/pom.xml b/pom.xml index 12267d17f..28e2c6690 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ maven-resolver-util maven-resolver-named-locks maven-resolver-named-locks-hazelcast + maven-resolver-named-locks-ipc maven-resolver-named-locks-redisson maven-resolver-impl maven-resolver-test-util diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index c279cda4c..23a4b2b4c 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -65,66 +65,71 @@ under the License. | 38. | `"aether.named.file-lock.attempts"` | `java.lang.Integer` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to implement similar fix as referenced JDK bug report: retry and hope the best. Default value is 5 attempts (will retry 4 times). | `5` | 1.7.3 | No | Java System Properties | | 39. | `"aether.named.file-lock.deleteLockFiles"` | `java.lang.Boolean` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to have it removed from effective flags, at the cost that lockfile directory becomes crowded with 0 byte sized lock files that are never cleaned up. Default value is on non-Windows OS. See JDK-8252883 for Windows related bug. Users on Windows can still force "delete on close" by explicitly setting this property to . | `true` | 1.7.3 | No | Java System Properties | | 40. | `"aether.named.file-lock.sleepMillis"` | `java.lang.Long` | Tweak: When used, the amount of milliseconds to sleep between subsequent retries. Default value is 50 milliseconds. | `50` | 1.7.3 | No | Java System Properties | -| 41. | `"aether.offline.hosts"` | `java.lang.String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | -| 42. | `"aether.offline.protocols"` | `java.lang.String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | -| 43. | `"aether.priority.cached"` | `java.lang.Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | -| 44. | `"aether.priority.implicit"` | `java.lang.Boolean` | A flag indicating whether the priorities of pluggable extensions are implicitly given by their iteration order such that the first extension has the highest priority. If set, an extension's built-in priority as well as any corresponding configuration properties are ignored when searching for a suitable implementation among the available extensions. This priority mode is meant for cases where the application will present/inject extensions in the desired search order. | `false` | | No | Session Configuration | -| 45. | `"aether.remoteRepositoryFilter.groupId"` | `java.lang.Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | -| 46. | `"aether.remoteRepositoryFilter.groupId.basedir"` | `java.lang.String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | -| 47. | `"aether.remoteRepositoryFilter.groupId.record"` | `java.lang.Boolean` | Should filter go into "record" mode (and collect encountered artifacts)? | `false` | 1.9.0 | No | Session Configuration | -| 48. | `"aether.remoteRepositoryFilter.prefixes"` | `java.lang.Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | -| 49. | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `java.lang.String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | -| 50. | `"aether.snapshotFilter"` | `java.lang.Boolean` | The key in the repository session's used to store a flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | -| 51. | `"aether.syncContext.named.basedir.locksDir"` | `java.lang.String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | -| 52. | `"aether.syncContext.named.discriminating.discriminator"` | `java.lang.String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | -| 53. | `"aether.syncContext.named.discriminating.hostname"` | `java.lang.String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | -| 54. | `"aether.syncContext.named.factory"` | `java.lang.String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | -| 55. | `"aether.syncContext.named.hashing.depth"` | `java.lang.Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | -| 56. | `"aether.syncContext.named.nameMapper"` | `java.lang.String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | -| 57. | `"aether.syncContext.named.redisson.address"` | `java.lang.String` | Address of the Redis instance. Optional. | `"redis://localhost:6379"` | 2.0.0 | No | Java System Properties | -| 58. | `"aether.syncContext.named.redisson.configFile"` | `java.lang.String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | -| 59. | `"aether.syncContext.named.retry"` | `java.lang.Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | -| 60. | `"aether.syncContext.named.retry.wait"` | `java.lang.Long` | The amount of milliseconds to wait between retries on time-out. | `200` | 1.7.0 | No | Session Configuration | -| 61. | `"aether.syncContext.named.time"` | `java.lang.Long` | The maximum of time amount to be blocked to obtain lock. | `30` | 1.7.0 | No | Session Configuration | -| 62. | `"aether.syncContext.named.time.unit"` | `java.lang.String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | -| 63. | `"aether.system.dependencyVisitor"` | `java.lang.String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration | -| 64. | `"aether.transport.apache.https.cipherSuites"` | `java.lang.String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | -| 65. | `"aether.transport.apache.https.protocols"` | `java.lang.String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | -| 66. | `"aether.transport.apache.retryHandler.name"` | `java.lang.String` | The name of retryHandler, supported values are “standard”, that obeys RFC-2616, regarding idempotent methods, and “default” that considers requests w/o payload as idempotent. | `"standard"` | 2.0.0 | Yes | Session Configuration | -| 67. | `"aether.transport.apache.retryHandler.requestSentEnabled"` | `java.lang.Boolean` | Set to true if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | 2.0.0 | Yes | Session Configuration | -| 68. | `"aether.transport.apache.useSystemProperties"` | `java.lang.Boolean` | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is not recommended, better use documented ways of configuration instead. | `false` | 2.0.0 | Yes | Session Configuration | -| 69. | `"aether.transport.classpath.loader"` | `java.lang.ClassLoader` | The key in the repository session's used to store a from which resources should be retrieved. If unspecified, the of the current thread will be used. | - | | No | Session Configuration | -| 70. | `"aether.transport.http.connectTimeout"` | `java.lang.Integer` | The maximum amount of time (in milliseconds) to wait for a successful connection to a remote server. Non-positive values indicate no timeout. | `10000` | | Yes | Session Configuration | -| 71. | `"aether.transport.http.connectionMaxTtl"` | `java.lang.Integer` | Total time to live in seconds for an HTTP connection, after that time, the connection will be dropped (no matter for how long it was idle). | `300` | 1.9.8 | Yes | Session Configuration | -| 72. | `"aether.transport.http.credentialEncoding"` | `java.lang.String` | The encoding/charset to use when exchanging credentials with HTTP servers. Besides this general key, clients may also specify the encoding for a specific remote repository by appending the suffix to this key when storing the charset name. | `"ISO-8859-1"` | | Yes | Session Configuration | -| 73. | `"aether.transport.http.expectContinue"` | `java.lang.Boolean` | Boolean flag should the HTTP transport use expect-continue handshake for PUT requests. Not all transport support this option. This option may be needed for some broken HTTP servers. Default value corresponds to given transport default one (resolver does not override those), but if configuration IS given, it will replace given transport own default value. | - | 1.9.17 | Yes | Session Configuration | -| 74. | `"aether.transport.http.headers"` | `java.util.Map` | The request headers to use for HTTP-based repository connectors. The headers are specified using a , mapping a header name to its value. Besides this general key, clients may also specify headers for a specific remote repository by appending the suffix to this key when storing the headers map. The repository-specific headers map is supposed to be complete, i.e. is not merged with the general headers map. | - | | Yes | Session Configuration | -| 75. | `"aether.transport.http.localAddress"` | `java.lang.String` | The local address (interface) to use with HTTP transport. Not all transport supports this option. | - | 2.0.0 | Yes | Session Configuration | -| 76. | `"aether.transport.http.maxConnectionsPerRoute"` | `java.lang.Integer` | The maximum concurrent connections per route HTTP client is allowed to use. | `50` | 1.9.8 | Yes | Session Configuration | -| 77. | `"aether.transport.http.preemptiveAuth"` | `java.lang.Boolean` | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default, is disabled, as it is considered less secure. | `false` | 1.9.6 | Yes | Session Configuration | -| 78. | `"aether.transport.http.preemptivePutAuth"` | `java.lang.Boolean` | Boolean flag should the HTTP transport use preemptive-auth for PUT requests. Not all transport support this option. | `true` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | -| 79. | `"aether.transport.http.requestTimeout"` | `java.lang.Integer` | The maximum amount of time (in milliseconds) to wait for remaining data to arrive from a remote server. Note that this timeout does not restrict the overall duration of a request, it only restricts the duration of inactivity between consecutive data packets. Non-positive values indicate no timeout. | `1800000` | | Yes | Session Configuration | -| 80. | `"aether.transport.http.retryHandler.count"` | `java.lang.Integer` | The maximum number of times a request to a remote server should be retried in case of an error. | `3` | 1.9.6 | Yes | Session Configuration | -| 81. | `"aether.transport.http.retryHandler.interval"` | `java.lang.Long` | The initial retry interval in millis of request to a remote server should be waited in case of "too many requests" (HTTP codes 429 and 503). Accepts long as milliseconds. This value is used if remote server does not use header, in which case Server value is obeyed. | `5000` | 1.9.16 | Yes | Session Configuration | -| 82. | `"aether.transport.http.retryHandler.intervalMax"` | `java.lang.Long` | The maximum retry interval in millis of request to a remote server above which the request should be aborted instead. In theory, a malicious server could tell Maven "come back after 100 years" that would stall the build for some. Using this parameter Maven will fail the request instead, if interval is above this value. | `300000` | 1.9.16 | Yes | Session Configuration | -| 83. | `"aether.transport.http.retryHandler.serviceUnavailable"` | `java.lang.String` | The HTTP codes of remote server responses that should be handled as "too many requests" (examples: HTTP codes 429 and 503). Accepts comma separated list of HTTP response codes. | `"429,503"` | 1.9.16 | Yes | Session Configuration | -| 84. | `"aether.transport.http.reuseConnections"` | `java.lang.Boolean` | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | 1.9.8 | Yes | Session Configuration | -| 85. | `"aether.transport.http.supportWebDav"` | `java.lang.Boolean` | Boolean flag should the HTTP transport support WebDAV remote. Not all transport support this option. | `false` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | -| 86. | `"aether.transport.http.userAgent"` | `java.lang.String` | The user agent that repository connectors should report to servers. | `"Aether"` | | No | Session Configuration | -| 87. | `"aether.transport.https.securityMode"` | `java.lang.String` | The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, hostname verification). The default value is . | `"default"` | 1.9.6 | Yes | Session Configuration | -| 88. | `"aether.transport.jdk.httpVersion"` | `java.lang.String` | Use string representation of HttpClient version enum "HTTP_2" or "HTTP_1_1" to set default HTTP protocol to use. | `"HTTP_2"` | 2.0.0 | Yes | Session Configuration | -| 89. | `"aether.transport.jdk.maxConcurrentRequests"` | `java.lang.Integer` | The hard limit of maximum concurrent requests JDK transport can do. This is a workaround for the fact, that in HTTP/2 mode, JDK HttpClient initializes this value to Integer.MAX_VALUE (!) and lowers it on first response from the remote server (but it may be too late). See JDK bug JDK-8225647 for details. | `100` | 2.0.0 | Yes | Session Configuration | -| 90. | `"aether.transport.wagon.config"` | `java.lang.Object` | The configuration to use for the Wagon provider. | - | | Yes | Session Configuration | -| 91. | `"aether.transport.wagon.perms.dirMode"` | `java.lang.String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 92. | `"aether.transport.wagon.perms.fileMode"` | `java.lang.String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 93. | `"aether.transport.wagon.perms.group"` | `java.lang.String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 94. | `"aether.trustedChecksumsSource.sparseDirectory"` | `java.lang.Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | -| 95. | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `java.lang.String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | -| 96. | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `java.lang.Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | -| 97. | `"aether.trustedChecksumsSource.summaryFile"` | `java.lang.Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | -| 98. | `"aether.trustedChecksumsSource.summaryFile.basedir"` | `java.lang.String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | -| 99. | `"aether.trustedChecksumsSource.summaryFile.originAware"` | `java.lang.Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | -| 100. | `"aether.updateCheckManager.sessionState"` | `java.lang.String` | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If "enabled" will enable the session state. If "bypass" will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | | No | Session Configuration | +| 41. | `"aether.named.ipc.debug"` | `java.lang.Boolean` | Should the IPC server log debug messages? (i.e. for testing purposes) | `false` | 2.0.0 | No | Java System Properties | +| 42. | `"aether.named.ipc.family"` | `java.lang.String` | IPC socket family to use. | `"inet"` | 2.0.0 | No | Java System Properties | +| 43. | `"aether.named.ipc.idleTimeout"` | `java.lang.Integer` | IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. | `60` | 2.0.0 | No | Java System Properties | +| 44. | `"aether.named.ipc.nofork"` | `java.lang.Boolean` | Should the IPC server not fork? (i.e. for testing purposes) | `false` | 2.0.0 | No | Java System Properties | +| 45. | `"aether.named.ipc.nonative"` | `java.lang.Boolean` | Should the IPC server not use native executable? | `false` | 2.0.0 | No | Java System Properties | +| 46. | `"aether.offline.hosts"` | `java.lang.String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | +| 47. | `"aether.offline.protocols"` | `java.lang.String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | +| 48. | `"aether.priority.cached"` | `java.lang.Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | +| 49. | `"aether.priority.implicit"` | `java.lang.Boolean` | A flag indicating whether the priorities of pluggable extensions are implicitly given by their iteration order such that the first extension has the highest priority. If set, an extension's built-in priority as well as any corresponding configuration properties are ignored when searching for a suitable implementation among the available extensions. This priority mode is meant for cases where the application will present/inject extensions in the desired search order. | `false` | | No | Session Configuration | +| 50. | `"aether.remoteRepositoryFilter.groupId"` | `java.lang.Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | +| 51. | `"aether.remoteRepositoryFilter.groupId.basedir"` | `java.lang.String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | +| 52. | `"aether.remoteRepositoryFilter.groupId.record"` | `java.lang.Boolean` | Should filter go into "record" mode (and collect encountered artifacts)? | `false` | 1.9.0 | No | Session Configuration | +| 53. | `"aether.remoteRepositoryFilter.prefixes"` | `java.lang.Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | +| 54. | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `java.lang.String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | +| 55. | `"aether.snapshotFilter"` | `java.lang.Boolean` | The key in the repository session's used to store a flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | +| 56. | `"aether.syncContext.named.basedir.locksDir"` | `java.lang.String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | +| 57. | `"aether.syncContext.named.discriminating.discriminator"` | `java.lang.String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | +| 58. | `"aether.syncContext.named.discriminating.hostname"` | `java.lang.String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | +| 59. | `"aether.syncContext.named.factory"` | `java.lang.String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | +| 60. | `"aether.syncContext.named.hashing.depth"` | `java.lang.Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | +| 61. | `"aether.syncContext.named.nameMapper"` | `java.lang.String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | +| 62. | `"aether.syncContext.named.redisson.address"` | `java.lang.String` | Address of the Redis instance. Optional. | `"redis://localhost:6379"` | 2.0.0 | No | Java System Properties | +| 63. | `"aether.syncContext.named.redisson.configFile"` | `java.lang.String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | +| 64. | `"aether.syncContext.named.retry"` | `java.lang.Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | +| 65. | `"aether.syncContext.named.retry.wait"` | `java.lang.Long` | The amount of milliseconds to wait between retries on time-out. | `200` | 1.7.0 | No | Session Configuration | +| 66. | `"aether.syncContext.named.time"` | `java.lang.Long` | The maximum of time amount to be blocked to obtain lock. | `30` | 1.7.0 | No | Session Configuration | +| 67. | `"aether.syncContext.named.time.unit"` | `java.lang.String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | +| 68. | `"aether.system.dependencyVisitor"` | `java.lang.String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration | +| 69. | `"aether.transport.apache.https.cipherSuites"` | `java.lang.String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | +| 70. | `"aether.transport.apache.https.protocols"` | `java.lang.String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | +| 71. | `"aether.transport.apache.retryHandler.name"` | `java.lang.String` | The name of retryHandler, supported values are “standard”, that obeys RFC-2616, regarding idempotent methods, and “default” that considers requests w/o payload as idempotent. | `"standard"` | 2.0.0 | Yes | Session Configuration | +| 72. | `"aether.transport.apache.retryHandler.requestSentEnabled"` | `java.lang.Boolean` | Set to true if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | 2.0.0 | Yes | Session Configuration | +| 73. | `"aether.transport.apache.useSystemProperties"` | `java.lang.Boolean` | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is not recommended, better use documented ways of configuration instead. | `false` | 2.0.0 | Yes | Session Configuration | +| 74. | `"aether.transport.classpath.loader"` | `java.lang.ClassLoader` | The key in the repository session's used to store a from which resources should be retrieved. If unspecified, the of the current thread will be used. | - | | No | Session Configuration | +| 75. | `"aether.transport.http.connectTimeout"` | `java.lang.Integer` | The maximum amount of time (in milliseconds) to wait for a successful connection to a remote server. Non-positive values indicate no timeout. | `10000` | | Yes | Session Configuration | +| 76. | `"aether.transport.http.connectionMaxTtl"` | `java.lang.Integer` | Total time to live in seconds for an HTTP connection, after that time, the connection will be dropped (no matter for how long it was idle). | `300` | 1.9.8 | Yes | Session Configuration | +| 77. | `"aether.transport.http.credentialEncoding"` | `java.lang.String` | The encoding/charset to use when exchanging credentials with HTTP servers. Besides this general key, clients may also specify the encoding for a specific remote repository by appending the suffix to this key when storing the charset name. | `"ISO-8859-1"` | | Yes | Session Configuration | +| 78. | `"aether.transport.http.expectContinue"` | `java.lang.Boolean` | Boolean flag should the HTTP transport use expect-continue handshake for PUT requests. Not all transport support this option. This option may be needed for some broken HTTP servers. Default value corresponds to given transport default one (resolver does not override those), but if configuration IS given, it will replace given transport own default value. | - | 1.9.17 | Yes | Session Configuration | +| 79. | `"aether.transport.http.headers"` | `java.util.Map` | The request headers to use for HTTP-based repository connectors. The headers are specified using a , mapping a header name to its value. Besides this general key, clients may also specify headers for a specific remote repository by appending the suffix to this key when storing the headers map. The repository-specific headers map is supposed to be complete, i.e. is not merged with the general headers map. | - | | Yes | Session Configuration | +| 80. | `"aether.transport.http.localAddress"` | `java.lang.String` | The local address (interface) to use with HTTP transport. Not all transport supports this option. | - | 2.0.0 | Yes | Session Configuration | +| 81. | `"aether.transport.http.maxConnectionsPerRoute"` | `java.lang.Integer` | The maximum concurrent connections per route HTTP client is allowed to use. | `50` | 1.9.8 | Yes | Session Configuration | +| 82. | `"aether.transport.http.preemptiveAuth"` | `java.lang.Boolean` | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default, is disabled, as it is considered less secure. | `false` | 1.9.6 | Yes | Session Configuration | +| 83. | `"aether.transport.http.preemptivePutAuth"` | `java.lang.Boolean` | Boolean flag should the HTTP transport use preemptive-auth for PUT requests. Not all transport support this option. | `true` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | +| 84. | `"aether.transport.http.requestTimeout"` | `java.lang.Integer` | The maximum amount of time (in milliseconds) to wait for remaining data to arrive from a remote server. Note that this timeout does not restrict the overall duration of a request, it only restricts the duration of inactivity between consecutive data packets. Non-positive values indicate no timeout. | `1800000` | | Yes | Session Configuration | +| 85. | `"aether.transport.http.retryHandler.count"` | `java.lang.Integer` | The maximum number of times a request to a remote server should be retried in case of an error. | `3` | 1.9.6 | Yes | Session Configuration | +| 86. | `"aether.transport.http.retryHandler.interval"` | `java.lang.Long` | The initial retry interval in millis of request to a remote server should be waited in case of "too many requests" (HTTP codes 429 and 503). Accepts long as milliseconds. This value is used if remote server does not use header, in which case Server value is obeyed. | `5000` | 1.9.16 | Yes | Session Configuration | +| 87. | `"aether.transport.http.retryHandler.intervalMax"` | `java.lang.Long` | The maximum retry interval in millis of request to a remote server above which the request should be aborted instead. In theory, a malicious server could tell Maven "come back after 100 years" that would stall the build for some. Using this parameter Maven will fail the request instead, if interval is above this value. | `300000` | 1.9.16 | Yes | Session Configuration | +| 88. | `"aether.transport.http.retryHandler.serviceUnavailable"` | `java.lang.String` | The HTTP codes of remote server responses that should be handled as "too many requests" (examples: HTTP codes 429 and 503). Accepts comma separated list of HTTP response codes. | `"429,503"` | 1.9.16 | Yes | Session Configuration | +| 89. | `"aether.transport.http.reuseConnections"` | `java.lang.Boolean` | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | 1.9.8 | Yes | Session Configuration | +| 90. | `"aether.transport.http.supportWebDav"` | `java.lang.Boolean` | Boolean flag should the HTTP transport support WebDAV remote. Not all transport support this option. | `false` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | +| 91. | `"aether.transport.http.userAgent"` | `java.lang.String` | The user agent that repository connectors should report to servers. | `"Aether"` | | No | Session Configuration | +| 92. | `"aether.transport.https.securityMode"` | `java.lang.String` | The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, hostname verification). The default value is . | `"default"` | 1.9.6 | Yes | Session Configuration | +| 93. | `"aether.transport.jdk.httpVersion"` | `java.lang.String` | Use string representation of HttpClient version enum "HTTP_2" or "HTTP_1_1" to set default HTTP protocol to use. | `"HTTP_2"` | 2.0.0 | Yes | Session Configuration | +| 94. | `"aether.transport.jdk.maxConcurrentRequests"` | `java.lang.Integer` | The hard limit of maximum concurrent requests JDK transport can do. This is a workaround for the fact, that in HTTP/2 mode, JDK HttpClient initializes this value to Integer.MAX_VALUE (!) and lowers it on first response from the remote server (but it may be too late). See JDK bug JDK-8225647 for details. | `100` | 2.0.0 | Yes | Session Configuration | +| 95. | `"aether.transport.wagon.config"` | `java.lang.Object` | The configuration to use for the Wagon provider. | - | | Yes | Session Configuration | +| 96. | `"aether.transport.wagon.perms.dirMode"` | `java.lang.String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 97. | `"aether.transport.wagon.perms.fileMode"` | `java.lang.String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 98. | `"aether.transport.wagon.perms.group"` | `java.lang.String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 99. | `"aether.trustedChecksumsSource.sparseDirectory"` | `java.lang.Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | +| 100. | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `java.lang.String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | +| 101. | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `java.lang.Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | +| 102. | `"aether.trustedChecksumsSource.summaryFile"` | `java.lang.Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | +| 103. | `"aether.trustedChecksumsSource.summaryFile.basedir"` | `java.lang.String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | +| 104. | `"aether.trustedChecksumsSource.summaryFile.originAware"` | `java.lang.Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | +| 105. | `"aether.updateCheckManager.sessionState"` | `java.lang.String` | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If "enabled" will enable the session state. If "bypass" will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | | No | Session Configuration | All properties which have `yes` in the column `Supports Repo ID Suffix` can be optionally configured specifically for a repository id. In that case the configuration property needs to be suffixed with a period followed by the repository id of the repository to configure, e.g. `aether.connector.http.headers.central` for repository with id `central`. From 9abcb01e843706d1ac423d961ed21d02de9d68e9 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 13 Jun 2024 14:09:18 +0200 Subject: [PATCH 02/11] Post merge refresh --- maven-resolver-named-locks-ipc/pom.xml | 2 +- src/site/markdown/configuration.md | 125 +++++++++++++------------ 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/maven-resolver-named-locks-ipc/pom.xml b/maven-resolver-named-locks-ipc/pom.xml index 8a6adb023..2a12f4575 100644 --- a/maven-resolver-named-locks-ipc/pom.xml +++ b/maven-resolver-named-locks-ipc/pom.xml @@ -32,7 +32,7 @@ A synchronization utility implementation using IPC. - 9 + 17 org.apache.maven.resolver.named.ipc ${Automatic-Module-Name} diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 98795ea2e..f515e7e3f 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -72,66 +72,71 @@ under the License. | 45. | `"aether.named.file-lock.attempts"` | `Integer` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to implement similar fix as referenced JDK bug report: retry and hope the best. Default value is 5 attempts (will retry 4 times). | `5` | 1.7.3 | No | Java System Properties | | 46. | `"aether.named.file-lock.deleteLockFiles"` | `Boolean` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to have it removed from effective flags, at the cost that lockfile directory becomes crowded with 0 byte sized lock files that are never cleaned up. Default value is on non-Windows OS. See JDK-8252883 for Windows related bug. Users on Windows can still force "delete on close" by explicitly setting this property to . | `true` | 1.7.3 | No | Java System Properties | | 47. | `"aether.named.file-lock.sleepMillis"` | `Long` | Tweak: When used, the amount of milliseconds to sleep between subsequent retries. Default value is 50 milliseconds. | `50` | 1.7.3 | No | Java System Properties | -| 48. | `"aether.offline.hosts"` | `String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | -| 49. | `"aether.offline.protocols"` | `String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | -| 50. | `"aether.priority.cached"` | `Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | -| 51. | `"aether.priority.implicit"` | `Boolean` | A flag indicating whether the priorities of pluggable extensions are implicitly given by their iteration order such that the first extension has the highest priority. If set, an extension's built-in priority as well as any corresponding configuration properties are ignored when searching for a suitable implementation among the available extensions. This priority mode is meant for cases where the application will present/inject extensions in the desired search order. | `false` | | No | Session Configuration | -| 52. | `"aether.remoteRepositoryFilter.groupId"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | -| 53. | `"aether.remoteRepositoryFilter.groupId.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | -| 54. | `"aether.remoteRepositoryFilter.groupId.record"` | `Boolean` | Should filter go into "record" mode (and collect encountered artifacts)? | `false` | 1.9.0 | No | Session Configuration | -| 55. | `"aether.remoteRepositoryFilter.prefixes"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | -| 56. | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | -| 57. | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's used to store a flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | -| 58. | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | -| 59. | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | -| 60. | `"aether.syncContext.named.discriminating.hostname"` | `String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | -| 61. | `"aether.syncContext.named.factory"` | `String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | -| 62. | `"aether.syncContext.named.hashing.depth"` | `Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | -| 63. | `"aether.syncContext.named.nameMapper"` | `String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | -| 64. | `"aether.syncContext.named.redisson.address"` | `String` | Address of the Redis instance. Optional. | `"redis://localhost:6379"` | 2.0.0 | No | Java System Properties | -| 65. | `"aether.syncContext.named.redisson.configFile"` | `String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | -| 66. | `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | -| 67. | `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration | -| 68. | `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration | -| 69. | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | -| 70. | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration | -| 71. | `"aether.transport.apache.https.cipherSuites"` | `String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | -| 72. | `"aether.transport.apache.https.protocols"` | `String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | -| 73. | `"aether.transport.apache.retryHandler.name"` | `String` | The name of retryHandler, supported values are “standard”, that obeys RFC-2616, regarding idempotent methods, and “default” that considers requests w/o payload as idempotent. | `"standard"` | 2.0.0 | Yes | Session Configuration | -| 74. | `"aether.transport.apache.retryHandler.requestSentEnabled"` | `Boolean` | Set to true if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | 2.0.0 | Yes | Session Configuration | -| 75. | `"aether.transport.apache.useSystemProperties"` | `Boolean` | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is not recommended, better use documented ways of configuration instead. | `false` | 2.0.0 | Yes | Session Configuration | -| 76. | `"aether.transport.classpath.loader"` | `ClassLoader` | The key in the repository session's used to store a from which resources should be retrieved. If unspecified, the of the current thread will be used. | - | | No | Session Configuration | -| 77. | `"aether.transport.http.connectTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for a successful connection to a remote server. Non-positive values indicate no timeout. | `10000` | | Yes | Session Configuration | -| 78. | `"aether.transport.http.connectionMaxTtl"` | `Integer` | Total time to live in seconds for an HTTP connection, after that time, the connection will be dropped (no matter for how long it was idle). | `300` | 1.9.8 | Yes | Session Configuration | -| 79. | `"aether.transport.http.credentialEncoding"` | `String` | The encoding/charset to use when exchanging credentials with HTTP servers. Besides this general key, clients may also specify the encoding for a specific remote repository by appending the suffix to this key when storing the charset name. | `"ISO-8859-1"` | | Yes | Session Configuration | -| 80. | `"aether.transport.http.expectContinue"` | `Boolean` | Boolean flag should the HTTP transport use expect-continue handshake for PUT requests. Not all transport support this option. This option may be needed for some broken HTTP servers. Default value corresponds to given transport default one (resolver does not override those), but if configuration IS given, it will replace given transport own default value. | - | 1.9.17 | Yes | Session Configuration | -| 81. | `"aether.transport.http.headers"` | `java.util.Map` | The request headers to use for HTTP-based repository connectors. The headers are specified using a , mapping a header name to its value. Besides this general key, clients may also specify headers for a specific remote repository by appending the suffix to this key when storing the headers map. The repository-specific headers map is supposed to be complete, i.e. is not merged with the general headers map. | - | | Yes | Session Configuration | -| 82. | `"aether.transport.http.localAddress"` | `String` | The local address (interface) to use with HTTP transport. Not all transport supports this option. | - | 2.0.0 | Yes | Session Configuration | -| 83. | `"aether.transport.http.maxConnectionsPerRoute"` | `Integer` | The maximum concurrent connections per route HTTP client is allowed to use. | `50` | 1.9.8 | Yes | Session Configuration | -| 84. | `"aether.transport.http.preemptiveAuth"` | `Boolean` | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default, is disabled, as it is considered less secure. | `false` | 1.9.6 | Yes | Session Configuration | -| 85. | `"aether.transport.http.preemptivePutAuth"` | `Boolean` | Boolean flag should the HTTP transport use preemptive-auth for PUT requests. Not all transport support this option. | `true` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | -| 86. | `"aether.transport.http.requestTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for remaining data to arrive from a remote server. Note that this timeout does not restrict the overall duration of a request, it only restricts the duration of inactivity between consecutive data packets. Non-positive values indicate no timeout. | `1800000` | | Yes | Session Configuration | -| 87. | `"aether.transport.http.retryHandler.count"` | `Integer` | The maximum number of times a request to a remote server should be retried in case of an error. | `3` | 1.9.6 | Yes | Session Configuration | -| 88. | `"aether.transport.http.retryHandler.interval"` | `Long` | The initial retry interval in millis of request to a remote server should be waited in case of "too many requests" (HTTP codes 429 and 503). Accepts long as milliseconds. This value is used if remote server does not use header, in which case Server value is obeyed. | `5000l` | 1.9.16 | Yes | Session Configuration | -| 89. | `"aether.transport.http.retryHandler.intervalMax"` | `Long` | The maximum retry interval in millis of request to a remote server above which the request should be aborted instead. In theory, a malicious server could tell Maven "come back after 100 years" that would stall the build for some. Using this parameter Maven will fail the request instead, if interval is above this value. | `300000l` | 1.9.16 | Yes | Session Configuration | -| 90. | `"aether.transport.http.retryHandler.serviceUnavailable"` | `String` | The HTTP codes of remote server responses that should be handled as "too many requests" (examples: HTTP codes 429 and 503). Accepts comma separated list of HTTP response codes. | `"429,503"` | 1.9.16 | Yes | Session Configuration | -| 91. | `"aether.transport.http.reuseConnections"` | `Boolean` | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | 1.9.8 | Yes | Session Configuration | -| 92. | `"aether.transport.http.supportWebDav"` | `Boolean` | Boolean flag should the HTTP transport support WebDAV remote. Not all transport support this option. | `false` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | -| 93. | `"aether.transport.http.userAgent"` | `String` | The user agent that repository connectors should report to servers. | `"Aether"` | | No | Session Configuration | -| 94. | `"aether.transport.https.securityMode"` | `String` | The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, hostname verification). The default value is . | `"default"` | 1.9.6 | Yes | Session Configuration | -| 95. | `"aether.transport.jdk.httpVersion"` | `String` | Use string representation of HttpClient version enum "HTTP_2" or "HTTP_1_1" to set default HTTP protocol to use. | `"HTTP_2"` | 2.0.0 | Yes | Session Configuration | -| 96. | `"aether.transport.jdk.maxConcurrentRequests"` | `Integer` | The hard limit of maximum concurrent requests JDK transport can do. This is a workaround for the fact, that in HTTP/2 mode, JDK HttpClient initializes this value to Integer.MAX_VALUE (!) and lowers it on first response from the remote server (but it may be too late). See JDK bug JDK-8225647 for details. | `100` | 2.0.0 | Yes | Session Configuration | -| 97. | `"aether.transport.wagon.config"` | `Object` | The configuration to use for the Wagon provider. | - | | Yes | Session Configuration | -| 98. | `"aether.transport.wagon.perms.dirMode"` | `String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 99. | `"aether.transport.wagon.perms.fileMode"` | `String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 100. | `"aether.transport.wagon.perms.group"` | `String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 101. | `"aether.trustedChecksumsSource.sparseDirectory"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | -| 102. | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | -| 103. | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | -| 104. | `"aether.trustedChecksumsSource.summaryFile"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | -| 105. | `"aether.trustedChecksumsSource.summaryFile.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | -| 106. | `"aether.trustedChecksumsSource.summaryFile.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | -| 107. | `"aether.updateCheckManager.sessionState"` | `String` | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If "enabled" will enable the session state. If "bypass" will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | | No | Session Configuration | +| 48. | `"aether.named.ipc.debug"` | `Boolean` | Should the IPC server log debug messages? (i.e. for testing purposes) | `false` | 2.0.0 | No | Java System Properties | +| 49. | `"aether.named.ipc.family"` | `String` | IPC socket family to use. | `"inet"` | 2.0.0 | No | Java System Properties | +| 50. | `"aether.named.ipc.idleTimeout"` | `Integer` | IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. | `60` | 2.0.0 | No | Java System Properties | +| 51. | `"aether.named.ipc.nofork"` | `Boolean` | Should the IPC server not fork? (i.e. for testing purposes) | `false` | 2.0.0 | No | Java System Properties | +| 52. | `"aether.named.ipc.nonative"` | `Boolean` | Should the IPC server not use native executable? | `false` | 2.0.0 | No | Java System Properties | +| 53. | `"aether.offline.hosts"` | `String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | +| 54. | `"aether.offline.protocols"` | `String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | +| 55. | `"aether.priority.cached"` | `Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | +| 56. | `"aether.priority.implicit"` | `Boolean` | A flag indicating whether the priorities of pluggable extensions are implicitly given by their iteration order such that the first extension has the highest priority. If set, an extension's built-in priority as well as any corresponding configuration properties are ignored when searching for a suitable implementation among the available extensions. This priority mode is meant for cases where the application will present/inject extensions in the desired search order. | `false` | | No | Session Configuration | +| 57. | `"aether.remoteRepositoryFilter.groupId"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | +| 58. | `"aether.remoteRepositoryFilter.groupId.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | +| 59. | `"aether.remoteRepositoryFilter.groupId.record"` | `Boolean` | Should filter go into "record" mode (and collect encountered artifacts)? | `false` | 1.9.0 | No | Session Configuration | +| 60. | `"aether.remoteRepositoryFilter.prefixes"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | +| 61. | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | +| 62. | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's used to store a flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | +| 63. | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | +| 64. | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | +| 65. | `"aether.syncContext.named.discriminating.hostname"` | `String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | +| 66. | `"aether.syncContext.named.factory"` | `String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | +| 67. | `"aether.syncContext.named.hashing.depth"` | `Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | +| 68. | `"aether.syncContext.named.nameMapper"` | `String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | +| 69. | `"aether.syncContext.named.redisson.address"` | `String` | Address of the Redis instance. Optional. | `"redis://localhost:6379"` | 2.0.0 | No | Java System Properties | +| 70. | `"aether.syncContext.named.redisson.configFile"` | `String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | +| 71. | `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | +| 72. | `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration | +| 73. | `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration | +| 74. | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | +| 75. | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration | +| 76. | `"aether.transport.apache.https.cipherSuites"` | `String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | +| 77. | `"aether.transport.apache.https.protocols"` | `String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | +| 78. | `"aether.transport.apache.retryHandler.name"` | `String` | The name of retryHandler, supported values are “standard”, that obeys RFC-2616, regarding idempotent methods, and “default” that considers requests w/o payload as idempotent. | `"standard"` | 2.0.0 | Yes | Session Configuration | +| 79. | `"aether.transport.apache.retryHandler.requestSentEnabled"` | `Boolean` | Set to true if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | 2.0.0 | Yes | Session Configuration | +| 80. | `"aether.transport.apache.useSystemProperties"` | `Boolean` | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is not recommended, better use documented ways of configuration instead. | `false` | 2.0.0 | Yes | Session Configuration | +| 81. | `"aether.transport.classpath.loader"` | `ClassLoader` | The key in the repository session's used to store a from which resources should be retrieved. If unspecified, the of the current thread will be used. | - | | No | Session Configuration | +| 82. | `"aether.transport.http.connectTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for a successful connection to a remote server. Non-positive values indicate no timeout. | `10000` | | Yes | Session Configuration | +| 83. | `"aether.transport.http.connectionMaxTtl"` | `Integer` | Total time to live in seconds for an HTTP connection, after that time, the connection will be dropped (no matter for how long it was idle). | `300` | 1.9.8 | Yes | Session Configuration | +| 84. | `"aether.transport.http.credentialEncoding"` | `String` | The encoding/charset to use when exchanging credentials with HTTP servers. Besides this general key, clients may also specify the encoding for a specific remote repository by appending the suffix to this key when storing the charset name. | `"ISO-8859-1"` | | Yes | Session Configuration | +| 85. | `"aether.transport.http.expectContinue"` | `Boolean` | Boolean flag should the HTTP transport use expect-continue handshake for PUT requests. Not all transport support this option. This option may be needed for some broken HTTP servers. Default value corresponds to given transport default one (resolver does not override those), but if configuration IS given, it will replace given transport own default value. | - | 1.9.17 | Yes | Session Configuration | +| 86. | `"aether.transport.http.headers"` | `java.util.Map` | The request headers to use for HTTP-based repository connectors. The headers are specified using a , mapping a header name to its value. Besides this general key, clients may also specify headers for a specific remote repository by appending the suffix to this key when storing the headers map. The repository-specific headers map is supposed to be complete, i.e. is not merged with the general headers map. | - | | Yes | Session Configuration | +| 87. | `"aether.transport.http.localAddress"` | `String` | The local address (interface) to use with HTTP transport. Not all transport supports this option. | - | 2.0.0 | Yes | Session Configuration | +| 88. | `"aether.transport.http.maxConnectionsPerRoute"` | `Integer` | The maximum concurrent connections per route HTTP client is allowed to use. | `50` | 1.9.8 | Yes | Session Configuration | +| 89. | `"aether.transport.http.preemptiveAuth"` | `Boolean` | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default, is disabled, as it is considered less secure. | `false` | 1.9.6 | Yes | Session Configuration | +| 90. | `"aether.transport.http.preemptivePutAuth"` | `Boolean` | Boolean flag should the HTTP transport use preemptive-auth for PUT requests. Not all transport support this option. | `true` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | +| 91. | `"aether.transport.http.requestTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for remaining data to arrive from a remote server. Note that this timeout does not restrict the overall duration of a request, it only restricts the duration of inactivity between consecutive data packets. Non-positive values indicate no timeout. | `1800000` | | Yes | Session Configuration | +| 92. | `"aether.transport.http.retryHandler.count"` | `Integer` | The maximum number of times a request to a remote server should be retried in case of an error. | `3` | 1.9.6 | Yes | Session Configuration | +| 93. | `"aether.transport.http.retryHandler.interval"` | `Long` | The initial retry interval in millis of request to a remote server should be waited in case of "too many requests" (HTTP codes 429 and 503). Accepts long as milliseconds. This value is used if remote server does not use header, in which case Server value is obeyed. | `5000l` | 1.9.16 | Yes | Session Configuration | +| 94. | `"aether.transport.http.retryHandler.intervalMax"` | `Long` | The maximum retry interval in millis of request to a remote server above which the request should be aborted instead. In theory, a malicious server could tell Maven "come back after 100 years" that would stall the build for some. Using this parameter Maven will fail the request instead, if interval is above this value. | `300000l` | 1.9.16 | Yes | Session Configuration | +| 95. | `"aether.transport.http.retryHandler.serviceUnavailable"` | `String` | The HTTP codes of remote server responses that should be handled as "too many requests" (examples: HTTP codes 429 and 503). Accepts comma separated list of HTTP response codes. | `"429,503"` | 1.9.16 | Yes | Session Configuration | +| 96. | `"aether.transport.http.reuseConnections"` | `Boolean` | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | 1.9.8 | Yes | Session Configuration | +| 97. | `"aether.transport.http.supportWebDav"` | `Boolean` | Boolean flag should the HTTP transport support WebDAV remote. Not all transport support this option. | `false` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | +| 98. | `"aether.transport.http.userAgent"` | `String` | The user agent that repository connectors should report to servers. | `"Aether"` | | No | Session Configuration | +| 99. | `"aether.transport.https.securityMode"` | `String` | The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, hostname verification). The default value is . | `"default"` | 1.9.6 | Yes | Session Configuration | +| 100. | `"aether.transport.jdk.httpVersion"` | `String` | Use string representation of HttpClient version enum "HTTP_2" or "HTTP_1_1" to set default HTTP protocol to use. | `"HTTP_2"` | 2.0.0 | Yes | Session Configuration | +| 101. | `"aether.transport.jdk.maxConcurrentRequests"` | `Integer` | The hard limit of maximum concurrent requests JDK transport can do. This is a workaround for the fact, that in HTTP/2 mode, JDK HttpClient initializes this value to Integer.MAX_VALUE (!) and lowers it on first response from the remote server (but it may be too late). See JDK bug JDK-8225647 for details. | `100` | 2.0.0 | Yes | Session Configuration | +| 102. | `"aether.transport.wagon.config"` | `Object` | The configuration to use for the Wagon provider. | - | | Yes | Session Configuration | +| 103. | `"aether.transport.wagon.perms.dirMode"` | `String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 104. | `"aether.transport.wagon.perms.fileMode"` | `String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 105. | `"aether.transport.wagon.perms.group"` | `String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 106. | `"aether.trustedChecksumsSource.sparseDirectory"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | +| 107. | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | +| 108. | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | +| 109. | `"aether.trustedChecksumsSource.summaryFile"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | +| 110. | `"aether.trustedChecksumsSource.summaryFile.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | +| 111. | `"aether.trustedChecksumsSource.summaryFile.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | +| 112. | `"aether.updateCheckManager.sessionState"` | `String` | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If "enabled" will enable the session state. If "bypass" will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | | No | Session Configuration | All properties which have `yes` in the column `Supports Repo ID Suffix` can be optionally configured specifically for a repository id. In that case the configuration property needs to be suffixed with a period followed by the repository id of the repository to configure, e.g. `aether.connector.http.headers.central` for repository with id `central`. From d8c78372cb130b4aeb211fc45665bbb125e3fd96 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 12:11:51 +0200 Subject: [PATCH 03/11] Updates post merge and PR comments --- maven-resolver-named-locks-ipc/pom.xml | 2 +- .../src/main/java/org/eclipse/aether/named/ipc/IpcClient.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/maven-resolver-named-locks-ipc/pom.xml b/maven-resolver-named-locks-ipc/pom.xml index 2a12f4575..6f8a816b7 100644 --- a/maven-resolver-named-locks-ipc/pom.xml +++ b/maven-resolver-named-locks-ipc/pom.xml @@ -23,7 +23,7 @@ org.apache.maven.resolver maven-resolver - 2.0.0-SNAPSHOT + 2.0.1-SNAPSHOT maven-resolver-named-locks-ipc diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java index 8e5bae0af..0dd40f868 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java @@ -57,7 +57,7 @@ public class IpcClient { static final boolean IS_WINDOWS = - System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win"); + System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win"); volatile boolean initialized; Path lockPath; @@ -99,7 +99,7 @@ SocketChannel createClient() throws IOException { Path lockPath = this.lockPath.toAbsolutePath().normalize(); Path lockFile = - lockPath.resolve(".maven-resolver-ipc-lock-" + family.name().toLowerCase()); + lockPath.resolve(".maven-resolver-ipc-lock-" + family.name().toLowerCase(Locale.ENGLISH)); if (!Files.isRegularFile(lockFile)) { if (!Files.isDirectory(lockFile.getParent())) { Files.createDirectories(lockFile.getParent()); From 032a24d0f31a7647c2c6ea5f625a0926e1db4e4d Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 12:25:49 +0200 Subject: [PATCH 04/11] Make since 2.0.1 --- .../eclipse/aether/named/ipc/ByteChannelWrapper.java | 2 +- .../java/org/eclipse/aether/named/ipc/IpcClient.java | 2 +- .../java/org/eclipse/aether/named/ipc/IpcMessages.java | 2 +- .../org/eclipse/aether/named/ipc/IpcNamedLock.java | 2 +- .../eclipse/aether/named/ipc/IpcNamedLockFactory.java | 2 +- .../java/org/eclipse/aether/named/ipc/IpcServer.java | 2 +- .../org/eclipse/aether/named/ipc/SocketFamily.java | 2 +- src/site/markdown/configuration.md | 10 +++++----- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/ByteChannelWrapper.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/ByteChannelWrapper.java index 3b3c978e2..66aab9f95 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/ByteChannelWrapper.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/ByteChannelWrapper.java @@ -26,7 +26,7 @@ * Trivial ByteChannel wrapper to avoid the read/write synchronization which * happens when the channel implements SelectableChannel. * - * @since 2.0.0 + * @since 2.0.1 */ public class ByteChannelWrapper implements ByteChannel { diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java index 0dd40f868..fa034bfbd 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java @@ -52,7 +52,7 @@ * Client side implementation. * The client instance is bound to a given maven repository. * - * @since 2.0.0 + * @since 2.0.1 */ public class IpcClient { diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcMessages.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcMessages.java index c17fe1298..a9cd9d2ec 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcMessages.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcMessages.java @@ -21,7 +21,7 @@ /** * Constants used for the inter-process communication protocol. * - * @since 2.0.0 + * @since 2.0.1 */ public class IpcMessages { diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java index 01107f96c..1ce5fd800 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java @@ -31,7 +31,7 @@ /** * IPC named locks. * - * @since 2.0.0 + * @since 2.0.1 */ class IpcNamedLock extends NamedLockSupport { diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java index 276b740f0..64c2a854e 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java @@ -36,7 +36,7 @@ /** * IPC named locks factory. * - * @since 2.0.0 + * @since 2.0.1 */ @Singleton @Named(IpcNamedLockFactory.NAME) diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java index 74be3555f..df4c77e59 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java @@ -41,7 +41,7 @@ * Implementation of the server side. * The server instance is bound to a given maven repository. * - * @since 2.0.0 + * @since 2.0.1 */ public class IpcServer { /** diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java index 88d61aad1..5248be2a7 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java @@ -29,7 +29,7 @@ /** * Socket factory. * - * @since 2.0.0 + * @since 2.0.1 */ public enum SocketFamily { inet; diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index f515e7e3f..2e4b65711 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -72,11 +72,11 @@ under the License. | 45. | `"aether.named.file-lock.attempts"` | `Integer` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to implement similar fix as referenced JDK bug report: retry and hope the best. Default value is 5 attempts (will retry 4 times). | `5` | 1.7.3 | No | Java System Properties | | 46. | `"aether.named.file-lock.deleteLockFiles"` | `Boolean` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to have it removed from effective flags, at the cost that lockfile directory becomes crowded with 0 byte sized lock files that are never cleaned up. Default value is on non-Windows OS. See JDK-8252883 for Windows related bug. Users on Windows can still force "delete on close" by explicitly setting this property to . | `true` | 1.7.3 | No | Java System Properties | | 47. | `"aether.named.file-lock.sleepMillis"` | `Long` | Tweak: When used, the amount of milliseconds to sleep between subsequent retries. Default value is 50 milliseconds. | `50` | 1.7.3 | No | Java System Properties | -| 48. | `"aether.named.ipc.debug"` | `Boolean` | Should the IPC server log debug messages? (i.e. for testing purposes) | `false` | 2.0.0 | No | Java System Properties | -| 49. | `"aether.named.ipc.family"` | `String` | IPC socket family to use. | `"inet"` | 2.0.0 | No | Java System Properties | -| 50. | `"aether.named.ipc.idleTimeout"` | `Integer` | IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. | `60` | 2.0.0 | No | Java System Properties | -| 51. | `"aether.named.ipc.nofork"` | `Boolean` | Should the IPC server not fork? (i.e. for testing purposes) | `false` | 2.0.0 | No | Java System Properties | -| 52. | `"aether.named.ipc.nonative"` | `Boolean` | Should the IPC server not use native executable? | `false` | 2.0.0 | No | Java System Properties | +| 48. | `"aether.named.ipc.debug"` | `Boolean` | Should the IPC server log debug messages? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | +| 49. | `"aether.named.ipc.family"` | `String` | IPC socket family to use. | `"inet"` | 2.0.1 | No | Java System Properties | +| 50. | `"aether.named.ipc.idleTimeout"` | `Integer` | IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. | `60` | 2.0.1 | No | Java System Properties | +| 51. | `"aether.named.ipc.nofork"` | `Boolean` | Should the IPC server not fork? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | +| 52. | `"aether.named.ipc.nonative"` | `Boolean` | Should the IPC server not use native executable? | `false` | 2.0.1 | No | Java System Properties | | 53. | `"aether.offline.hosts"` | `String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | | 54. | `"aether.offline.protocols"` | `String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | | 55. | `"aether.priority.cached"` | `Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | From ff92d35541158fab9d47f34b7a04fb471de1fd9e Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 12:59:49 +0200 Subject: [PATCH 05/11] Add unix family --- .../eclipse/aether/named/ipc/IpcServer.java | 2 +- .../aether/named/ipc/SocketFamily.java | 47 ++++++++++++------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java index df4c77e59..62c039a75 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java @@ -75,7 +75,7 @@ public class IpcServer { */ public static final String SYSTEM_PROP_FAMILY = "aether.named.ipc.family"; - public static final String DEFAULT_FAMILY = "inet"; + public static final String DEFAULT_FAMILY = "unix"; /** * Should the IPC server not use native executable? diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java index 5248be2a7..7ef2696ff 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java @@ -23,6 +23,8 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; import java.net.UnknownHostException; import java.nio.channels.ServerSocketChannel; @@ -32,12 +34,15 @@ * @since 2.0.1 */ public enum SocketFamily { - inet; + inet, + unix; public ServerSocketChannel openServerSocket() throws IOException { switch (this) { case inet: return ServerSocketChannel.open().bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + case unix: + return ServerSocketChannel.open(StandardProtocolFamily.UNIX).bind(null, 0); default: throw new IllegalStateException(); } @@ -75,35 +80,43 @@ public static SocketAddress fromString(String str) { } return new InetSocketAddress(addr, Integer.parseInt(p)); } + } else if (str.startsWith("unix:")) { + return UnixDomainSocketAddress.of(str.substring("unix:".length())); } else { throw new IllegalArgumentException("Unsupported socket address: '" + str + "'"); } } public static String toString(SocketAddress address) { - if (familyOf(address) == SocketFamily.inet) { - InetSocketAddress isa = (InetSocketAddress) address; - String host = isa.getHostString(); - InetAddress addr = isa.getAddress(); - int port = isa.getPort(); - String formatted; - if (addr == null) { - formatted = host + "/"; - } else { - formatted = addr.toString(); - if (addr instanceof Inet6Address) { - int i = formatted.lastIndexOf("/"); - formatted = formatted.substring(0, i + 1) + "[" + formatted.substring(i + 1) + "]"; + switch (familyOf(address)) { + case inet: + InetSocketAddress isa = (InetSocketAddress) address; + String host = isa.getHostString(); + InetAddress addr = isa.getAddress(); + int port = isa.getPort(); + String formatted; + if (addr == null) { + formatted = host + "/"; + } else { + formatted = addr.toString(); + if (addr instanceof Inet6Address) { + int i = formatted.lastIndexOf("/"); + formatted = formatted.substring(0, i + 1) + "[" + formatted.substring(i + 1) + "]"; + } } - } - return "inet:" + formatted + ":" + port; + return "inet:" + formatted + ":" + port; + case unix: + return "unix:" + address; + default: + throw new IllegalArgumentException("Unsupported socket address: '" + address + "'"); } - throw new IllegalArgumentException("Unsupported socket address: '" + address + "'"); } public static SocketFamily familyOf(SocketAddress address) { if (address instanceof InetSocketAddress) { return SocketFamily.inet; + } else if ("java.net.UnixDomainSocketAddress".equals(address.getClass().getName())) { + return SocketFamily.unix; } else { throw new IllegalArgumentException("Unsupported socket address '" + address + "'"); } From 70276dfab6f7e5eac3602523d78783ea80b78565 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 13:30:19 +0200 Subject: [PATCH 06/11] Adapt to latest MANIFEST changes --- maven-resolver-named-locks-ipc/pom.xml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/maven-resolver-named-locks-ipc/pom.xml b/maven-resolver-named-locks-ipc/pom.xml index 6f8a816b7..c4c675f7e 100644 --- a/maven-resolver-named-locks-ipc/pom.xml +++ b/maven-resolver-named-locks-ipc/pom.xml @@ -33,8 +33,6 @@ 17 - org.apache.maven.resolver.named.ipc - ${Automatic-Module-Name} @@ -86,19 +84,6 @@ org.eclipse.sisu sisu-maven-plugin - - biz.aQute.bnd - bnd-maven-plugin - - - org.apache.maven.plugins - maven-jar-plugin - - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - - From 7e1ecf7fed080fe13a42fa5c2431bcca8483f064 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 13:35:45 +0200 Subject: [PATCH 07/11] Add UT for SocketFamily --- .../aether/named/ipc/SocketFamilyTest.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/SocketFamilyTest.java diff --git a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/SocketFamilyTest.java b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/SocketFamilyTest.java new file mode 100644 index 000000000..8b04a87cb --- /dev/null +++ b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/SocketFamilyTest.java @@ -0,0 +1,72 @@ +/* + * 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.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SocketFamilyTest { + + @Test + void testInetNullHost() throws UnknownHostException { + InetSocketAddress i4a = + new InetSocketAddress(InetAddress.getByAddress(null, new byte[] {(byte) 192, (byte) 168, 0, 1}), 8080); + + assertEquals("inet:/192.168.0.1:8080", SocketFamily.toString(i4a)); + assertEquals(i4a, SocketFamily.fromString("inet:/192.168.0.1:8080")); + } + + @Test + void testInetDummyHost() throws UnknownHostException { + InetSocketAddress i4a = new InetSocketAddress( + InetAddress.getByAddress("dummy.org", new byte[] {(byte) 192, (byte) 168, 0, 1}), 8080); + + assertEquals("inet:dummy.org/192.168.0.1:8080", SocketFamily.toString(i4a)); + assertEquals(i4a, SocketFamily.fromString("inet:dummy.org/192.168.0.1:8080")); + } + + @Test + void testInetLoopback() throws UnknownHostException { + InetSocketAddress i4a = new InetSocketAddress(8080); + + assertEquals("inet:0.0.0.0/0.0.0.0:8080", SocketFamily.toString(i4a)); + assertEquals(i4a, SocketFamily.fromString("inet:0.0.0.0/0.0.0.0:8080")); + } + + @Test + void testInetUnresolved() throws UnknownHostException { + InetSocketAddress i4a = InetSocketAddress.createUnresolved("google.com", 8080); + + assertEquals("inet:google.com/:8080", SocketFamily.toString(i4a)); + assertEquals(i4a, SocketFamily.fromString("inet:google.com/:8080")); + } + + @Test + void testUnixFromTo() { + SocketAddress address = SocketFamily.fromString("unix:/tmp/foo-0123456.socket"); + assertEquals(SocketFamily.unix, SocketFamily.familyOf(address)); + assertEquals("unix:/tmp/foo-0123456.socket", SocketFamily.toString(address)); + } +} From 50c68bcba18f85e5947273266b47cbaec93cd842 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 13:41:51 +0200 Subject: [PATCH 08/11] Fix address string --- .../main/java/org/eclipse/aether/named/ipc/SocketFamily.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java index 7ef2696ff..ec8ee5375 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/SocketFamily.java @@ -106,7 +106,8 @@ public static String toString(SocketAddress address) { } return "inet:" + formatted + ":" + port; case unix: - return "unix:" + address; + // to keep address string unchanged across all OSes + return "unix:" + address.toString().replace('\\', '/'); default: throw new IllegalArgumentException("Unsupported socket address: '" + address + "'"); } From 1eb74f06a780b2267fd0cdb7b233dfccd269400e Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 14:02:06 +0200 Subject: [PATCH 09/11] Make native image name configurable Also, changes: * native name configurable * make default nonative=true (as we do not provide any) * simplify factory --- .../eclipse/aether/named/ipc/IpcClient.java | 3 ++- .../aether/named/ipc/IpcNamedLockFactory.java | 22 +++++++++---------- .../eclipse/aether/named/ipc/IpcServer.java | 13 ++++++++++- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java index fa034bfbd..bcf698924 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java @@ -121,7 +121,8 @@ SocketChannel createClient() throws IOException { ServerSocketChannel ss = family.openServerSocket(); String tmpaddr = SocketFamily.toString(ss.getLocalAddress()); String rand = Long.toHexString(new Random().nextLong()); - String syncCmd = IS_WINDOWS ? "mvnd-sync.exe" : "mvnd-sync"; + String nativeName = System.getProperty(IpcServer.SYSTEM_PROP_NATIVE_NAME, IpcServer.DEFAULT_NATIVE_NAME); + String syncCmd = IS_WINDOWS ? nativeName + ".exe" : nativeName; boolean debug = Boolean.parseBoolean( System.getProperty(IpcServer.SYSTEM_PROP_DEBUG, Boolean.toString(IpcServer.DEFAULT_DEBUG))); diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java index 64c2a854e..660882e09 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLockFactory.java @@ -33,6 +33,8 @@ import org.eclipse.aether.named.support.NamedLockSupport; import org.eclipse.aether.util.StringDigestUtil; +import static java.util.Objects.requireNonNull; + /** * IPC named locks factory. * @@ -43,22 +45,18 @@ public class IpcNamedLockFactory extends NamedLockFactorySupport { public static final String NAME = "ipc"; - private final Path ipcHome; - - private final Path repository; - - private final Path logPath; - - private final Path syncPath; - protected final IpcClient client; @Inject public IpcNamedLockFactory() { - this.ipcHome = Paths.get(System.getProperty("user.home")).resolve(".ipc-sync"); - this.repository = ipcHome.resolve("repository"); - this.logPath = ipcHome.resolve("log"); - this.syncPath = null; + this(Paths.get(System.getProperty("user.home")).resolve(".ipc-sync")); + } + + public IpcNamedLockFactory(Path ipcHome) { + requireNonNull(ipcHome); + Path repository = ipcHome.resolve("repository"); + Path logPath = ipcHome.resolve("log"); + Path syncPath = null; this.client = new IpcClient(repository, logPath, syncPath); } diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java index 62c039a75..2b59342af 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java @@ -86,7 +86,18 @@ public class IpcServer { */ public static final String SYSTEM_PROP_NO_NATIVE = "aether.named.ipc.nonative"; - public static final boolean DEFAULT_NO_NATIVE = false; + public static final boolean DEFAULT_NO_NATIVE = true; + + /** + * The name if the IPC server native executable (without file extension like ".exe") + * + * @configurationSource {@link System#getProperty(String, String)} + * @configurationType {@link java.lang.String} + * @configurationDefaultValue {@link #DEFAULT_NATIVE_NAME} + */ + public static final String SYSTEM_PROP_NATIVE_NAME = "aether.named.ipc.nativeName"; + + public static final String DEFAULT_NATIVE_NAME = "ipc-sync"; /** * Should the IPC server log debug messages? (i.e. for testing purposes) From 03f6fc0a27ff3a902abbc3321f25ac9b96b1b3df Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 14:05:29 +0200 Subject: [PATCH 10/11] Reformat and remove now redundant IT config --- .../src/main/java/org/eclipse/aether/named/ipc/IpcClient.java | 3 ++- .../test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.java | 1 - .../org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java index bcf698924..bf67f42d1 100644 --- a/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java +++ b/maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java @@ -121,7 +121,8 @@ SocketChannel createClient() throws IOException { ServerSocketChannel ss = family.openServerSocket(); String tmpaddr = SocketFamily.toString(ss.getLocalAddress()); String rand = Long.toHexString(new Random().nextLong()); - String nativeName = System.getProperty(IpcServer.SYSTEM_PROP_NATIVE_NAME, IpcServer.DEFAULT_NATIVE_NAME); + String nativeName = + System.getProperty(IpcServer.SYSTEM_PROP_NATIVE_NAME, IpcServer.DEFAULT_NATIVE_NAME); String syncCmd = IS_WINDOWS ? nativeName + ".exe" : nativeName; boolean debug = Boolean.parseBoolean( diff --git a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.java b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.java index a17b63012..a4b4d48c4 100644 --- a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.java +++ b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcAdapterIT.java @@ -24,7 +24,6 @@ public class IpcAdapterIT extends NamedLockFactoryAdapterTestSupport { @BeforeAll static void createNamedLockFactory() { System.setProperty(IpcServer.SYSTEM_PROP_DEBUG, Boolean.TRUE.toString()); - System.setProperty(IpcServer.SYSTEM_PROP_NO_NATIVE, Boolean.TRUE.toString()); setNamedLockFactory(new IpcNamedLockFactory() { @Override protected void doShutdown() { diff --git a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java index bee8e354c..158d8a562 100644 --- a/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java +++ b/maven-resolver-named-locks-ipc/src/test/java/org/eclipse/aether/named/ipc/IpcNamedLockFactoryIT.java @@ -24,7 +24,6 @@ public class IpcNamedLockFactoryIT extends NamedLockFactoryTestSupport { @BeforeAll static void createNamedLockFactory() { System.setProperty(IpcServer.SYSTEM_PROP_DEBUG, Boolean.TRUE.toString()); - System.setProperty(IpcServer.SYSTEM_PROP_NO_NATIVE, Boolean.TRUE.toString()); namedLockFactory = new IpcNamedLockFactory() { @Override protected void doShutdown() { From 2bc232f7c59250bbb23219a6cde6ea456ddfea7a Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 9 Jul 2024 14:07:32 +0200 Subject: [PATCH 11/11] Update doco as well --- src/site/markdown/configuration.md | 127 +++++++++++++++-------------- 1 file changed, 64 insertions(+), 63 deletions(-) diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 2e4b65711..7abd373b6 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -73,70 +73,71 @@ under the License. | 46. | `"aether.named.file-lock.deleteLockFiles"` | `Boolean` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to have it removed from effective flags, at the cost that lockfile directory becomes crowded with 0 byte sized lock files that are never cleaned up. Default value is on non-Windows OS. See JDK-8252883 for Windows related bug. Users on Windows can still force "delete on close" by explicitly setting this property to . | `true` | 1.7.3 | No | Java System Properties | | 47. | `"aether.named.file-lock.sleepMillis"` | `Long` | Tweak: When used, the amount of milliseconds to sleep between subsequent retries. Default value is 50 milliseconds. | `50` | 1.7.3 | No | Java System Properties | | 48. | `"aether.named.ipc.debug"` | `Boolean` | Should the IPC server log debug messages? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | -| 49. | `"aether.named.ipc.family"` | `String` | IPC socket family to use. | `"inet"` | 2.0.1 | No | Java System Properties | +| 49. | `"aether.named.ipc.family"` | `String` | IPC socket family to use. | `"unix"` | 2.0.1 | No | Java System Properties | | 50. | `"aether.named.ipc.idleTimeout"` | `Integer` | IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. | `60` | 2.0.1 | No | Java System Properties | -| 51. | `"aether.named.ipc.nofork"` | `Boolean` | Should the IPC server not fork? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | -| 52. | `"aether.named.ipc.nonative"` | `Boolean` | Should the IPC server not use native executable? | `false` | 2.0.1 | No | Java System Properties | -| 53. | `"aether.offline.hosts"` | `String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | -| 54. | `"aether.offline.protocols"` | `String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | -| 55. | `"aether.priority.cached"` | `Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | -| 56. | `"aether.priority.implicit"` | `Boolean` | A flag indicating whether the priorities of pluggable extensions are implicitly given by their iteration order such that the first extension has the highest priority. If set, an extension's built-in priority as well as any corresponding configuration properties are ignored when searching for a suitable implementation among the available extensions. This priority mode is meant for cases where the application will present/inject extensions in the desired search order. | `false` | | No | Session Configuration | -| 57. | `"aether.remoteRepositoryFilter.groupId"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | -| 58. | `"aether.remoteRepositoryFilter.groupId.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | -| 59. | `"aether.remoteRepositoryFilter.groupId.record"` | `Boolean` | Should filter go into "record" mode (and collect encountered artifacts)? | `false` | 1.9.0 | No | Session Configuration | -| 60. | `"aether.remoteRepositoryFilter.prefixes"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | -| 61. | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | -| 62. | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's used to store a flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | -| 63. | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | -| 64. | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | -| 65. | `"aether.syncContext.named.discriminating.hostname"` | `String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | -| 66. | `"aether.syncContext.named.factory"` | `String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | -| 67. | `"aether.syncContext.named.hashing.depth"` | `Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | -| 68. | `"aether.syncContext.named.nameMapper"` | `String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | -| 69. | `"aether.syncContext.named.redisson.address"` | `String` | Address of the Redis instance. Optional. | `"redis://localhost:6379"` | 2.0.0 | No | Java System Properties | -| 70. | `"aether.syncContext.named.redisson.configFile"` | `String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | -| 71. | `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | -| 72. | `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration | -| 73. | `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration | -| 74. | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | -| 75. | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration | -| 76. | `"aether.transport.apache.https.cipherSuites"` | `String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | -| 77. | `"aether.transport.apache.https.protocols"` | `String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | -| 78. | `"aether.transport.apache.retryHandler.name"` | `String` | The name of retryHandler, supported values are “standard”, that obeys RFC-2616, regarding idempotent methods, and “default” that considers requests w/o payload as idempotent. | `"standard"` | 2.0.0 | Yes | Session Configuration | -| 79. | `"aether.transport.apache.retryHandler.requestSentEnabled"` | `Boolean` | Set to true if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | 2.0.0 | Yes | Session Configuration | -| 80. | `"aether.transport.apache.useSystemProperties"` | `Boolean` | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is not recommended, better use documented ways of configuration instead. | `false` | 2.0.0 | Yes | Session Configuration | -| 81. | `"aether.transport.classpath.loader"` | `ClassLoader` | The key in the repository session's used to store a from which resources should be retrieved. If unspecified, the of the current thread will be used. | - | | No | Session Configuration | -| 82. | `"aether.transport.http.connectTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for a successful connection to a remote server. Non-positive values indicate no timeout. | `10000` | | Yes | Session Configuration | -| 83. | `"aether.transport.http.connectionMaxTtl"` | `Integer` | Total time to live in seconds for an HTTP connection, after that time, the connection will be dropped (no matter for how long it was idle). | `300` | 1.9.8 | Yes | Session Configuration | -| 84. | `"aether.transport.http.credentialEncoding"` | `String` | The encoding/charset to use when exchanging credentials with HTTP servers. Besides this general key, clients may also specify the encoding for a specific remote repository by appending the suffix to this key when storing the charset name. | `"ISO-8859-1"` | | Yes | Session Configuration | -| 85. | `"aether.transport.http.expectContinue"` | `Boolean` | Boolean flag should the HTTP transport use expect-continue handshake for PUT requests. Not all transport support this option. This option may be needed for some broken HTTP servers. Default value corresponds to given transport default one (resolver does not override those), but if configuration IS given, it will replace given transport own default value. | - | 1.9.17 | Yes | Session Configuration | -| 86. | `"aether.transport.http.headers"` | `java.util.Map` | The request headers to use for HTTP-based repository connectors. The headers are specified using a , mapping a header name to its value. Besides this general key, clients may also specify headers for a specific remote repository by appending the suffix to this key when storing the headers map. The repository-specific headers map is supposed to be complete, i.e. is not merged with the general headers map. | - | | Yes | Session Configuration | -| 87. | `"aether.transport.http.localAddress"` | `String` | The local address (interface) to use with HTTP transport. Not all transport supports this option. | - | 2.0.0 | Yes | Session Configuration | -| 88. | `"aether.transport.http.maxConnectionsPerRoute"` | `Integer` | The maximum concurrent connections per route HTTP client is allowed to use. | `50` | 1.9.8 | Yes | Session Configuration | -| 89. | `"aether.transport.http.preemptiveAuth"` | `Boolean` | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default, is disabled, as it is considered less secure. | `false` | 1.9.6 | Yes | Session Configuration | -| 90. | `"aether.transport.http.preemptivePutAuth"` | `Boolean` | Boolean flag should the HTTP transport use preemptive-auth for PUT requests. Not all transport support this option. | `true` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | -| 91. | `"aether.transport.http.requestTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for remaining data to arrive from a remote server. Note that this timeout does not restrict the overall duration of a request, it only restricts the duration of inactivity between consecutive data packets. Non-positive values indicate no timeout. | `1800000` | | Yes | Session Configuration | -| 92. | `"aether.transport.http.retryHandler.count"` | `Integer` | The maximum number of times a request to a remote server should be retried in case of an error. | `3` | 1.9.6 | Yes | Session Configuration | -| 93. | `"aether.transport.http.retryHandler.interval"` | `Long` | The initial retry interval in millis of request to a remote server should be waited in case of "too many requests" (HTTP codes 429 and 503). Accepts long as milliseconds. This value is used if remote server does not use header, in which case Server value is obeyed. | `5000l` | 1.9.16 | Yes | Session Configuration | -| 94. | `"aether.transport.http.retryHandler.intervalMax"` | `Long` | The maximum retry interval in millis of request to a remote server above which the request should be aborted instead. In theory, a malicious server could tell Maven "come back after 100 years" that would stall the build for some. Using this parameter Maven will fail the request instead, if interval is above this value. | `300000l` | 1.9.16 | Yes | Session Configuration | -| 95. | `"aether.transport.http.retryHandler.serviceUnavailable"` | `String` | The HTTP codes of remote server responses that should be handled as "too many requests" (examples: HTTP codes 429 and 503). Accepts comma separated list of HTTP response codes. | `"429,503"` | 1.9.16 | Yes | Session Configuration | -| 96. | `"aether.transport.http.reuseConnections"` | `Boolean` | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | 1.9.8 | Yes | Session Configuration | -| 97. | `"aether.transport.http.supportWebDav"` | `Boolean` | Boolean flag should the HTTP transport support WebDAV remote. Not all transport support this option. | `false` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | -| 98. | `"aether.transport.http.userAgent"` | `String` | The user agent that repository connectors should report to servers. | `"Aether"` | | No | Session Configuration | -| 99. | `"aether.transport.https.securityMode"` | `String` | The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, hostname verification). The default value is . | `"default"` | 1.9.6 | Yes | Session Configuration | -| 100. | `"aether.transport.jdk.httpVersion"` | `String` | Use string representation of HttpClient version enum "HTTP_2" or "HTTP_1_1" to set default HTTP protocol to use. | `"HTTP_2"` | 2.0.0 | Yes | Session Configuration | -| 101. | `"aether.transport.jdk.maxConcurrentRequests"` | `Integer` | The hard limit of maximum concurrent requests JDK transport can do. This is a workaround for the fact, that in HTTP/2 mode, JDK HttpClient initializes this value to Integer.MAX_VALUE (!) and lowers it on first response from the remote server (but it may be too late). See JDK bug JDK-8225647 for details. | `100` | 2.0.0 | Yes | Session Configuration | -| 102. | `"aether.transport.wagon.config"` | `Object` | The configuration to use for the Wagon provider. | - | | Yes | Session Configuration | -| 103. | `"aether.transport.wagon.perms.dirMode"` | `String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 104. | `"aether.transport.wagon.perms.fileMode"` | `String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 105. | `"aether.transport.wagon.perms.group"` | `String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 106. | `"aether.trustedChecksumsSource.sparseDirectory"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | -| 107. | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | -| 108. | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | -| 109. | `"aether.trustedChecksumsSource.summaryFile"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | -| 110. | `"aether.trustedChecksumsSource.summaryFile.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | -| 111. | `"aether.trustedChecksumsSource.summaryFile.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | -| 112. | `"aether.updateCheckManager.sessionState"` | `String` | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If "enabled" will enable the session state. If "bypass" will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | | No | Session Configuration | +| 51. | `"aether.named.ipc.nativeName"` | `String` | The name if the IPC server native executable (without file extension like ".exe") | `"ipc-sync"` | 2.0.1 | No | Java System Properties | +| 52. | `"aether.named.ipc.nofork"` | `Boolean` | Should the IPC server not fork? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | +| 53. | `"aether.named.ipc.nonative"` | `Boolean` | Should the IPC server not use native executable? | `true` | 2.0.1 | No | Java System Properties | +| 54. | `"aether.offline.hosts"` | `String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | +| 55. | `"aether.offline.protocols"` | `String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | +| 56. | `"aether.priority.cached"` | `Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | +| 57. | `"aether.priority.implicit"` | `Boolean` | A flag indicating whether the priorities of pluggable extensions are implicitly given by their iteration order such that the first extension has the highest priority. If set, an extension's built-in priority as well as any corresponding configuration properties are ignored when searching for a suitable implementation among the available extensions. This priority mode is meant for cases where the application will present/inject extensions in the desired search order. | `false` | | No | Session Configuration | +| 58. | `"aether.remoteRepositoryFilter.groupId"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | +| 59. | `"aether.remoteRepositoryFilter.groupId.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | +| 60. | `"aether.remoteRepositoryFilter.groupId.record"` | `Boolean` | Should filter go into "record" mode (and collect encountered artifacts)? | `false` | 1.9.0 | No | Session Configuration | +| 61. | `"aether.remoteRepositoryFilter.prefixes"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | +| 62. | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | +| 63. | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's used to store a flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | +| 64. | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | +| 65. | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | +| 66. | `"aether.syncContext.named.discriminating.hostname"` | `String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | +| 67. | `"aether.syncContext.named.factory"` | `String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | +| 68. | `"aether.syncContext.named.hashing.depth"` | `Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | +| 69. | `"aether.syncContext.named.nameMapper"` | `String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | +| 70. | `"aether.syncContext.named.redisson.address"` | `String` | Address of the Redis instance. Optional. | `"redis://localhost:6379"` | 2.0.0 | No | Java System Properties | +| 71. | `"aether.syncContext.named.redisson.configFile"` | `String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | +| 72. | `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | +| 73. | `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration | +| 74. | `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration | +| 75. | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | +| 76. | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration | +| 77. | `"aether.transport.apache.https.cipherSuites"` | `String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | +| 78. | `"aether.transport.apache.https.protocols"` | `String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | +| 79. | `"aether.transport.apache.retryHandler.name"` | `String` | The name of retryHandler, supported values are “standard”, that obeys RFC-2616, regarding idempotent methods, and “default” that considers requests w/o payload as idempotent. | `"standard"` | 2.0.0 | Yes | Session Configuration | +| 80. | `"aether.transport.apache.retryHandler.requestSentEnabled"` | `Boolean` | Set to true if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | 2.0.0 | Yes | Session Configuration | +| 81. | `"aether.transport.apache.useSystemProperties"` | `Boolean` | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is not recommended, better use documented ways of configuration instead. | `false` | 2.0.0 | Yes | Session Configuration | +| 82. | `"aether.transport.classpath.loader"` | `ClassLoader` | The key in the repository session's used to store a from which resources should be retrieved. If unspecified, the of the current thread will be used. | - | | No | Session Configuration | +| 83. | `"aether.transport.http.connectTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for a successful connection to a remote server. Non-positive values indicate no timeout. | `10000` | | Yes | Session Configuration | +| 84. | `"aether.transport.http.connectionMaxTtl"` | `Integer` | Total time to live in seconds for an HTTP connection, after that time, the connection will be dropped (no matter for how long it was idle). | `300` | 1.9.8 | Yes | Session Configuration | +| 85. | `"aether.transport.http.credentialEncoding"` | `String` | The encoding/charset to use when exchanging credentials with HTTP servers. Besides this general key, clients may also specify the encoding for a specific remote repository by appending the suffix to this key when storing the charset name. | `"ISO-8859-1"` | | Yes | Session Configuration | +| 86. | `"aether.transport.http.expectContinue"` | `Boolean` | Boolean flag should the HTTP transport use expect-continue handshake for PUT requests. Not all transport support this option. This option may be needed for some broken HTTP servers. Default value corresponds to given transport default one (resolver does not override those), but if configuration IS given, it will replace given transport own default value. | - | 1.9.17 | Yes | Session Configuration | +| 87. | `"aether.transport.http.headers"` | `java.util.Map` | The request headers to use for HTTP-based repository connectors. The headers are specified using a , mapping a header name to its value. Besides this general key, clients may also specify headers for a specific remote repository by appending the suffix to this key when storing the headers map. The repository-specific headers map is supposed to be complete, i.e. is not merged with the general headers map. | - | | Yes | Session Configuration | +| 88. | `"aether.transport.http.localAddress"` | `String` | The local address (interface) to use with HTTP transport. Not all transport supports this option. | - | 2.0.0 | Yes | Session Configuration | +| 89. | `"aether.transport.http.maxConnectionsPerRoute"` | `Integer` | The maximum concurrent connections per route HTTP client is allowed to use. | `50` | 1.9.8 | Yes | Session Configuration | +| 90. | `"aether.transport.http.preemptiveAuth"` | `Boolean` | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default, is disabled, as it is considered less secure. | `false` | 1.9.6 | Yes | Session Configuration | +| 91. | `"aether.transport.http.preemptivePutAuth"` | `Boolean` | Boolean flag should the HTTP transport use preemptive-auth for PUT requests. Not all transport support this option. | `true` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | +| 92. | `"aether.transport.http.requestTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for remaining data to arrive from a remote server. Note that this timeout does not restrict the overall duration of a request, it only restricts the duration of inactivity between consecutive data packets. Non-positive values indicate no timeout. | `1800000` | | Yes | Session Configuration | +| 93. | `"aether.transport.http.retryHandler.count"` | `Integer` | The maximum number of times a request to a remote server should be retried in case of an error. | `3` | 1.9.6 | Yes | Session Configuration | +| 94. | `"aether.transport.http.retryHandler.interval"` | `Long` | The initial retry interval in millis of request to a remote server should be waited in case of "too many requests" (HTTP codes 429 and 503). Accepts long as milliseconds. This value is used if remote server does not use header, in which case Server value is obeyed. | `5000l` | 1.9.16 | Yes | Session Configuration | +| 95. | `"aether.transport.http.retryHandler.intervalMax"` | `Long` | The maximum retry interval in millis of request to a remote server above which the request should be aborted instead. In theory, a malicious server could tell Maven "come back after 100 years" that would stall the build for some. Using this parameter Maven will fail the request instead, if interval is above this value. | `300000l` | 1.9.16 | Yes | Session Configuration | +| 96. | `"aether.transport.http.retryHandler.serviceUnavailable"` | `String` | The HTTP codes of remote server responses that should be handled as "too many requests" (examples: HTTP codes 429 and 503). Accepts comma separated list of HTTP response codes. | `"429,503"` | 1.9.16 | Yes | Session Configuration | +| 97. | `"aether.transport.http.reuseConnections"` | `Boolean` | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | 1.9.8 | Yes | Session Configuration | +| 98. | `"aether.transport.http.supportWebDav"` | `Boolean` | Boolean flag should the HTTP transport support WebDAV remote. Not all transport support this option. | `false` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | +| 99. | `"aether.transport.http.userAgent"` | `String` | The user agent that repository connectors should report to servers. | `"Aether"` | | No | Session Configuration | +| 100. | `"aether.transport.https.securityMode"` | `String` | The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, hostname verification). The default value is . | `"default"` | 1.9.6 | Yes | Session Configuration | +| 101. | `"aether.transport.jdk.httpVersion"` | `String` | Use string representation of HttpClient version enum "HTTP_2" or "HTTP_1_1" to set default HTTP protocol to use. | `"HTTP_2"` | 2.0.0 | Yes | Session Configuration | +| 102. | `"aether.transport.jdk.maxConcurrentRequests"` | `Integer` | The hard limit of maximum concurrent requests JDK transport can do. This is a workaround for the fact, that in HTTP/2 mode, JDK HttpClient initializes this value to Integer.MAX_VALUE (!) and lowers it on first response from the remote server (but it may be too late). See JDK bug JDK-8225647 for details. | `100` | 2.0.0 | Yes | Session Configuration | +| 103. | `"aether.transport.wagon.config"` | `Object` | The configuration to use for the Wagon provider. | - | | Yes | Session Configuration | +| 104. | `"aether.transport.wagon.perms.dirMode"` | `String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 105. | `"aether.transport.wagon.perms.fileMode"` | `String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 106. | `"aether.transport.wagon.perms.group"` | `String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 107. | `"aether.trustedChecksumsSource.sparseDirectory"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | +| 108. | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | +| 109. | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | +| 110. | `"aether.trustedChecksumsSource.summaryFile"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | +| 111. | `"aether.trustedChecksumsSource.summaryFile.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | +| 112. | `"aether.trustedChecksumsSource.summaryFile.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | +| 113. | `"aether.updateCheckManager.sessionState"` | `String` | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If "enabled" will enable the session state. If "bypass" will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | | No | Session Configuration | All properties which have `yes` in the column `Supports Repo ID Suffix` can be optionally configured specifically for a repository id. In that case the configuration property needs to be suffixed with a period followed by the repository id of the repository to configure, e.g. `aether.connector.http.headers.central` for repository with id `central`.