diff options
Diffstat (limited to 'vespajlib/src/main/java')
26 files changed, 1314 insertions, 15 deletions
diff --git a/vespajlib/src/main/java/ai/vespa/validation/PathValidator.java b/vespajlib/src/main/java/ai/vespa/validation/PathValidator.java new file mode 100644 index 00000000000..0ae81e2315d --- /dev/null +++ b/vespajlib/src/main/java/ai/vespa/validation/PathValidator.java @@ -0,0 +1,36 @@ +package ai.vespa.validation; + +import java.nio.file.Path; + +/** + * Path validations + * + * @author mortent + */ +public class PathValidator { + + /** + * Validate that file is a child of basedir + * @param root Root directory to use for validation + * @param path Path to validate + * @throws IllegalArgumentException if path is not a child of root + */ + public static void validateChildOf(Path root, Path path) { + if (!path.normalize().startsWith(root)) { + throw new IllegalArgumentException("Invalid path %s".formatted(path)); + } + } + + /** + * Resolves a path under a root path + * @param root root poth + * @param path child to resolve + * @return The resolved path + * @throws IllegalArgumentException If the provided child path does not resolve as child of root + */ + public static Path resolveChildOf(Path root, String path) { + Path resolved = root.resolve(path); + validateChildOf(root, resolved); + return resolved; + } +} diff --git a/vespajlib/src/main/java/com/yahoo/concurrent/Threads.java b/vespajlib/src/main/java/com/yahoo/concurrent/Threads.java new file mode 100644 index 00000000000..d30750692e9 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/concurrent/Threads.java @@ -0,0 +1,30 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.concurrent; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * @author bjorncs + */ +public class Threads { + + private Threads() {} + + /** Returns all threads in JVM */ + public static Collection<Thread> getAllThreads() { + ThreadGroup root = Thread.currentThread().getThreadGroup(); + ThreadGroup parent; + while ((parent = root.getParent()) != null) { + root = parent; + } + // The number of threads may increase between activeCount() and enumerate() + Thread[] threads = new Thread[root.activeCount() + 100]; + int count; + while ((count = root.enumerate(threads, true)) == threads.length) { + threads = new Thread[threads.length + 1000]; + } + return List.of(Arrays.copyOf(threads, count)); + } +} diff --git a/vespajlib/src/main/java/com/yahoo/text/XML.java b/vespajlib/src/main/java/com/yahoo/text/XML.java index c6f235f486c..6aa42773ac0 100644 --- a/vespajlib/src/main/java/com/yahoo/text/XML.java +++ b/vespajlib/src/main/java/com/yahoo/text/XML.java @@ -1,17 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.text; -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.List; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -20,6 +9,16 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + /** * Static XML utility methods * @@ -453,7 +452,7 @@ public class XML { * @throws RuntimeException if we fail to create one */ public static DocumentBuilder getDocumentBuilder() { - return getDocumentBuilder("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", null); + return getDocumentBuilder("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", null, true); } /** @@ -465,12 +464,43 @@ public class XML { * @return a DocumentBuilder */ public static DocumentBuilder getDocumentBuilder(String implementation, ClassLoader classLoader) { + return getDocumentBuilder(true); + } + + /** + * Creates a new XML DocumentBuilder + * + * @return a DocumentBuilder + * @throws RuntimeException if we fail to create one + * @param namespaceAware Whether the parser should be aware of xml namespaces + */ + public static DocumentBuilder getDocumentBuilder(boolean namespaceAware) { + return getDocumentBuilder("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", null, namespaceAware); + } + + /** + * Creates a new XML DocumentBuilder + * + * @param implementation which jaxp implementation should be used + * @param classLoader which class loader should be used when getting a new DocumentBuilder + * @param namespaceAware Whether the parser should be aware of xml namespaces + * @throws RuntimeException if we fail to create one + * @return a DocumentBuilder + */ + public static DocumentBuilder getDocumentBuilder(String implementation, ClassLoader classLoader, boolean namespaceAware) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(implementation, classLoader); - factory.setNamespaceAware(true); - factory.setXIncludeAware(true); - // Prevent XXE + factory.setNamespaceAware(namespaceAware); + // Disable include directives. If enabled this allows inclusion of any resource, such as file:/// and + // http:///, and these are read even if the document eventually fails to parse + factory.setXIncludeAware(false); + // Prevent XXE by disabling DOCTYPE declarations factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + // Disable any kind of external entities. These likely cannot be exploited when doctype is disallowed, but + // it's better to leave them disabled in any case. See + // https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); return factory.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new RuntimeException("Could not create an XML builder", e); diff --git a/vespajlib/src/main/java/com/yahoo/yolean/Exceptions.java b/vespajlib/src/main/java/com/yahoo/yolean/Exceptions.java new file mode 100644 index 00000000000..89b4e76368b --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/Exceptions.java @@ -0,0 +1,202 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Optional; + +/** + * Helper methods for handling exceptions + * + * @author bratseth + */ +public class Exceptions { + + /** + * <p>Returns a user friendly error message string which includes information from all nested exceptions.</p> + * + * <p>The form of this string is + * <code>e.getMessage(): e.getCause().getMessage(): e.getCause().getCause().getMessage()...</code> + * In addition, some heuristics are used to clean up common cases where exception nesting causes bad messages. + */ + public static String toMessageString(Throwable t) { + StringBuilder b = new StringBuilder(); + String lastMessage = null; + String message; + for (; t != null; t = t.getCause()) { + message = getMessage(t); + if (message == null) continue; + if (message.equals(lastMessage)) continue; + if (b.length() > 0) { + b.append(": "); + } + b.append(message); + lastMessage = message; + } + return b.toString(); + } + + /** Returns a useful message from *this* exception, or null if there is nothing useful to return */ + private static String getMessage(Throwable t) { + String message = t.getMessage(); + if (t.getCause() == null) { + if (message == null) return t.getClass().getSimpleName(); + } else { + if (message == null) return null; + if (message.equals(t.getCause().getClass().getName() + ": " + t.getCause().getMessage())) return null; + } + return message; + } + + /** + * Returns the first cause or the given throwable that is an instance of {@code clazz} + */ + public static <T extends Throwable> Optional<T> findCause(Throwable t, Class<T> clazz) { + for (; t != null; t = t.getCause()) { + if (clazz.isInstance(t)) + return Optional.of(clazz.cast(t)); + } + return Optional.empty(); + } + + /** + * Wraps any IOException thrown from a runnable in an UncheckedIOException. + */ + public static void uncheck(RunnableThrowingIOException runnable) { + try { + runnable.run(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void uncheckInterrupted(RunnableThrowingInterruptedException runnable) { + try { + runnable.run(); + } catch (InterruptedException e) { + throw new UncheckedInterruptedException(e, false); + } + } + + public static void uncheckInterruptedAndRestoreFlag(RunnableThrowingInterruptedException runnable) { + try { + runnable.run(); + } catch (InterruptedException e) { + throw new UncheckedInterruptedException(e, true); + } + } + + /** + * Wraps any IOException thrown from a runnable in an UncheckedIOException w/message. + */ + public static void uncheck(RunnableThrowingIOException runnable, String format, String... args) { + try { + runnable.run(); + } catch (IOException e) { + String message = String.format(format, (Object[]) args); + throw new UncheckedIOException(message, e); + } + } + + /** Similar to uncheck(), except an exceptionToIgnore exception is silently ignored. */ + public static <T extends IOException> void uncheckAndIgnore(RunnableThrowingIOException runnable, Class<T> exceptionToIgnore) { + try { + runnable.run(); + } catch (UncheckedIOException e) { + IOException cause = e.getCause(); + if (cause == null) throw e; + try { + cause.getClass().asSubclass(exceptionToIgnore); + } catch (ClassCastException f) { + throw e; + } + // Do nothing - OK + } catch (IOException e) { + try { + e.getClass().asSubclass(exceptionToIgnore); + } catch (ClassCastException f) { + throw new UncheckedIOException(e); + } + // Do nothing - OK + } + } + + @FunctionalInterface + public interface RunnableThrowingIOException { + void run() throws IOException; + } + + @FunctionalInterface public interface RunnableThrowingInterruptedException { void run() throws InterruptedException; } + + /** + * Wraps any IOException thrown from a supplier in an UncheckedIOException. + */ + public static <T> T uncheck(SupplierThrowingIOException<T> supplier) { + try { + return supplier.get(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Wraps any IOException thrown from a supplier in an UncheckedIOException w/message. + */ + public static <T> T uncheck(SupplierThrowingIOException<T> supplier, String format, String... args) { + try { + return supplier.get(); + } catch (IOException e) { + String message = String.format(format, (Object[]) args); + throw new UncheckedIOException(message, e); + } + } + + /** Similar to uncheck(), except null is returned if exceptionToIgnore is thrown. */ + public static <R, T extends IOException> R uncheckAndIgnore(SupplierThrowingIOException<R> supplier, Class<T> exceptionToIgnore) { + try { + return supplier.get(); + } catch (UncheckedIOException e) { + IOException cause = e.getCause(); + if (cause == null) throw e; + try { + cause.getClass().asSubclass(exceptionToIgnore); + } catch (ClassCastException f) { + throw e; + } + return null; + } catch (IOException e) { + try { + e.getClass().asSubclass(exceptionToIgnore); + } catch (ClassCastException f) { + throw new UncheckedIOException(e); + } + return null; + } + } + + @FunctionalInterface + public interface SupplierThrowingIOException<T> { + T get() throws IOException; + } + + /** + * Allows treating checked exceptions as unchecked. + * Usage: + * throw throwUnchecked(e); + * The reason for the return type is to allow writing throw at the call site + * instead of just calling throwUnchecked. Just calling throwUnchecked + * means that the java compiler won't know that the statement will throw an exception, + * and will therefore complain on things such e.g. missing return value. + */ + public static RuntimeException throwUnchecked(Throwable e) { + throwUncheckedImpl(e); + return new RuntimeException(); // Non-null return value to stop tooling from complaining about potential NPE + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void throwUncheckedImpl(Throwable t) throws T { + throw (T)t; + } + + +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java b/vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java new file mode 100644 index 00000000000..d3317b5fb26 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java @@ -0,0 +1,27 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean; + +/** + * Wraps an {@link InterruptedException} with an unchecked exception. + * + * @author bjorncs + */ +public class UncheckedInterruptedException extends RuntimeException { + + public UncheckedInterruptedException(String message, InterruptedException cause, boolean restoreInterruptFlag) { + super(message, cause); + if (restoreInterruptFlag) Thread.currentThread().interrupt(); + } + + public UncheckedInterruptedException(InterruptedException cause, boolean restoreInterruptFlags) { + this(cause.toString(), cause, restoreInterruptFlags); + } + + public UncheckedInterruptedException(String message, boolean restoreInterruptFlag) { this(message, null, false); } + + public UncheckedInterruptedException(String message, InterruptedException cause) { this(message, cause, false); } + + public UncheckedInterruptedException(InterruptedException cause) { this(cause.toString(), cause, false); } + + @Override public InterruptedException getCause() { return (InterruptedException) super.getCause(); } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/chain/After.java b/vespajlib/src/main/java/com/yahoo/yolean/chain/After.java new file mode 100644 index 00000000000..a02408bb616 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/chain/After.java @@ -0,0 +1,23 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.chain; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The component that is annotated with this must be placed later than the components or phases providing names + * contained in the given list. + * + * @author Tony Vaagenes + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface After { + + String[] value() default { }; + +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/chain/Before.java b/vespajlib/src/main/java/com/yahoo/yolean/chain/Before.java new file mode 100644 index 00000000000..7bbba8ded5f --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/chain/Before.java @@ -0,0 +1,23 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.chain; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The component that is annotated with this must be placed earlier than the components or phases providing names + * contained in the given list. + * + * @author Tony Vaagenes + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface Before { + + String[] value() default { }; + +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/chain/Provides.java b/vespajlib/src/main/java/com/yahoo/yolean/chain/Provides.java new file mode 100644 index 00000000000..b8bf40686cb --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/chain/Provides.java @@ -0,0 +1,23 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.chain; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p>Mark this component as providing some named functionality. Other components can then mark themselves as "before" + * and "after" the string provided here, to impose constraints on ordering.</p> + * + * @author Tony Vaagenes + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface Provides { + + String[] value() default { }; + +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/chain/package-info.java b/vespajlib/src/main/java/com/yahoo/yolean/chain/package-info.java new file mode 100644 index 00000000000..e767e192ad7 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/chain/package-info.java @@ -0,0 +1,6 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +@PublicApi package com.yahoo.yolean.chain; + +import com.yahoo.api.annotations.PublicApi; +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ConcurrentResourcePool.java b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ConcurrentResourcePool.java new file mode 100644 index 00000000000..0e91a44bf5d --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ConcurrentResourcePool.java @@ -0,0 +1,53 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.concurrent; + +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Supplier; + +/** + * A pool of a resource. This create new instances of the resource on request until enough are created + * to deliver a unique one to all threads needing one concurrently and then reuse those instances + * in subsequent requests. + * + * @author baldersheim + */ +public class ConcurrentResourcePool<T> implements Iterable<T> { + + private final Queue<T> pool = new ConcurrentLinkedQueue<>(); + private final Supplier<T> factory; + + + public ConcurrentResourcePool(Supplier<T> factory) { + this.factory = factory; + } + + public void preallocate(int instances) { + for (int i = 0; i < instances; i++) { + pool.offer(factory.get()); + } + } + + /** + * Allocates an instance of the resource to the requestor. + * The resource will be allocated exclusively to the requestor until it calls free(instance). + * + * @return a reused or newly created instance of the resource + */ + public final T alloc() { + T e = pool.poll(); + return e != null ? e : factory.get(); + } + + /** Frees an instance previously acquired bty alloc */ + public final void free(T e) { + pool.offer(e); + } + + @Override + public Iterator<T> iterator() { + return pool.iterator(); + } + +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMap.java b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMap.java new file mode 100644 index 00000000000..536d9ab15c1 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMap.java @@ -0,0 +1,93 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.concurrent; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * <p>This is a thread hash map for small collections that are stable once built. Until it is stable there will be a + * race among all threads missing something in the map. They will then clone the map add the missing stuff and then put + * it back as active again. Here are no locks, but the cost is that inserts will happen a lot more than necessary. The + * map reference is volatile, but on most multi-cpu machines that has no cost unless modified.</p> + * + * @author baldersheim + */ +public class CopyOnWriteHashMap<K, V> implements Map<K, V> { + + private volatile HashMap<K, V> map = new HashMap<>(); + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + return map.get(key); + } + + @Override + public V put(K key, V value) { + HashMap<K, V> next = new HashMap<>(map); + V old = next.put(key, value); + map = next; + return old; + } + + @Override + @SuppressWarnings("SuspiciousMethodCalls") + public V remove(Object key) { + HashMap<K, V> prev = map; + if (!prev.containsKey(key)) { + return null; + } + HashMap<K, V> next = new HashMap<>(prev); + V old = next.remove(key); + map = next; + return old; + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + HashMap<K, V> next = new HashMap<>(map); + next.putAll(m); + map = next; + } + + @Override + public void clear() { + map = new HashMap<>(); + } + + @Override + public Set<K> keySet() { + return map.keySet(); + } + + @Override + public Collection<V> values() { + return map.values(); + } + + @Override + public Set<Entry<K, V>> entrySet() { + return map.entrySet(); + } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/concurrent/Memoized.java b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/Memoized.java new file mode 100644 index 00000000000..8e2b7b7a7eb --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/Memoized.java @@ -0,0 +1,103 @@ +package com.yahoo.yolean.concurrent; + +import com.yahoo.api.annotations.Beta; + +import java.util.function.Function; +import java.util.function.Supplier; + +import static java.util.Objects.requireNonNull; + +/** + * Wraps a lazily initialised resource which needs to be shut down. + * The wrapped supplier may not return {@code null}, and should be retryable on failure. + * If it throws, it will be retried if {@link #get} is retried. A supplier that fails to + * clean up partial state on failure may cause a resource leak. + * + * @author jonmv + */ +@Beta +public class Memoized<T, E extends Exception> implements Supplier<T>, AutoCloseable { + + /** + * Provides a tighter bound on the thrown exception type. + */ + @FunctionalInterface + public interface Closer<T, E extends Exception> { + + void close(T t) throws E; + + } + + + private final Object monitor = new Object(); + private final Closer<T, E> closer; + private volatile T wrapped; + private Supplier<T> factory; + + public Memoized(Supplier<T> factory, Closer<T, E> closer) { + this.factory = requireNonNull(factory); + this.closer = requireNonNull(closer); + } + + public static <T extends AutoCloseable> Memoized<T, ?> of(Supplier<T> factory) { + return new Memoized<>(factory, AutoCloseable::close); + } + + public static <T, U, E extends Exception> Memoized<U, E> combine(Memoized<T, ? extends E> inner, Function<T, U> outer, Closer<U, ? extends E> closer) { + return new Memoized<>(() -> outer.apply(inner.get()), compose(closer, inner::close)); + } + + @Override + public T get() { + // Double-checked locking: try the variable, and if not initialized, try to initialize it. + if (wrapped == null) synchronized (monitor) { + // Ensure the factory is called only once, by clearing it once successfully called. + if (factory != null) wrapped = requireNonNull(factory.get()); + factory = null; + + // If we found the factory, we won the initialization race, and return normally; otherwise + // if wrapped is non-null, we lost the race, wrapped was set by the winner, and we return; otherwise + // we tried to initialise because wrapped was cleared by closing this, and we fail. + if (wrapped == null) throw new IllegalStateException("already closed"); + } + return wrapped; + } + + @Override + public void close() throws E { + // Alter state only when synchronized with calls to get(). + synchronized (monitor) { + // Ensure we only try to close the generated resource once, by clearing it after picking it up here. + T maybe = wrapped; + wrapped = null; + // Clear the factory, to signal this has been closed. + factory = null; + if (maybe != null) closer.close(maybe); + } + } + + private interface Thrower<E extends Exception> { void call() throws E; } + + private static <T, E extends Exception> Closer<T, E> compose(Closer<T, ? extends E> outer, Thrower<? extends E> inner) { + return parent -> { + Exception thrown = null; + try { + outer.close(parent); + } + catch (Exception e) { + thrown = e; + } + try { + inner.call(); + } + catch (Exception e) { + if (thrown != null) thrown.addSuppressed(e); + else thrown = e; + } + @SuppressWarnings("unchecked") + E e = (E) thrown; + if (e != null) throw e; + }; + } + +}
\ No newline at end of file diff --git a/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ResourcePool.java b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ResourcePool.java new file mode 100644 index 00000000000..ffc761ad625 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ResourcePool.java @@ -0,0 +1,38 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.concurrent; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.function.Supplier; + +/** + * <p>This implements a simple stack based resource pool. If you are out of resources new are allocated from the + * factory.</p> + * + * @author baldersheim + * @since 5.2 + */ +public final class ResourcePool<T> implements Iterable<T> { + + private final Deque<T> pool = new ArrayDeque<>(); + private final Supplier<T> factory; + + + public ResourcePool(Supplier<T> factory) { + this.factory = factory; + } + + public T alloc() { + return pool.isEmpty() ? factory.get() : pool.pop(); + } + + public void free(T e) { + pool.push(e); + } + + @Override + public Iterator<T> iterator() { + return pool.iterator(); + } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/concurrent/Sleeper.java b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/Sleeper.java new file mode 100644 index 00000000000..530be935bc1 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/Sleeper.java @@ -0,0 +1,29 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.concurrent; + +import com.yahoo.yolean.UncheckedInterruptedException; + +import java.time.Duration; + +import static com.yahoo.yolean.Exceptions.uncheckInterrupted; + +/** + * An abstraction used for mocking {@link Thread#sleep(long)} in unit tests. + * + * @author bjorncs + */ +public interface Sleeper { + default void sleep(Duration duration) throws UncheckedInterruptedException { + uncheckInterrupted(() -> sleepChecked(duration.toMillis())); + } + + default void sleepChecked(Duration duration) throws InterruptedException { sleepChecked(duration.toMillis()); } + + default void sleep(long millis) throws UncheckedInterruptedException { uncheckInterrupted(() -> sleepChecked(millis)); } + + void sleepChecked(long millis) throws InterruptedException; + + Sleeper DEFAULT = Thread::sleep; + Sleeper NOOP = millis -> {}; + +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ThreadRobustList.java b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ThreadRobustList.java new file mode 100644 index 00000000000..f6d8b68416c --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/ThreadRobustList.java @@ -0,0 +1,122 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.concurrent; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * <p>This class implements a thread-safe, lock-free list of Objects that supports multiple readers and a single writer. + * Because there are no locks or other memory barriers involved, there exists no <i>happens-before</i> relationship + * among calls to either methods of the <code>ThreadRobustList</code>. This means that there are no guarantees as to when + * (or even if) an item {@link #add(Object)}ed becomes visible through {@link #iterator()}. If visibility is required, + * either use explicit synchronization between reader and writer thread, or move to a different concurrent collection + * (e.g. <code>CopyOnWriteArrayList</code>).</p> + * <p>Because it is lock-free, the <code>ThreadRobustList</code> has minimal overhead to both reading and writing. The + * iterator offered by this class always observes the list in a consistent state, and it never throws a + * <code>ConcurrentModificationException</code>.</p> + * <p>The <code>ThreadRobustList</code> does not permit adding <code>null</code> items.</p> + * <p>The usage of <code>ThreadRobustList</code> has no memory consistency effects. </p> + * + * @author Steinar Knutsen + * @author bratseth + * @since 5.1.15 + */ +public class ThreadRobustList<T> implements Iterable<T> { + + private Object[] items; + private int next = 0; + + /** + * <p>Constructs a new instance of this class with an initial capacity of <code>10</code>.</p> + */ + public ThreadRobustList() { + this(10); + } + + /** + * <p>Constructs a new instance of this class with a given initial capacity.</p> + * + * @param initialCapacity the initial capacity of this list + */ + public ThreadRobustList(int initialCapacity) { + items = new Object[initialCapacity]; + } + + /** + * <p>Returns whether or not this list is empty.</p> + * + * @return <code>true</code> if this list has zero items + */ + public boolean isEmpty() { + return next == 0; + } + + /** + * <p>Adds an item to this list. As opposed to <code>CopyOnWriteArrayList</code>, items added to this list may become + * visible to iterators created <em>before</em> a call to this method.</p> + * + * @param item the item to add + * @throws NullPointerException if <code>item</code> is <code>null</code> + */ + public void add(T item) { + if (item == null) { + throw new NullPointerException(); + } + Object[] workItems = items; + if (next >= items.length) { + workItems = Arrays.copyOf(workItems, 20 + items.length * 2); + workItems[next++] = item; + items = workItems; + } else { + workItems[next++] = item; + } + } + + /** + * <p>Returns an iterator over the items in this list. As opposed to <code>CopyOnWriteArrayList</code>, this iterator + * may see items added to the <code>ThreadRobustList</code> even if they occur <em>after</em> a call to this method.</p> + * <p>The returned iterator does not support <code>remove()</code>.</p> + * + * @return an iterator over this list + */ + @Override + public Iterator<T> iterator() { + return new ThreadRobustIterator<>(items); + } + + private static class ThreadRobustIterator<T> implements Iterator<T> { + + final Object[] items; + int nextIndex = 0; + + ThreadRobustIterator(Object[] items) { + this.items = items; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return (T)items[nextIndex++]; + } + + @Override + public boolean hasNext() { + if (nextIndex >= items.length) { + return false; + } + if (items[nextIndex] == null) { + return false; + } + return true; + } + } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/concurrent/package-info.java b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/package-info.java new file mode 100644 index 00000000000..1e89d85714e --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/concurrent/package-info.java @@ -0,0 +1,7 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +@PublicApi +package com.yahoo.yolean.concurrent; + +import com.yahoo.api.annotations.PublicApi; +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingConsumer.java b/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingConsumer.java new file mode 100644 index 00000000000..0860c7c34b4 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingConsumer.java @@ -0,0 +1,20 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.function; + +import java.util.Objects; + +/** + * Functional interface that mirrors the Consumer interface, but allows for an + * exception to be thrown. + * + * @author oyving + */ +@FunctionalInterface +public interface ThrowingConsumer<T, E extends Throwable> { + void accept(T input) throws E; + + default ThrowingConsumer<T, E> andThen(ThrowingConsumer<? super T, ? extends E> after) { + Objects.requireNonNull(after); + return (T t) -> { accept(t); after.accept(t); }; + } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingFunction.java b/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingFunction.java new file mode 100644 index 00000000000..6e459509b1d --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingFunction.java @@ -0,0 +1,25 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.function; + +import java.util.Objects; + +/** + * Functional interface that mirrors the Function interface, but allows for an + * exception to be thrown. + * + * @author oyving + */ +@FunctionalInterface +public interface ThrowingFunction<T, R, E extends Throwable> { + R apply(T input) throws E; + + default <V> ThrowingFunction<T, V, E> andThen(ThrowingFunction<? super R, ? extends V, ? extends E> after) { + Objects.requireNonNull(after); + return (T t) -> after.apply(apply(t)); + } + + default <V> ThrowingFunction<V, R, E> compose(ThrowingFunction<? super V, ? extends T, ? extends E> before) { + Objects.requireNonNull(before); + return (V v) -> apply(before.apply(v)); + } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingSupplier.java b/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingSupplier.java new file mode 100644 index 00000000000..348c1c739ee --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingSupplier.java @@ -0,0 +1,13 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.function; + +/** + * Functional interface that mirrors the Supplier interface, but allows for an + * exception to be thrown. + * + * @author oyving + */ +@FunctionalInterface +public interface ThrowingSupplier<R, E extends Throwable> { + R get() throws E; +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/function/package-info.java b/vespajlib/src/main/java/com/yahoo/yolean/function/package-info.java new file mode 100644 index 00000000000..e55b39c478f --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/function/package-info.java @@ -0,0 +1,10 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author oyving + */ +@ExportPackage +@PublicApi +package com.yahoo.yolean.function; + +import com.yahoo.api.annotations.PublicApi; +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/vespajlib/src/main/java/com/yahoo/yolean/package-info.java b/vespajlib/src/main/java/com/yahoo/yolean/package-info.java new file mode 100644 index 00000000000..c9f2b088688 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/package-info.java @@ -0,0 +1,7 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +@PublicApi +package com.yahoo.yolean; + +import com.yahoo.api.annotations.PublicApi; +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/vespajlib/src/main/java/com/yahoo/yolean/system/CatchSignals.java b/vespajlib/src/main/java/com/yahoo/yolean/system/CatchSignals.java new file mode 100644 index 00000000000..572d8fba122 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/system/CatchSignals.java @@ -0,0 +1,75 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.system; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.concurrent.atomic.AtomicBoolean; + +// import sun.misc.Signal; +// import sun.misc.SignalHandler; + + +public class CatchSignals { + /** + * Sets up a signal handler for SIGTERM and SIGINT, where a given AtomicBoolean + * gets a true value when the signal is caught. + * + * Callers basically have two options for acting on the signal: + * + * They may choose to synchronize and wait() on this variable, + * and they will be notified when it changes state to true. To avoid + * problems with spurious wakeups, use a while loop and wait() + * again if the state is still false. As soon as the caller has been + * woken up and the state is true, the application should exit as + * soon as possible. + * + * They may also choose to poll the state of this variable. As soon + * as its state becomes true, the signal has been received, and the + * application should exit as soon as possible. + * + * @param signalCaught set to false initially, will be set to true when SIGTERM or SIGINT is caught. + */ + @SuppressWarnings("rawtypes") + public static void setup(final AtomicBoolean signalCaught) { + signalCaught.set(false); + try { + Class shc = Class.forName("sun.misc.SignalHandler"); + Class ssc = Class.forName("sun.misc.Signal"); + + InvocationHandler ihandler = new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + synchronized (signalCaught) { + signalCaught.set(true); + signalCaught.notifyAll(); + } + return null; + } + }; + Object shandler = Proxy.newProxyInstance(CatchSignals.class.getClassLoader(), + new Class[] { shc }, + ihandler); + Constructor[] c = ssc.getDeclaredConstructors(); + assert c.length == 1; + Object sigterm = c[0].newInstance("TERM"); + Object sigint = c[0].newInstance("INT"); + Method m = findMethod(ssc, "handle"); + assert m != null; // "NoSuchMethodException" + m.invoke(null, sigterm, shandler); + m.invoke(null, sigint, shandler); + } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + System.err.println("FAILED setting up signal catching: "+e); + } + } + + private static Method findMethod(Class<?> c, String name) { + for (Method m : c.getDeclaredMethods()) { + if (m.getName().equals(name)) { + return m; + } + } + return null; + } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/system/package-info.java b/vespajlib/src/main/java/com/yahoo/yolean/system/package-info.java new file mode 100644 index 00000000000..cf3a4f33e1a --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/system/package-info.java @@ -0,0 +1,5 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.yolean.system; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/vespajlib/src/main/java/com/yahoo/yolean/trace/TraceNode.java b/vespajlib/src/main/java/com/yahoo/yolean/trace/TraceNode.java new file mode 100644 index 00000000000..fd19c1b1388 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/trace/TraceNode.java @@ -0,0 +1,247 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.trace; + +import com.yahoo.yolean.concurrent.ThreadRobustList; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * <p>This class represents a single node in a tree of <code>TraceNodes</code>. The trace forms a tree where there is a + * branch for each parallel execution, and a node within such a branch for each traced event. As each <code>TraceNode</code> + * may contain a payload of any type, the trace tree can be used to exchange any thread-safe state between producers and + * consumers in different threads, whether or not the shape of the trace tree is relevant to the particular + * information.</p> + * <p>This class uses a {@link ThreadRobustList} for its children. That list allows multiple threads to inspect the + * hierarchy of a <code>TraceNode</code> tree while there are other threads concurrently modifying it, without incurring the + * cost of memory synchronization. The only caveat being that for each <code>TraceNode</code> there can never be more than + * exactly one writer thread. If multiple threads need to mutate a single <code>TraceNode</code>, then the writer threads + * need to synchronize their access on the <code>TraceNode</code>.</p> + * + * @author Steinar Knutsen + * @author bratseth + * @since 5.1.15 + */ +public class TraceNode { + + private final Object payload; + private final long timestamp; + private ThreadRobustList<TraceNode> children; + private TraceNode parent; + + /** + * <p>Creates a new instance of this class.</p> + * + * @param payload the payload to assign to this, may be <code>null</code> + * @param timestamp the timestamp to assign to this + */ + public TraceNode(Object payload, long timestamp) { + this.payload = payload; + this.timestamp = timestamp; + } + + /** + * <p>Adds another <code>TraceNode</code> as a child to this.</p> + * + * @param child the TraceNode to add + * @return this, to allow chaining + * @throws IllegalArgumentException if <code>child</code> is not a root TraceNode + * @see #isRoot() + */ + public TraceNode add(TraceNode child) { + if (child.parent != null) { + throw new IllegalArgumentException("Can not add " + child + " to " + this + "; it is not a root."); + } + child.parent = this; + if (children == null) { + children = new ThreadRobustList<>(); + } + children.add(child); + return this; + } + + /** + * <p>Returns a read-only iterable of all {@link #payload() payloads} that are instances of <code>payloadType</code>, + * in all its decendants. The payload of <em>this</em> <code>TraceNode</code> is ignored.</p> + * <p>The payloads are retrieved in depth-first, prefix order.</p> + * + * @param payloadType the type of payloads to retrieve + * @return the payloads, never <code>null</code> + */ + public <PAYLOADTYPE> Iterable<PAYLOADTYPE> descendants(final Class<PAYLOADTYPE> payloadType) { + if (children == null) { + return Collections.emptyList(); + } + return new Iterable<PAYLOADTYPE>() { + + @Override + public Iterator<PAYLOADTYPE> iterator() { + return new PayloadIterator<>(TraceNode.this, payloadType); + } + }; + } + + /** + * <p>Returns the payload of this <code>TraceNode</code>, or null if none.</p> + * + * @return the payload + */ + public Object payload() { + return payload; + } + + /** + * <p>Returns the timestamp of this <code>TraceNode</code>.</p> + * + * @return the timestamp + */ + public long timestamp() { + return timestamp; + } + + /** + * <p>Returns the parent <code>TraceNode</code> of this.</p> + * + * @return the parent + */ + public TraceNode parent() { + return parent; + } + + /** + * <p>Returns the child <code>TraceNodes</code> of this.</p> + * + * @return the children + */ + public Iterable<TraceNode> children() { + if (children == null) { + return Collections.emptyList(); + } + return children; + } + + /** + * <p>Returns whether or not this <code>TraceNode</code> is a root node (i.e. it has no parent).</p> + * + * @return <code>true</code> if {@link #parent()} returns <code>null</code> + */ + public boolean isRoot() { + return parent == null; + } + + /** + * <p>Returns the root <code>TraceNode</code> of the tree that this <code>TraceNode</code> belongs to.</p> + * + * @return the root + */ + public TraceNode root() { + TraceNode node = this; + while (node.parent != null) { + node = node.parent; + } + return node; + } + + /** + * <p>Visits this <code>TraceNode</code> and all of its descendants in depth-first, prefix order.</p> + * + * @param visitor The visitor to accept. + * @return The <code>visitor</code> parameter. + */ + public <T extends TraceVisitor> T accept(T visitor) { + visitor.visit(this); + if (children == null || children.isEmpty()) { + return visitor; + } + visitor.entering(this); + for (TraceNode child : children) { + child.accept(visitor); + } + visitor.leaving(this); + return visitor; + } + + @Override + public String toString() { + final StringBuilder out = new StringBuilder("[ "); + accept(new TraceVisitor() { + + @Override + public void visit(TraceNode node) { + if (node.payload != null) { + out.append(node.payload).append(" "); + } + } + + @Override + public void entering(TraceNode node) { + out.append("[ "); + } + + @Override + public void leaving(TraceNode node) { + out.append("] "); + } + }); + return out.append("]").toString(); + } + + private static class PayloadIterator<PAYLOADTYPE> implements Iterator<PAYLOADTYPE> { + + final List<TraceNode> unexploredNodes = new LinkedList<>(); + final Class<PAYLOADTYPE> payloadType; + PAYLOADTYPE next; + + PayloadIterator(TraceNode root, Class<PAYLOADTYPE> payloadType) { + payloadType.getClass(); // throws NullPointerException + this.payloadType = payloadType; + unexploredNodes.add(root); + next = advance(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public PAYLOADTYPE next() { + if (next == null) { + throw new NoSuchElementException(); + } + PAYLOADTYPE current = next; + next = advance(); + return current; + } + + PAYLOADTYPE advance() { + // Current node is depleted, find next + while (unexploredNodes.size() > 0) { + // Take the next node + TraceNode node = unexploredNodes.remove(0); + + // Add its children to the list of nodes we1'll look at + if (node.children != null) { + int i = 0; // used to fabricate depth-first traversal order + for (TraceNode child : node.children) { + unexploredNodes.add(i++, child); + } + } + + Object payload = node.payload(); + if (payloadType.isInstance(payload)) { + return payloadType.cast(payload); + } + } + return null; + } + } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/trace/TraceVisitor.java b/vespajlib/src/main/java/com/yahoo/yolean/trace/TraceVisitor.java new file mode 100644 index 00000000000..1b3507777b7 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/trace/TraceVisitor.java @@ -0,0 +1,45 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.yolean.trace; + +/** + * <p>This class is an abstract visitor of {@link TraceNode}. See {@link TraceNode#accept(TraceVisitor)}.</p> + * + * @author bratseth + * @since 5.1.15 + */ +public abstract class TraceVisitor { + + /** + * <p>Visits a {@link TraceNode}. Called by {@link TraceNode#accept(TraceVisitor)}, before visiting its + * children.</p> + * + * @param node the <code>TraceNode</code> being visited + * @see TraceNode#accept(TraceVisitor) + */ + public abstract void visit(TraceNode node); + + /** + * <p>Enters a {@link TraceNode}. This method is called after {@link #visit(TraceNode)}, but before visiting its + * children. Note that this method is NOT called if a <code>TraceNode</code> has zero children.</p> + * <p>The default implementation of this method does nothing.</p> + * + * @param node the <code>TraceNode</code> being entered + * @see #leaving(TraceNode) + */ + @SuppressWarnings("UnusedParameters") + public void entering(TraceNode node) { + // empty + } + + /** + * <p>Leaves a {@link TraceNode}. This method is called after {@link #entering(TraceNode)}, and after visiting its + * children. Note that this method is NOT called if a <code>TraceNode</code> has zero children.</p> + * <p>The default implementation of this method does nothing.</p> + * + * @param node the <code>TraceNode</code> being left + */ + @SuppressWarnings("UnusedParameters") + public void leaving(TraceNode node) { + // empty + } +} diff --git a/vespajlib/src/main/java/com/yahoo/yolean/trace/package-info.java b/vespajlib/src/main/java/com/yahoo/yolean/trace/package-info.java new file mode 100644 index 00000000000..dabc8217025 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/yolean/trace/package-info.java @@ -0,0 +1,7 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +@PublicApi +package com.yahoo.yolean.trace; + +import com.yahoo.api.annotations.PublicApi; +import com.yahoo.osgi.annotation.ExportPackage; |