permits JavaType {
/**
- * Returns the java {@link Type} corresponding to this data type. Or {@link Optional#empty()} if
- * the type is not present, or not available to the current {@link ClassLoader}
+ * Returns the java {@link Type} corresponding to this data type.
+ *
+ * @throws ClassNotFoundException if the reflection type is not present, or not available to the
+ * current {@link ClassLoader}
*/
Type javaReflectType() throws ClassNotFoundException;
TypeMirror javaModelType(ProcessingEnvironment processingEnv);
+
+ /**
+ * Returns the default value for this data type. This is useful in case the developer has marked
+ * the datatype as mandatory but there is no way to detect if the value present or not - forcing
+ * the platform to choose a default.
+ *
+ * The most common example for this is scalars (like primitive types) . This value becomes
+ * especially relevant in distributed environments when the client vajram is in a different
+ * process and the server vajram is in a different process. In the case of non scalar types, if
+ * the client vajram does not pass any value for the mandatory facet, we check for null and throw
+ * an error. But in the case of scalar values, there is no way to check if the client has sent the
+ * value or not. This is because of quirks of serialization protocols. Wire protocols like json,
+ * flatbuffers and protobuf
+ * (version 2 and version 3.15+) allow marking a scalar field as optional. Similarly in
+ * in-memory objects,we can potentially detect missing primitive values by modelling the field as
+ * a boxed primitive and checking its value for null. But this optionality comes at the cost of
+ * suboptimal memory utilization (This is because of the extra bytes used to encode the null case
+ * and the additional pointer hop to access the value. While this might be ignorable in some
+ * cases, the savings might be significant for serialized messages and in-memory java objects in
+ * low-bandwidth or high-performance environments).
+ *
+ *
This is the reason wire protocols like protobuf and flatbuffers provide a way to mark scalar
+ * and list/map types as non-optional and they auto-assign zero-ish/empty values to the scalars
+ * and collections when they are non-optional, since in these optimized cases we cannot
+ * differentiate between the field being set or not set (default values are not transmitted over
+ * the wire). This means non-optional scalars and collections will never be null and hence can
+ * never fail mandatory checks in the case of serialized communtation over the wire between
+ * vajrams in a distributed setup.
+ *
+ *
To make this behaviour choice explicit (whether we differentate between set and unset
+ * values), Vajram developers can opt into this memory saving behaviour by tagging the facet with
+ * `@Mandatory(ifNotSet = DEFAULT_TO_ZERO)`, `@Mandatory(ifNotSet = DEFAULT_TO_EMPTY)`
+ * or @Mandatory(ifNotSet = DEFAULT_TO_FALSE) as the case may be. In case of lists and maps, many
+ * serialization protocols don't provide a way to differentiate between set and unset. So tagging
+ * facets of list and map types with `@Mandatory(ifNotSet = DEFAULT_TO_EMPTY)` may not be allowed
+ * when the vajram is configured to be serialized over such protocols - and the SDK might throw a
+ * compilation error upfront. When the developer does opt-in to this optimization via
+ * DEFAULT_TO_..., the relevant platform default value is always used in case the value is not
+ * set.
+ *
+ *
This method returns that default value.
+ *
+ * @throws ClassNotFoundException if {@link #javaReflectType()} throws {@link
+ * ClassNotFoundException}
+ * @throws IllegalArgumentException if the datatype does not have a platform default value.
+ */
+ T getPlatformDefaultValue() throws ClassNotFoundException, IllegalArgumentException;
+
+ /**
+ * Returns true if the datatype has a platform default value. Generally true for scalars,
+ * collections/arrays, maps and strings.
+ */
+ boolean hasPlatformDefaultValue(ProcessingEnvironment processingEnv);
}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/datatypes/JavaType.java b/krystal-common/src/main/java/com/flipkart/krystal/datatypes/JavaType.java
index c71f87575..851a1b4f5 100644
--- a/krystal-common/src/main/java/com/flipkart/krystal/datatypes/JavaType.java
+++ b/krystal-common/src/main/java/com/flipkart/krystal/datatypes/JavaType.java
@@ -4,32 +4,33 @@
import static com.flipkart.krystal.datatypes.TypeUtils.dataTypeMappings;
import static com.flipkart.krystal.datatypes.TypeUtils.getJavaType;
import static com.flipkart.krystal.datatypes.TypeUtils.typeKindMappings;
+import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.function.Function.identity;
+import com.google.common.base.Defaults;
import com.google.common.collect.ImmutableList;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import lombok.EqualsAndHashCode;
import lombok.Getter;
-import lombok.experimental.Accessors;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
-@Accessors(fluent = true)
-@EqualsAndHashCode(
- of = {"canonicalClassName", "typeParameters"},
- callSuper = false)
+@EqualsAndHashCode(of = {"canonicalClassName", "typeParameters"})
public final class JavaType implements DataType {
/** the fully qualified name of the class, i.e. pck.outer.inner */
@@ -42,27 +43,31 @@ public final class JavaType implements DataType {
private @MonotonicNonNull Type clazz;
- JavaType(Class clazz, List extends DataType>> typeParams) {
+ JavaType(Class> clazz, List extends DataType>> typeParams) {
this(
- Optional.ofNullable(clazz.getPackage()).map(Package::getName).orElse(null),
+ Optional.ofNullable(clazz.getPackage()).map(Package::getName),
clazz.getSimpleName(),
getEnclosingClasses(clazz),
typeParams);
this.clazz = clazz;
}
+ @SuppressWarnings({
+ "UnnecessaryTypeArgument",
+ "optional.parameter"
+ }) // -> To prevent Null checker errors
private JavaType(
- @Nullable String packageName,
+ Optional packageName,
String simpleName,
List enclosingClasses,
List extends DataType>> typeParameters) {
this(
- Stream.of(Stream.of(packageName), enclosingClasses.stream(), Stream.of(simpleName))
+ Stream.of(packageName.stream(), enclosingClasses.stream(), Stream.of(simpleName))
.flatMap(identity())
.filter(Objects::nonNull)
.collect(Collectors.joining(".")),
typeParameters);
- this.packageName = packageName;
+ this.packageName = packageName.orElse(null);
this.simpleName = simpleName;
this.enclosingClasses = ImmutableList.copyOf(enclosingClasses);
}
@@ -77,12 +82,12 @@ public static JavaType create(Class clazz) {
}
@SuppressWarnings("unchecked")
- public static JavaType create(Class clazz, List> typeParams) {
+ public static JavaType create(Class> clazz, List> typeParams) {
String canonicalClassName = clazz.getCanonicalName();
if (canonicalClassName != null && dataTypeMappings.containsKey(canonicalClassName)) {
return (JavaType) dataTypeMappings.get(canonicalClassName).apply(typeParams);
} else {
- return new JavaType<>(clazz, typeParams);
+ return new JavaType(clazz, typeParams);
}
}
@@ -90,6 +95,7 @@ public static JavaType create(Class clazz, List> typeParam
public static JavaType create(
String canonicalClassName, List extends DataType>> typeParameters) {
if (dataTypeMappings.containsKey(canonicalClassName)) {
+ //noinspection unchecked
return (JavaType) dataTypeMappings.get(canonicalClassName).apply(typeParameters);
} else {
return new JavaType<>(canonicalClassName, typeParameters);
@@ -121,13 +127,7 @@ public Type javaReflectType() throws ClassNotFoundException {
}
@SuppressWarnings("unchecked")
Class type =
- (Class)
- Optional.ofNullable(this.getClass().getClassLoader())
- .orElseThrow(
- () ->
- new IllegalStateException(
- "null classloader returned. Cannot proceed further"))
- .loadClass(canonicalClassName());
+ (Class) checkNotNull(this.getClass().getClassLoader()).loadClass(canonicalClassName());
List list = new ArrayList<>();
for (DataType> typeParameter : typeParameters) {
@@ -162,6 +162,56 @@ public TypeMirror javaModelType(ProcessingEnvironment processingEnv) {
.toArray(TypeMirror[]::new));
}
+ @Override
+ @SuppressWarnings("unchecked")
+ public T getPlatformDefaultValue() {
+ try {
+ Type type = javaReflectType();
+ if (type instanceof Class> c) {
+ T defaultPrimitiveValue = (T) Defaults.defaultValue(c);
+ if (defaultPrimitiveValue != null) {
+ return defaultPrimitiveValue;
+ } else {
+ T defaultNonPrimitiveValue = defaultNonPrimitiveValue(c);
+ if (defaultNonPrimitiveValue != null) {
+ return defaultNonPrimitiveValue;
+ }
+ }
+ } else if (type instanceof ArrayType) {
+ return (T) new Object[0];
+ } else if (type instanceof ParameterizedType p) {
+ Type rawType = p.getRawType();
+ if (rawType instanceof Class> c) {
+ T defaultValue = defaultNonPrimitiveValue(c);
+ if (defaultValue != null) {
+ return defaultValue;
+ }
+ }
+ }
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ throw new IllegalArgumentException(
+ "Cannot determine platform default value for type %s".formatted(this));
+ }
+
+ @Override
+ public boolean hasPlatformDefaultValue(ProcessingEnvironment processingEnv) {
+ return TypeUtils.hasPlatformDefaultValue(javaModelType(processingEnv));
+ }
+
+ @SuppressWarnings("unchecked")
+ private @Nullable T defaultNonPrimitiveValue(Class> c) {
+ if (String.class.isAssignableFrom(c)) {
+ return (T) "";
+ } else if (List.class.isAssignableFrom(c)) {
+ return (T) List.of();
+ } else if (Map.class.isAssignableFrom(c)) {
+ return (T) Map.of();
+ }
+ return null;
+ }
+
private static ImmutableList getEnclosingClasses(Class> clazz) {
Deque enclosingClasses = new ArrayDeque<>();
Class> enclosingClass = clazz;
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/datatypes/TypeUtils.java b/krystal-common/src/main/java/com/flipkart/krystal/datatypes/TypeUtils.java
index 92b52568c..7f68ff8d3 100644
--- a/krystal-common/src/main/java/com/flipkart/krystal/datatypes/TypeUtils.java
+++ b/krystal-common/src/main/java/com/flipkart/krystal/datatypes/TypeUtils.java
@@ -1,5 +1,7 @@
package com.flipkart.krystal.datatypes;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.common.collect.ImmutableList;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@@ -9,18 +11,24 @@
import java.util.Set;
import java.util.function.Function;
import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.Nullable;
-final class TypeUtils {
+public final class TypeUtils {
static final Map>, JavaType>>> dataTypeMappings =
new LinkedHashMap<>();
static final Map typeKindMappings = new LinkedHashMap<>();
+ static final Set> NON_PRIMITIVE_CLASSES_WITH_PLATFORM_DEFAULTS =
+ Set.of(String.class, List.class, Map.class);
+
static {
dataTypeMappings.put(
boolean.class.getName(), _unused -> new JavaType<>(boolean.class, ImmutableList.of()));
@@ -96,5 +104,22 @@ static TypeMirror box(TypeMirror typeMirror, ProcessingEnvironment processingEnv
}
}
+ static boolean hasPlatformDefaultValue(TypeMirror t) {
+ if (t instanceof PrimitiveType) {
+ return true;
+ } else if (t instanceof ArrayType) {
+ return true;
+ } else if (t instanceof DeclaredType declaredType
+ && declaredType.asElement() instanceof TypeElement typeElement) {
+ return NON_PRIMITIVE_CLASSES_WITH_PLATFORM_DEFAULTS.stream()
+ .anyMatch(
+ aClass ->
+ typeElement
+ .getQualifiedName()
+ .contentEquals(checkNotNull(aClass.getCanonicalName())));
+ }
+ return false;
+ }
+
private TypeUtils() {}
}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/except/IllegalModificationException.java b/krystal-common/src/main/java/com/flipkart/krystal/except/IllegalModificationException.java
new file mode 100644
index 000000000..5e69aa6b5
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/except/IllegalModificationException.java
@@ -0,0 +1,8 @@
+package com.flipkart.krystal.except;
+
+public class IllegalModificationException extends RuntimeException {
+
+ public IllegalModificationException() {
+ super("Cannot modify a facet after it has been already set");
+ }
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/except/StackTracelessException.java b/krystal-common/src/main/java/com/flipkart/krystal/except/StackTracelessException.java
index 46f526f36..3b01aa62e 100644
--- a/krystal-common/src/main/java/com/flipkart/krystal/except/StackTracelessException.java
+++ b/krystal-common/src/main/java/com/flipkart/krystal/except/StackTracelessException.java
@@ -9,11 +9,13 @@
*/
public class StackTracelessException extends RuntimeException {
+ protected StackTracelessException() {}
+
public StackTracelessException(String message) {
super(message);
}
- public StackTracelessException(String message, Exception cause) {
+ public StackTracelessException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/AbstractFacet.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/AbstractFacet.java
new file mode 100644
index 000000000..51f6b9b50
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/AbstractFacet.java
@@ -0,0 +1,30 @@
+package com.flipkart.krystal.facets;
+
+import com.google.common.collect.ImmutableSet;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+@Getter
+@AllArgsConstructor
+public abstract class AbstractFacet implements Facet {
+ private final int id;
+ private final String name;
+ private final ImmutableSet facetTypes;
+ private final String documentation;
+
+ @Override
+ public final boolean equals(@Nullable Object obj) {
+ return obj instanceof Facet f && f.id() == id;
+ }
+
+ @Override
+ public final int hashCode() {
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return "Facet(" + id() + ", " + name() + ", " + facetTypes() + ')';
+ }
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/BasicFacetInfo.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/BasicFacetInfo.java
new file mode 100644
index 000000000..68116928e
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/BasicFacetInfo.java
@@ -0,0 +1,9 @@
+package com.flipkart.krystal.facets;
+
+public interface BasicFacetInfo {
+ int id();
+
+ String name();
+
+ String documentation();
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/Dependency.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/Dependency.java
new file mode 100644
index 000000000..672fb44d3
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/Dependency.java
@@ -0,0 +1,17 @@
+package com.flipkart.krystal.facets;
+
+import static com.flipkart.krystal.facets.FacetType.DEPENDENCY;
+
+import com.google.common.collect.ImmutableSet;
+
+public interface Dependency extends Facet {
+
+ ImmutableSet DEP_FACET_TYPES = ImmutableSet.of(DEPENDENCY);
+
+ boolean canFanout();
+
+ @Override
+ default ImmutableSet facetTypes() {
+ return DEP_FACET_TYPES;
+ }
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/Facet.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/Facet.java
new file mode 100644
index 000000000..d72348787
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/Facet.java
@@ -0,0 +1,16 @@
+package com.flipkart.krystal.facets;
+
+import com.flipkart.krystal.data.FacetValue;
+import com.flipkart.krystal.data.FacetValues;
+import com.flipkart.krystal.data.FacetValuesBuilder;
+import com.google.common.collect.ImmutableSet;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public interface Facet extends BasicFacetInfo {
+
+ ImmutableSet facetTypes();
+
+ @Nullable FacetValue getFacetValue(FacetValues facetValues);
+
+ void setFacetValue(FacetValuesBuilder facets, FacetValue value);
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/FacetType.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/FacetType.java
new file mode 100644
index 000000000..2959d3d52
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/FacetType.java
@@ -0,0 +1,12 @@
+package com.flipkart.krystal.facets;
+
+public enum FacetType {
+ /** Facet whose value is provided by client */
+ INPUT,
+ /** Facet whose value is provided by the runtime */
+ INJECTION,
+ /** Facet whose value is computed by a dependency */
+ DEPENDENCY,
+ /** Facet whose value is computed by this vajram */
+ OUTPUT
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/FacetUtils.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/FacetUtils.java
new file mode 100644
index 000000000..1a0ea5b48
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/FacetUtils.java
@@ -0,0 +1,11 @@
+package com.flipkart.krystal.facets;
+
+public class FacetUtils {
+
+ public static boolean isGiven(Facet facet) {
+ return facet.facetTypes().contains(FacetType.INPUT)
+ || facet.facetTypes().contains(FacetType.INJECTION);
+ }
+
+ private FacetUtils() {}
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/InputMirror.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/InputMirror.java
new file mode 100644
index 000000000..d0bbedf39
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/InputMirror.java
@@ -0,0 +1,18 @@
+package com.flipkart.krystal.facets;
+
+import com.flipkart.krystal.data.ImmutableRequest;
+import com.flipkart.krystal.data.Request;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A client-side mirror of an input facet. In distributed krystal graphs where client vajram is in a
+ * different process and server vajram is in a different process, models generated for client-side
+ * consumption use this class to represent an input facet instead of the {@link Facet} class which
+ * is used on the server side, so that any server-side runtime dependencies of the Facet class do
+ * not pollute the runtime of the client and hence preserving server-client abstraction.
+ */
+public interface InputMirror extends BasicFacetInfo {
+ @Nullable Object getFromRequest(Request request);
+
+ void setToRequest(ImmutableRequest.Builder request, @Nullable Object value);
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolutionTarget.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolutionTarget.java
new file mode 100644
index 000000000..b9e96df14
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolutionTarget.java
@@ -0,0 +1,12 @@
+package com.flipkart.krystal.facets.resolution;
+
+import com.flipkart.krystal.facets.Dependency;
+import com.flipkart.krystal.facets.InputMirror;
+import com.google.common.collect.ImmutableSet;
+
+public record ResolutionTarget(Dependency dependency, ImmutableSet targetInputs) {
+
+ public ResolutionTarget(Dependency dependency, InputMirror targetInput) {
+ this(dependency, ImmutableSet.of(targetInput));
+ }
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolverCommand.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolverCommand.java
new file mode 100644
index 000000000..e38e5af69
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolverCommand.java
@@ -0,0 +1,41 @@
+package com.flipkart.krystal.facets.resolution;
+
+import com.flipkart.krystal.data.ImmutableRequest;
+import com.google.common.collect.ImmutableList;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+@SuppressWarnings("ClassReferencesSubclass")
+@FunctionalInterface
+public interface ResolverCommand {
+
+ ImmutableList extends ImmutableRequest.Builder> getRequests();
+
+ static SkipDependency skip(String reason) {
+ return skip(reason, null);
+ }
+
+ static SkipDependency skip(String reason, @Nullable Throwable skipCause) {
+ return new SkipDependency(reason, skipCause);
+ }
+
+ static ExecuteDependency executeWithRequests(
+ ImmutableList extends ImmutableRequest.@NonNull Builder> inputs) {
+ return new ExecuteDependency(inputs);
+ }
+
+ record SkipDependency(String reason, @Nullable Throwable cause) implements ResolverCommand {
+ @Override
+ public ImmutableList extends ImmutableRequest.Builder> getRequests() {
+ return ImmutableList.of();
+ }
+ }
+
+ record ExecuteDependency(ImmutableList extends ImmutableRequest.Builder> requests)
+ implements ResolverCommand {
+ @Override
+ public ImmutableList extends ImmutableRequest.Builder> getRequests() {
+ return requests;
+ }
+ }
+}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolverDefinition.java b/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolverDefinition.java
new file mode 100644
index 000000000..d54e90201
--- /dev/null
+++ b/krystal-common/src/main/java/com/flipkart/krystal/facets/resolution/ResolverDefinition.java
@@ -0,0 +1,7 @@
+package com.flipkart.krystal.facets.resolution;
+
+import com.flipkart.krystal.facets.Facet;
+import com.google.common.collect.ImmutableSet;
+
+public record ResolverDefinition(
+ ImmutableSet extends Facet> sources, ResolutionTarget target, boolean canFanout) {}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/pooling/FairMultiLeasePool.java b/krystal-common/src/main/java/com/flipkart/krystal/pooling/FairMultiLeasePool.java
deleted file mode 100644
index 56530416d..000000000
--- a/krystal-common/src/main/java/com/flipkart/krystal/pooling/FairMultiLeasePool.java
+++ /dev/null
@@ -1,224 +0,0 @@
-package com.flipkart.krystal.pooling;
-
-import com.flipkart.krystal.pooling.MultiLeasePoolStatsImpl.MultiLeasePoolStatsImplBuilder;
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import org.checkerframework.checker.nullness.qual.NonNull;
-import org.checkerframework.checker.nullness.qual.Nullable;
-
-public class FairMultiLeasePool implements MultiLeasePool {
-
- private final Supplier<@NonNull T> creator;
- private final MultiLeasePolicy leasePolicy;
-
- private final Consumer destroyer;
-
- private final Deque> queue = new ArrayDeque<>();
- private volatile boolean closed;
-
- private final MultiLeasePoolStatsImplBuilder stats = MultiLeasePoolStatsImpl.builder();
-
- public FairMultiLeasePool(
- Supplier<@NonNull T> creator, MultiLeasePolicy leasePolicy, Consumer destroyer) {
- this.creator = creator;
- this.leasePolicy = leasePolicy;
- this.destroyer = destroyer;
- }
-
- @Override
- public final Lease lease() throws LeaseUnavailableException {
- synchronized (this) {
- if (closed) {
- throw new IllegalStateException("MultiLeasePool already closed");
- }
- int count = queue.size();
- PooledObject head;
- do {
- head = queue.peek();
- boolean leasable = checkLeasabilityAndRotateIfNeeded(head);
- if (leasable) {
- break;
- }
- } while (head != null && --count > 0);
- PooledObject leasable;
- if (head == null || !shouldLeaseOut(head)) {
- leasable = createNewForLeasing();
- } else {
- leasable = head;
- leasable.incrementActiveLeases();
- }
- stats.reportNewLease(leasable.activeLeases());
- return new LeaseImpl<>(leasable, this::giveBack);
- }
- }
-
- private boolean checkLeasabilityAndRotateIfNeeded(@Nullable PooledObject head) {
- if (head == null) {
- return false;
- }
- boolean shouldLeaseOut = shouldLeaseOut(head);
- boolean shouldPushToLast =
- // If this head shouldn't be leased out because it has too many leases active,
- // then we need to move it the last position in the queue so that others get a chance to be
- // leased out
- !shouldLeaseOut
- ||
- // If the object can be leased out, but we have reached max active objects, even then
- // push it to the last of the queue so that others get a chance to be leased out and the
- // leases get distributed in a round-robin fashion
- (leasePolicy instanceof DistributeLeases distributeLeases
- && distributeLeases.maxActiveObjects() == queue.size());
- if (shouldPushToLast) {
- queue.poll();
- if (!shouldDelete(head)) {
- queue.add(head);
- }
- }
- return shouldLeaseOut;
- }
-
- private boolean shouldDelete(@Nullable PooledObject pooledObject) {
- if (pooledObject == null) {
- return false;
- }
- return pooledObject.markForDeletion
- > 100; // This number is a bit random - need to find a better way to calibrate this.
- }
-
- private void addLeasedToQueue(PooledObject pooledObject) {
- if (leasePolicy instanceof PreferObjectReuse) {
- // Since object reuse is preferred, add the pooledObject at the head so that it is used
- // immediately for the next lease.
- queue.addFirst(pooledObject);
- } else if (leasePolicy instanceof DistributeLeases) {
- // Since lease distribution is preferred, add th pooledObject at the tail so that other
- // pooledObjects are used to subsequent leases.
- queue.addLast(pooledObject);
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- private boolean shouldLeaseOut(PooledObject pooledObject) {
- if (shouldDelete(pooledObject)) {
- return false;
- }
- if (leasePolicy instanceof PreferObjectReuse preferObjectReuse) {
- return pooledObject.activeLeases() < preferObjectReuse.maxActiveLeasesPerObject();
- } else if (leasePolicy instanceof DistributeLeases distributeLeases) {
- return pooledObject.activeLeases() < distributeLeases.distributionTriggerThreshold()
- || queue.size() == distributeLeases.maxActiveObjects();
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- private synchronized void giveBack(PooledObject pooledObject) {
- stats.reportLeaseClosed();
- if (shouldDelete(pooledObject) && pooledObject.activeLeases() == 0) {
- destroyer.accept(pooledObject.ref());
- stats.reportObjectDeleted();
- }
- }
-
- private PooledObject createNewForLeasing() throws LeaseUnavailableException {
- int limit = Integer.MAX_VALUE;
- if (leasePolicy instanceof PreferObjectReuse preferObjectReuse
- && preferObjectReuse.maxActiveObjects().isPresent()) {
- limit = preferObjectReuse.maxActiveObjects().get();
- } else if (leasePolicy instanceof DistributeLeases distributeLeases) {
- limit = distributeLeases.maxActiveObjects();
- }
- if (queue.size() >= limit) {
- throw new LeaseUnavailableException(
- "Reached max object limit : " + limit + " in MultiLeasePool");
- }
- //noinspection NumericCastThatLosesPrecision
- PooledObject pooledObject =
- new PooledObject<>(creator.get(), (int) stats.getPeakAvgActiveLeasesPerObject());
- pooledObject.incrementActiveLeases();
- addLeasedToQueue(pooledObject);
- stats.reportNewObject();
- return pooledObject;
- }
-
- @Override
- public void close() {
- this.closed = true;
- PooledObject pooledObject;
- while ((pooledObject = queue.pollLast()) != null) {
- destroyer.accept(pooledObject.ref());
- }
- }
-
- public static final class LeaseImpl implements Lease {
-
- private @Nullable PooledObject pooledObject;
- private final Consumer> giveback;
-
- private LeaseImpl(@NonNull PooledObject pooledObject, Consumer> giveback) {
- this.pooledObject = pooledObject;
- this.giveback = giveback;
- }
-
- @Override
- public T get() {
- if (pooledObject == null) {
- throw new IllegalStateException("Lease already released");
- }
- return pooledObject.ref();
- }
-
- @Override
- public void close() {
- PooledObject pooledObject = this.pooledObject;
- if (pooledObject != null) {
- giveback.accept(pooledObject);
- pooledObject.decrementActiveLeases();
- this.pooledObject = null;
- }
- }
- }
-
- @Override
- public MultiLeasePoolStats stats() {
- return stats.build();
- }
-
- private static final class PooledObject {
-
- private final @NonNull T ref;
- private final int deletionThreshold;
- private int activeLeases = 0;
- private int markForDeletion;
-
- private PooledObject(@NonNull T ref, int deletionThreshold) {
- this.ref = ref;
- this.deletionThreshold = deletionThreshold;
- }
-
- private T ref() {
- return ref;
- }
-
- private int activeLeases() {
- return activeLeases;
- }
-
- private void incrementActiveLeases() {
- activeLeases++;
- if (activeLeases() == deletionThreshold) {
- markForDeletion = 0;
- }
- }
-
- private void decrementActiveLeases() {
- activeLeases--;
- if (activeLeases() < deletionThreshold) {
- markForDeletion++;
- }
- }
- }
-}
diff --git a/krystal-common/src/main/java/com/flipkart/krystal/pooling/PartitionedPool.java b/krystal-common/src/main/java/com/flipkart/krystal/pooling/PartitionedPool.java
index 2fce50050..ce715133a 100644
--- a/krystal-common/src/main/java/com/flipkart/krystal/pooling/PartitionedPool.java
+++ b/krystal-common/src/main/java/com/flipkart/krystal/pooling/PartitionedPool.java
@@ -11,7 +11,6 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
-import lombok.experimental.Accessors;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
@@ -41,7 +40,6 @@
class PartitionedPool implements Iterable> {
private final int hardMaxLeasesPerObject;
-
private final ArrayList> partitionedList = new ArrayList<>();
/**
@@ -163,10 +161,10 @@ private void tryMakeAvailable(int indexToMakeAvailable) {
PooledObject toMove = partitionedList.get(indexToMakeAvailable);
PooledObject other = partitionedList.get(unavailableStartIndex);
+ partitionedList.set(indexToMakeAvailable, other);
+ partitionedList.set(unavailableStartIndex, toMove);
toMove.index(unavailableStartIndex);
other.index(indexToMakeAvailable);
- partitionedList.set(other.index(), other);
- partitionedList.set(toMove.index(), toMove);
this.unavailableStartIndex++;
}
@@ -180,21 +178,23 @@ private void tryMakeUnavailable(int indexToMakeUnavailable) {
indexToMakeUnavailable < partitionedList.size(),
"Index to make unavailable should be < elements.size()");
+ final int unavailableStartIndex = this.unavailableStartIndex;
+
if (indexToMakeUnavailable >= unavailableStartIndex) {
// This index is already unavailable!
return;
}
- final int newUnavailableStartIndex = this.unavailableStartIndex - 1;
if (partitionedList.get(indexToMakeUnavailable).activeLeases < hardMaxLeasesPerObject) {
// Object at index has not reached the hard max leases yet. So it cannot be made unavailable
return;
}
PooledObject