summaryrefslogtreecommitdiffstats
path: root/vespajlib
diff options
context:
space:
mode:
authorgjoranv <gv@verizonmedia.com>2022-07-13 11:38:09 +0200
committergjoranv <gv@verizonmedia.com>2022-07-13 11:39:09 +0200
commit3ad0117c5ec96bb3d32495a222409df074cfd7d9 (patch)
tree3f2d0024cbf719b8cd38365016e1dd49754b5a48 /vespajlib
parent0781a0b3ce6b7433f442d3a1bc3097272971f0ba (diff)
Move yolean code into vespajlib.
Diffstat (limited to 'vespajlib')
-rw-r--r--vespajlib/abi-spec.json330
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/Exceptions.java202
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/UncheckedInterruptedException.java27
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/chain/After.java23
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/chain/Before.java23
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/chain/Provides.java23
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/chain/package-info.java6
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/concurrent/ConcurrentResourcePool.java53
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMap.java93
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/concurrent/Memoized.java103
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/concurrent/ResourcePool.java38
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/concurrent/Sleeper.java29
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/concurrent/ThreadRobustList.java122
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/concurrent/package-info.java7
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingConsumer.java20
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingFunction.java25
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/function/ThrowingSupplier.java13
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/function/package-info.java10
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/package-info.java7
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/system/CatchSignals.java75
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/system/package-info.java5
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/trace/TraceNode.java247
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/trace/TraceVisitor.java45
-rw-r--r--vespajlib/src/main/java/com/yahoo/yolean/trace/package-info.java7
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java81
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java106
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/concurrent/MemoizedTest.java101
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java146
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/system/CatchSignalsTestCase.java18
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java231
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java21
31 files changed, 2237 insertions, 0 deletions
diff --git a/vespajlib/abi-spec.json b/vespajlib/abi-spec.json
index 2c861dd2f1f..97eaa1f76f6 100644
--- a/vespajlib/abi-spec.json
+++ b/vespajlib/abi-spec.json
@@ -3637,5 +3637,335 @@
"public abstract com.yahoo.vespa.objects.Serializer put(com.yahoo.vespa.objects.FieldBase, java.lang.String)"
],
"fields": []
+ },
+ "com.yahoo.yolean.Exceptions$RunnableThrowingIOException": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract void run()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.Exceptions$RunnableThrowingInterruptedException": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract void run()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.Exceptions$SupplierThrowingIOException": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract java.lang.Object get()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.Exceptions": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>()",
+ "public static java.lang.String toMessageString(java.lang.Throwable)",
+ "public static java.util.Optional findCause(java.lang.Throwable, java.lang.Class)",
+ "public static void uncheck(com.yahoo.yolean.Exceptions$RunnableThrowingIOException)",
+ "public static void uncheckInterrupted(com.yahoo.yolean.Exceptions$RunnableThrowingInterruptedException)",
+ "public static void uncheckInterruptedAndRestoreFlag(com.yahoo.yolean.Exceptions$RunnableThrowingInterruptedException)",
+ "public static varargs void uncheck(com.yahoo.yolean.Exceptions$RunnableThrowingIOException, java.lang.String, java.lang.String[])",
+ "public static void uncheckAndIgnore(com.yahoo.yolean.Exceptions$RunnableThrowingIOException, java.lang.Class)",
+ "public static java.lang.Object uncheck(com.yahoo.yolean.Exceptions$SupplierThrowingIOException)",
+ "public static varargs java.lang.Object uncheck(com.yahoo.yolean.Exceptions$SupplierThrowingIOException, java.lang.String, java.lang.String[])",
+ "public static java.lang.Object uncheckAndIgnore(com.yahoo.yolean.Exceptions$SupplierThrowingIOException, java.lang.Class)",
+ "public static java.lang.RuntimeException throwUnchecked(java.lang.Throwable)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.UncheckedInterruptedException": {
+ "superClass": "java.lang.RuntimeException",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.lang.String, java.lang.InterruptedException, boolean)",
+ "public void <init>(java.lang.InterruptedException, boolean)",
+ "public void <init>(java.lang.String, boolean)",
+ "public void <init>(java.lang.String, java.lang.InterruptedException)",
+ "public void <init>(java.lang.InterruptedException)",
+ "public java.lang.InterruptedException getCause()",
+ "public bridge synthetic java.lang.Throwable getCause()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.chain.After": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "java.lang.annotation.Annotation"
+ ],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract",
+ "annotation"
+ ],
+ "methods": [
+ "public abstract java.lang.String[] value()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.chain.Before": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "java.lang.annotation.Annotation"
+ ],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract",
+ "annotation"
+ ],
+ "methods": [
+ "public abstract java.lang.String[] value()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.chain.Provides": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "java.lang.annotation.Annotation"
+ ],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract",
+ "annotation"
+ ],
+ "methods": [
+ "public abstract java.lang.String[] value()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.concurrent.ConcurrentResourcePool": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "java.lang.Iterable"
+ ],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.util.function.Supplier)",
+ "public void preallocate(int)",
+ "public final java.lang.Object alloc()",
+ "public final void free(java.lang.Object)",
+ "public java.util.Iterator iterator()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.concurrent.CopyOnWriteHashMap": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "java.util.Map"
+ ],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>()",
+ "public int size()",
+ "public boolean isEmpty()",
+ "public boolean containsKey(java.lang.Object)",
+ "public boolean containsValue(java.lang.Object)",
+ "public java.lang.Object get(java.lang.Object)",
+ "public java.lang.Object put(java.lang.Object, java.lang.Object)",
+ "public java.lang.Object remove(java.lang.Object)",
+ "public void putAll(java.util.Map)",
+ "public void clear()",
+ "public java.util.Set keySet()",
+ "public java.util.Collection values()",
+ "public java.util.Set entrySet()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.concurrent.Memoized$Closer": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract void close(java.lang.Object)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.concurrent.Memoized": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "java.util.function.Supplier",
+ "java.lang.AutoCloseable"
+ ],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.util.function.Supplier, com.yahoo.yolean.concurrent.Memoized$Closer)",
+ "public static com.yahoo.yolean.concurrent.Memoized of(java.util.function.Supplier)",
+ "public static com.yahoo.yolean.concurrent.Memoized combine(com.yahoo.yolean.concurrent.Memoized, java.util.function.Function, com.yahoo.yolean.concurrent.Memoized$Closer)",
+ "public java.lang.Object get()",
+ "public void close()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.concurrent.ResourcePool": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "java.lang.Iterable"
+ ],
+ "attributes": [
+ "public",
+ "final"
+ ],
+ "methods": [
+ "public void <init>(java.util.function.Supplier)",
+ "public java.lang.Object alloc()",
+ "public void free(java.lang.Object)",
+ "public java.util.Iterator iterator()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.concurrent.Sleeper": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public void sleep(java.time.Duration)",
+ "public void sleepChecked(java.time.Duration)",
+ "public void sleep(long)",
+ "public abstract void sleepChecked(long)"
+ ],
+ "fields": [
+ "public static final com.yahoo.yolean.concurrent.Sleeper DEFAULT",
+ "public static final com.yahoo.yolean.concurrent.Sleeper NOOP"
+ ]
+ },
+ "com.yahoo.yolean.concurrent.ThreadRobustList": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "java.lang.Iterable"
+ ],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>()",
+ "public void <init>(int)",
+ "public boolean isEmpty()",
+ "public void add(java.lang.Object)",
+ "public java.util.Iterator iterator()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.function.ThrowingConsumer": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract void accept(java.lang.Object)",
+ "public com.yahoo.yolean.function.ThrowingConsumer andThen(com.yahoo.yolean.function.ThrowingConsumer)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.function.ThrowingFunction": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract java.lang.Object apply(java.lang.Object)",
+ "public com.yahoo.yolean.function.ThrowingFunction andThen(com.yahoo.yolean.function.ThrowingFunction)",
+ "public com.yahoo.yolean.function.ThrowingFunction compose(com.yahoo.yolean.function.ThrowingFunction)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.function.ThrowingSupplier": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract java.lang.Object get()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.trace.TraceNode": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.lang.Object, long)",
+ "public com.yahoo.yolean.trace.TraceNode add(com.yahoo.yolean.trace.TraceNode)",
+ "public java.lang.Iterable descendants(java.lang.Class)",
+ "public java.lang.Object payload()",
+ "public long timestamp()",
+ "public com.yahoo.yolean.trace.TraceNode parent()",
+ "public java.lang.Iterable children()",
+ "public boolean isRoot()",
+ "public com.yahoo.yolean.trace.TraceNode root()",
+ "public com.yahoo.yolean.trace.TraceVisitor accept(com.yahoo.yolean.trace.TraceVisitor)",
+ "public java.lang.String toString()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.yolean.trace.TraceVisitor": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "abstract"
+ ],
+ "methods": [
+ "public void <init>()",
+ "public abstract void visit(com.yahoo.yolean.trace.TraceNode)",
+ "public void entering(com.yahoo.yolean.trace.TraceNode)",
+ "public void leaving(com.yahoo.yolean.trace.TraceNode)"
+ ],
+ "fields": []
}
} \ No newline at end of file
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;
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java
new file mode 100644
index 00000000000..53cf3efe363
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java
@@ -0,0 +1,81 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.NoSuchFileException;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author bratseth
+ */
+public class ExceptionsTestCase {
+
+ @Test
+ public void testFindCause() {
+ IllegalArgumentException e1 = new IllegalArgumentException();
+ IllegalStateException e2 = new IllegalStateException(e1);
+ RuntimeException e3 = new RuntimeException(e2);
+
+ assertEquals(Optional.of(e3), Exceptions.findCause(e3, RuntimeException.class));
+ assertEquals(Optional.of(e1), Exceptions.findCause(e3, IllegalArgumentException.class));
+ assertEquals(Optional.empty(), Exceptions.findCause(e3, NumberFormatException.class));
+
+ assertEquals(Optional.of(e2), Exceptions.findCause(e2, RuntimeException.class));
+ }
+
+ @Test
+ public void testToMessageStrings() {
+ assertEquals("Blah",Exceptions.toMessageString(new Exception("Blah")));
+ assertEquals("Blah", Exceptions.toMessageString(new Exception(new Exception("Blah"))));
+ assertEquals("Exception",Exceptions.toMessageString(new Exception()));
+ assertEquals("Foo: Blah",Exceptions.toMessageString(new Exception("Foo",new Exception(new IllegalArgumentException("Blah")))));
+ assertEquals("Foo",Exceptions.toMessageString(new Exception("Foo",new Exception("Foo"))));
+ assertEquals("Foo: Exception",Exceptions.toMessageString(new Exception("Foo",new Exception())));
+ assertEquals("Foo",Exceptions.toMessageString(new Exception(new Exception("Foo"))));
+ }
+
+ @Test
+ public void testUnchecks() {
+ try {
+ Exceptions.uncheck(this::throwNoSuchFileException);
+ } catch (UncheckedIOException e) {
+ assertEquals("filename", e.getCause().getMessage());
+ }
+
+ try {
+ Exceptions.uncheck(this::throwNoSuchFileException, "additional %s", "info");
+ } catch (UncheckedIOException e) {
+ assertEquals("additional info", e.getMessage());
+ }
+
+ try {
+ int i = Exceptions.uncheck(this::throwNoSuchFileExceptionSupplier);
+ } catch (UncheckedIOException e) {
+ assertEquals("filename", e.getCause().getMessage());
+ }
+
+ try {
+ int i = Exceptions.uncheck(this::throwNoSuchFileExceptionSupplier, "additional %s", "info");
+ } catch (UncheckedIOException e) {
+ assertEquals("additional info", e.getMessage());
+ }
+
+ Exceptions.uncheckAndIgnore(this::throwNoSuchFileException, NoSuchFileException.class);
+ assertNull(Exceptions.uncheckAndIgnore(this::throwNoSuchFileExceptionSupplier, NoSuchFileException.class));
+ }
+
+ private void throwNoSuchFileException() throws IOException {
+ throw new NoSuchFileException("filename");
+ }
+
+ private int throwNoSuchFileExceptionSupplier() throws IOException {
+ throw new NoSuchFileException("filename");
+ }
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java
new file mode 100644
index 00000000000..3f2526172a9
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java
@@ -0,0 +1,106 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.concurrent;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author baldersheim
+ * @since 5.2
+ */
+public class CopyOnWriteHashMapTest {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ assertEquals(0, map.size());
+ assertEquals(true, map.isEmpty());
+ assertEquals(false, map.containsKey("fooKey"));
+ assertEquals(false, map.containsValue("fooVal"));
+ assertNull(map.get("fooKey"));
+ assertNull(map.remove("fooKey"));
+ assertEquals(0, map.keySet().size());
+ assertEquals(0, map.entrySet().size());
+ assertEquals(0, map.values().size());
+
+ map.put("fooKey", "fooVal");
+ assertEquals(1, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(true, map.containsKey("fooKey"));
+ assertEquals(true, map.containsValue("fooVal"));
+ assertEquals("fooVal", map.get("fooKey"));
+ assertEquals(1, map.keySet().size());
+ assertEquals(1, map.entrySet().size());
+ assertEquals(1, map.values().size());
+
+ map.put("barKey", "barVal");
+ assertEquals(2, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(true, map.containsKey("fooKey"));
+ assertEquals(true, map.containsKey("barKey"));
+ assertEquals(true, map.containsValue("fooVal"));
+ assertEquals(true, map.containsValue("barVal"));
+ assertEquals("fooVal", map.get("fooKey"));
+ assertEquals("barVal", map.get("barKey"));
+ assertEquals(2, map.keySet().size());
+ assertEquals(2, map.entrySet().size());
+ assertEquals(2, map.values().size());
+
+ assertEquals("fooVal", map.remove("fooKey"));
+ assertEquals(1, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(false, map.containsKey("fooKey"));
+ assertEquals(true, map.containsKey("barKey"));
+ assertEquals(false, map.containsValue("fooVal"));
+ assertEquals(true, map.containsValue("barVal"));
+ assertNull(map.get("fooKey"));
+ assertEquals("barVal", map.get("barKey"));
+ assertEquals(1, map.keySet().size());
+ assertEquals(1, map.entrySet().size());
+ assertEquals(1, map.values().size());
+ }
+
+ @Test
+ public void requireThatEntrySetDoesNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ Map.Entry<String, String> entry = it.next();
+ assertEquals("fooKey", entry.getKey());
+ assertEquals("fooVal", entry.getValue());
+ }
+
+ @Test
+ public void requireThatKeySetDoesNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<String> it = map.keySet().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ assertEquals("fooKey", it.next());
+ }
+
+ @Test
+ public void requireThatValuesDoNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<String> it = map.values().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ assertEquals("fooVal", it.next());
+ }
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/concurrent/MemoizedTest.java b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/MemoizedTest.java
new file mode 100644
index 00000000000..7f2f49c75f2
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/MemoizedTest.java
@@ -0,0 +1,101 @@
+package com.yahoo.yolean.concurrent;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+
+/**
+ * @author jonmv
+ */
+public class MemoizedTest {
+
+ final Phaser phaser = new Phaser();
+ final int threads = 128;
+
+ @Test
+ public void test() throws ExecutionException, InterruptedException {
+ var lazy = new Memoized<>(new OnceSupplier(), OnceCloseable::close);
+ phaser.register(); // test thread
+ phaser.register(); // whoever calls the factory
+
+ Phaser latch = new Phaser(threads + 1);
+ ExecutorService executor = Executors.newFixedThreadPool(threads);
+ List<Future<?>> futures = new ArrayList<>();
+ for (int i = 0; i < 128; i++) {
+ futures.add(executor.submit(() -> {
+ latch.arriveAndAwaitAdvance();
+ lazy.get().rendezvous();
+ while (true) lazy.get();
+ }));
+ }
+
+ // All threads waiting for latch, will race to factory
+ latch.arriveAndAwaitAdvance();
+
+ // One thread waiting in factory, the others are blocked, will go to rendezvous
+ phaser.arriveAndAwaitAdvance();
+
+ // All threads waiting in rendezvous, will repeatedly get until failure
+ phaser.arriveAndAwaitAdvance();
+
+ // Unsynchronized close should be detected by all threads
+ lazy.close();
+
+ // Close should carry through only once
+ lazy.close();
+
+ assertEquals("already closed",
+ assertThrows(IllegalStateException.class, lazy::get).getMessage());
+
+ for (Future<?> future : futures)
+ assertEquals("java.lang.IllegalStateException: already closed",
+ assertThrows(ExecutionException.class, future::get).getMessage());
+
+ executor.shutdown();
+ }
+
+ @Test
+ public void closeBeforeFirstGet() throws Exception {
+ OnceSupplier supplier = new OnceSupplier();
+ Memoized<OnceCloseable, ?> lazy = Memoized.of(supplier);
+ lazy.close();
+ assertEquals("already closed",
+ assertThrows(IllegalStateException.class, lazy::get).getMessage());
+ lazy.close();
+ assertFalse(supplier.initialized.get());
+ }
+
+ class OnceSupplier implements Supplier<OnceCloseable> {
+ final AtomicBoolean initialized = new AtomicBoolean();
+ @Override public OnceCloseable get() {
+ phaser.arriveAndAwaitAdvance();
+ if ( ! initialized.compareAndSet(false, true)) fail("initialized more than once");
+ phaser.bulkRegister(threads - 1); // register all the threads who didn't get the factory
+ return new OnceCloseable();
+ }
+ }
+
+ class OnceCloseable implements AutoCloseable {
+ final AtomicBoolean closed = new AtomicBoolean();
+ @Override public void close() {
+ if ( ! closed.compareAndSet(false, true)) fail("closed more than once");
+ }
+ void rendezvous() {
+ phaser.arriveAndAwaitAdvance();
+ }
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java
new file mode 100644
index 00000000000..c2edaf1fb00
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java
@@ -0,0 +1,146 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.concurrent;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ */
+public class ThreadRobustListTestCase {
+
+ private final static int NUM_THREADS = 64;
+ private final static int NUM_ITEMS_TO_WRITE = 1000000;
+ private final static int NUM_TIMES_TO_READ = 10;
+
+ @Test
+ public void requireThatListIsThreadRobust() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(NUM_THREADS);
+ final ThreadRobustList<Integer> sharedList = new ThreadRobustList<>();
+
+ List<Callable<Boolean>> tasks = new ArrayList<>(NUM_THREADS);
+ tasks.add(new WriterTask(latch, sharedList));
+ for (int i = 1; i < NUM_THREADS; ++i) {
+ tasks.add(new ReaderTask(latch, sharedList));
+ }
+ for (Future<Boolean> result : Executors.newFixedThreadPool(NUM_THREADS).invokeAll(tasks)) {
+ assertTrue(result.get(60, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ assertTrue(lst.isEmpty());
+ assertFalse(lst.iterator().hasNext());
+
+ Object foo = new Object();
+ lst.add(foo);
+ assertFalse(lst.isEmpty());
+ Iterator<Object> it = lst.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertFalse(it.hasNext());
+
+ Object bar = new Object();
+ lst.add(bar);
+ assertFalse(lst.isEmpty());
+ assertNotNull(it = lst.iterator());
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertTrue(it.hasNext());
+ assertSame(bar, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatIteratorNextThrowsNoSuchElementExceptionWhenDone() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ Iterator<Object> it = lst.iterator();
+ assertFalse(it.hasNext());
+ try {
+ it.next();
+ fail();
+ } catch (NoSuchElementException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatIteratorRemoveIsNotSupported() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ Object obj = new Object();
+ lst.add(obj);
+ Iterator<Object> it = lst.iterator();
+ assertTrue(it.hasNext());
+ assertSame(obj, it.next());
+ try {
+ it.remove();
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ private static class WriterTask implements Callable<Boolean> {
+
+ final CountDownLatch latch;
+ final ThreadRobustList<Integer> sharedList;
+
+ WriterTask(CountDownLatch latch, ThreadRobustList<Integer> sharedList) {
+ this.latch = latch;
+ this.sharedList = sharedList;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ latch.countDown();
+ assertTrue(latch.await(60, TimeUnit.SECONDS));
+ for (int i = 0; i < NUM_ITEMS_TO_WRITE; ++i) {
+ sharedList.add(i);
+ }
+ return true;
+ }
+ }
+
+ private static class ReaderTask implements Callable<Boolean> {
+
+ final CountDownLatch latch;
+ final ThreadRobustList<Integer> sharedList;
+
+ ReaderTask(CountDownLatch latch, ThreadRobustList<Integer> sharedList) {
+ this.latch = latch;
+ this.sharedList = sharedList;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ latch.countDown();
+ assertTrue(latch.await(60, TimeUnit.SECONDS));
+ for (int i = 0; i < NUM_TIMES_TO_READ; ++i) {
+ Iterator<Integer> it = sharedList.iterator();
+ for (int j = 0; it.hasNext(); ++j) {
+ assertEquals(j, it.next().intValue());
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/system/CatchSignalsTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/system/CatchSignalsTestCase.java
new file mode 100644
index 00000000000..66a27235088
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/system/CatchSignalsTestCase.java
@@ -0,0 +1,18 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.system;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author arnej27959
+ */
+public class CatchSignalsTestCase {
+
+ @Test
+ public void testThatSetupCompiles() {
+ CatchSignals.setup(new AtomicBoolean(false));
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java
new file mode 100644
index 00000000000..3019b646867
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java
@@ -0,0 +1,231 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.trace;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author bratseth
+ */
+public class TraceNodeTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ TraceNode node = new TraceNode(null, 6);
+ assertNull(node.payload());
+ assertEquals(6, node.timestamp());
+ assertFalse(node.children().iterator().hasNext());
+ assertFalse(node.descendants(Object.class).iterator().hasNext());
+ assertTrue(node.isRoot());
+ assertNull(node.parent());
+ assertSame(node, node.root());
+ }
+
+ @Test
+ public void requireThatToStringIsReadable() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 1))
+ .add(new TraceNode("b", 2)
+ .add(new TraceNode("c", 3)));
+ assertEquals("[ [ a b [ c ] ] ]", trace.toString());
+ }
+
+ @Test
+ public void requireThatPayloadMayBeNull() {
+ TraceNode node = new TraceNode(null, 6);
+ assertNull(node.payload());
+ }
+
+ @Test
+ public void requireThatRootNodesCanBeAdded() {
+ TraceNode parent = new TraceNode(null, 1);
+
+ TraceNode foo = new TraceNode(null, 2);
+ parent.add(foo);
+ assertSame(parent, foo.parent());
+
+ TraceNode bar = new TraceNode(null, 3);
+ parent.add(bar);
+ assertSame(parent, bar.parent());
+
+ Iterator<TraceNode> children = parent.children().iterator();
+ assertTrue(children.hasNext());
+ assertSame(foo, children.next());
+ assertTrue(children.hasNext());
+ assertSame(bar, children.next());
+ assertFalse(children.hasNext());
+
+ Iterator<Object> payloads = parent.descendants(Object.class).iterator();
+ assertFalse(payloads.hasNext());
+ }
+
+ @Test
+ public void requireThatNonRootNodeCanNotBeAdded() {
+ TraceNode foo = new TraceNode(null, 0);
+ TraceNode bar = new TraceNode(null, 0);
+ TraceNode baz = new TraceNode(null, 0);
+ bar.add(baz);
+ try {
+ foo.add(baz);
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ assertSame(bar, baz.parent());
+ assertTrue(bar.children().iterator().hasNext());
+ assertFalse(foo.children().iterator().hasNext());
+ }
+
+ @Test
+ public void requireThatChildrenIsNeverNull() {
+ assertNotNull(new TraceNode(null, 69).children());
+ }
+
+ @Test
+ public void requireThatDescendantsIsNeverNull() {
+ assertNotNull(new TraceNode(null, 69).descendants(Object.class));
+ }
+
+ @Test
+ public void requireThatDescendantsOrderIsDepthFirstPrefix() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode("e", 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ Iterator<String> it = trace.descendants(String.class).iterator();
+ assertTrue(it.hasNext());
+ assertEquals("a", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("b", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("c", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("d", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("e", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("f", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("g", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatDescendantsFilterPayloads() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode(69, 0))
+ .add(new TraceNode("b", 0)
+ .add(new TraceNode("c", 0))
+ .add(new TraceNode(new Object(), 0))))
+ .add(new TraceNode("d", 0)
+ .add(new TraceNode("e", 0)));
+
+ Iterator<String> it = trace.descendants(String.class).iterator();
+ assertTrue(it.hasNext());
+ assertEquals("a", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("b", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("c", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("d", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("e", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatVisitorOrderIsDepthFirstPrefix() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode(3, 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ final List<Object> payloads = new ArrayList<>();
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+ payloads.add(node.payload());
+ }
+ });
+ assertEquals(Arrays.<Object>asList(null, "a", "b", "c", "d", 3, "f", "g"),
+ payloads);
+ }
+
+ @Test
+ public void requireThatVisitorDoesNotEnterOrLeaveNodesThatHaveNoChildren() {
+ TraceNode trace = new TraceNode(null, 0);
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+
+ }
+
+ @Override
+ public void entering(TraceNode node) {
+ fail();
+ }
+
+ @Override
+ public void leaving(TraceNode node) {
+ fail();
+ }
+ });
+ }
+
+ @Test
+ public void requireThatVisitorEntersAndLeavesNodesThatHaveChildren() {
+ TraceNode trace = new TraceNode("", 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode("e", 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ final StringBuilder out = new StringBuilder();
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+ out.append(node.payload());
+ }
+
+ @Override
+ public void entering(TraceNode node) {
+ out.append("[");
+ }
+
+ @Override
+ public void leaving(TraceNode node) {
+ out.append("]");
+ }
+ });
+ assertEquals("[a[bc[de]]f[g]]", out.toString());
+ }
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java
new file mode 100644
index 00000000000..4eaa5b0241e
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java
@@ -0,0 +1,21 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.trace;
+
+import org.junit.Test;
+
+/**
+ * @author Simon Thoresen Hult
+ */
+public class TraceVisitorTestCase {
+
+ @Test
+ public void requireThatTraceVisitorCompilesWithOnlyVisitImplemented() {
+ new TraceNode(null, 0).accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+
+ }
+ });
+ }
+}