Skip to content

Commit

Permalink
Get rid of the wrappers for circular evaluation tracking
Browse files Browse the repository at this point in the history
These wrappers cause significant increase of memory consumption in the
provider-heavy builds. The wrapper-free approach is also marginally
more performant.
  • Loading branch information
mlopatkin committed Jan 17, 2024
1 parent fe0ce92 commit 489a48f
Show file tree
Hide file tree
Showing 17 changed files with 224 additions and 574 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ class Codecs(
filePropertyFactory: FilePropertyFactory,
nestedCodec: FixedValueReplacingProviderCodec
) {
bind(GuardedDataCodec)
bind(ListPropertyCodec(propertyFactory, nestedCodec))
bind(SetPropertyCodec(propertyFactory, nestedCodec))
bind(MapPropertyCodec(propertyFactory, nestedCodec))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ import org.gradle.api.internal.provider.DefaultMapProperty
import org.gradle.api.internal.provider.DefaultProperty
import org.gradle.api.internal.provider.DefaultSetProperty
import org.gradle.api.internal.provider.DefaultValueSourceProviderFactory.ValueSourceProvider
import org.gradle.api.internal.provider.EvaluationContext
import org.gradle.api.internal.provider.GuardedData
import org.gradle.api.internal.provider.PropertyFactory
import org.gradle.api.internal.provider.ProviderInternal
import org.gradle.api.internal.provider.ValueSourceProviderFactory
Expand Down Expand Up @@ -449,30 +447,3 @@ class MapPropertyCodec(
}
}
}


internal
object GuardedDataCodec : Codec<GuardedData<*>> {
private
const val NO_OWNER = -1

override suspend fun WriteContext.encode(value: GuardedData<*>) {
// GuardedData.owner must be a back-reference to the object being serialized up the stack.
// This object should not be "expanded", but only a reference should be stored.
val ownerId = when (val owner = value.owner) {
null -> NO_OWNER
else -> isolate.identities.getId(owner) ?: throw IllegalStateException("Cannot write guarded data before writing its owner")
}
writeSmallInt(ownerId)
write(value.unsafeGet())
}

override suspend fun ReadContext.decode(): GuardedData<*> {
val owner = when (val ownerId = readSmallInt()) {
NO_OWNER -> null
else -> isolate.identities.getInstance(ownerId) as EvaluationContext.EvaluationOwner
}
val value = readNonNull<Any>()
return GuardedData.of(owner, value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,15 @@
import java.util.List;
import java.util.function.Supplier;

public abstract class AbstractCollectionProperty<T, C extends Collection<T>>
extends AbstractProperty<C, AbstractCollectionProperty.CollectionSupplierGuard<T, C>>
public abstract class AbstractCollectionProperty<T, C extends Collection<T>> extends AbstractProperty<C, CollectionSupplier<T, C>>
implements CollectionPropertyInternal<T, C> {

private static final CollectionSupplier<Object, Collection<Object>> NO_VALUE = new NoValueSupplier<>(Value.missing());
private final Class<? extends Collection> collectionType;
private final Class<T> elementType;
private final Supplier<ImmutableCollection.Builder<T>> collectionFactory;
private final ValueCollector<T> valueCollector;
private CollectionSupplierGuard<T, C> defaultValue = emptySupplier();
private CollectionSupplier<T, C> defaultValue = emptySupplier();

AbstractCollectionProperty(PropertyHost host, Class<? extends Collection> collectionType, Class<T> elementType, Supplier<ImmutableCollection.Builder<T>> collectionFactory) {
super(host);
Expand All @@ -55,20 +54,12 @@ public abstract class AbstractCollectionProperty<T, C extends Collection<T>>
init(defaultValue, noValueSupplier());
}

private CollectionSupplierGuard<T, C> emptySupplier() {
return guard(new EmptySupplier());
private CollectionSupplier<T, C> emptySupplier() {
return new EmptySupplier();
}

private CollectionSupplierGuard<T, C> noValueSupplier() {
return guard(Cast.uncheckedCast(NO_VALUE));
}

private void setSupplier(CollectionSupplier<T, C> unguardedSupplier) {
setSupplier(guard(unguardedSupplier));
}

private void setConvention(CollectionSupplier<T, C> unguardedConvention) {
setConvention(guard(unguardedConvention));
private CollectionSupplier<T, C> noValueSupplier() {
return Cast.uncheckedCast(NO_VALUE);
}

/**
Expand Down Expand Up @@ -197,24 +188,24 @@ public HasMultipleValues<T> empty() {
}

@Override
protected Value<? extends C> calculateValueFrom(CollectionSupplierGuard<T, C> value, ValueConsumer consumer) {
protected Value<? extends C> calculateValueFrom(CollectionSupplier<T, C> value, ValueConsumer consumer) {
return value.calculateValue(consumer);
}

@Override
protected CollectionSupplierGuard<T, C> finalValue(CollectionSupplierGuard<T, C> value, ValueConsumer consumer) {
protected CollectionSupplier<T, C> finalValue(CollectionSupplier<T, C> value, ValueConsumer consumer) {
Value<? extends C> result = value.calculateValue(consumer);
if (!result.isMissing()) {
return guard(new FixedSupplier<>(result.getWithoutSideEffect(), Cast.uncheckedCast(result.getSideEffect())));
return new FixedSupplier<>(result.getWithoutSideEffect(), Cast.uncheckedCast(result.getSideEffect()));
} else if (result.getPathToOrigin().isEmpty()) {
return noValueSupplier();
} else {
return guard(new NoValueSupplier<>(result));
return new NoValueSupplier<>(result);
}
}

@Override
protected ExecutionTimeValue<? extends C> calculateOwnExecutionTimeValue(CollectionSupplierGuard<T, C> value) {
protected ExecutionTimeValue<? extends C> calculateOwnExecutionTimeValue(CollectionSupplier<T, C> value) {
return value.calculateExecutionTimeValue();
}

Expand All @@ -236,7 +227,7 @@ public HasMultipleValues<T> convention(Provider<? extends Iterable<? extends T>>

@Override
protected String describeContents() {
return String.format("%s(%s, %s)", collectionType.getSimpleName().toLowerCase(), elementType, getSupplier().toString());
return String.format("%s(%s, %s)", collectionType.getSimpleName().toLowerCase(), elementType, describeValue());
}

static class NoValueSupplier<T, C extends Collection<? extends T>> implements CollectionSupplier<T, C> {
Expand Down Expand Up @@ -507,66 +498,4 @@ public void update(Transformer<? extends @org.jetbrains.annotations.Nullable Pro
set((Iterable<? extends T>) null);
}
}

protected CollectionSupplierGuard<T, C> guard(CollectionSupplier<T, C> supplier) {
return new CollectionSupplierGuard<>(this, supplier);
}

protected static final class CollectionSupplierGuard<T, C extends Collection<T>> implements CollectionSupplier<T, C>, GuardedData<CollectionSupplier<T, C>>, GuardedValueSupplier<CollectionSupplierGuard<T, C>> {
private final EvaluationContext.EvaluationOwner owner;
private final CollectionSupplier<T, C> supplier;

public CollectionSupplierGuard(EvaluationContext.EvaluationOwner owner, CollectionSupplier<T, C> supplier) {
this.owner = owner;
this.supplier = supplier;
}

@Override
public CollectionSupplierGuard<T, C> withOwner(EvaluationContext.EvaluationOwner newOwner) {
return new CollectionSupplierGuard<T, C>(newOwner, supplier);
}

@Override
public EvaluationContext.EvaluationOwner getOwner() {
return owner;
}

@Override
public CollectionSupplier<T, C> unsafeGet() {
return supplier;
}

@Override
public Value<? extends C> calculateValue(ValueConsumer consumer) {
try (EvaluationContext.ScopeContext ignore = EvaluationContext.current().open(owner)) {
return supplier.calculateValue(consumer);
}
}

@Override
public CollectionSupplierGuard<T, C> plus(Collector<T> collector) {
return new CollectionSupplierGuard<>(owner, supplier.plus(collector));
}

@Override
public ExecutionTimeValue<? extends C> calculateExecutionTimeValue() {
try (EvaluationContext.ScopeContext ignore = EvaluationContext.current().open(owner)) {
return supplier.calculateExecutionTimeValue();
}
}

@Override
public ValueProducer getProducer() {
try (EvaluationContext.ScopeContext ignore = EvaluationContext.current().open(owner)) {
return supplier.getProducer();
}
}

@Override
public boolean calculatePresence(ValueConsumer consumer) {
try (EvaluationContext.ScopeContext ignore = EvaluationContext.current().open(owner)) {
return supplier.calculatePresence(consumer);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,146 +227,4 @@ protected String toStringNoReentrance() {
Class<?> type = getType();
return String.format("provider(%s)", type == null ? "?" : type.getName());
}

/**
* Wraps the given data in a {@link DataGuard}.
*
* @param data the data to wrap
* @return the data guard
*/
protected <V> DataGuard<V> guardData(V data) {
return new DataGuard<>(data);
}

/**
* A protocol for value suppliers that have an owner.
*
* @see org.gradle.api.internal.provider.EvaluationContext.EvaluationOwner
*/
public interface GuardedValueSupplier<T extends GuardedValueSupplier<T>> extends ValueSupplier {
/**
* Returns a view of this guarded value supplier but with the given owner.
*
* @param newOwner
* @return a new supplier that produces the same value, but under a different owner
*/
T withOwner(EvaluationContext.EvaluationOwner newOwner);
}

/**
* A wrapper for data used to calculate the value of {@link AbstractMinimalProvider}.
* The data should only be obtained inside the evaluation scope.
*/
protected static final class DataGuard<V> implements GuardedData<V> {
private final V data;

public DataGuard(V data) {
this.data = data;
}

@Override
@Nullable
public ProviderInternal<?> getOwner() {
return null;
}

@Override
public V unsafeGet() {
return data;
}

@Override
public String toString() {
return data.toString();
}
}

/**
* Wraps the given provider in a {@link ProviderGuard} with this {@link AbstractMinimalProvider} as an owner.
* In general, providers should be stored wrapped with guards.
*
* @param provider the provider to wrap
* @return the provider guard
*/
protected <V> ProviderGuard<V> guardProvider(ProviderInternal<V> provider) {
return new ProviderGuard<>(this, provider);
}

/**
* A wrapper for a {@link ProviderInternal} used to calculate the value of {@link AbstractMinimalProvider}, which acts as an owner.
* Calling a method that may cause recursive evaluation adds the owner to the evaluation context.
* <p>
* This class uses try-with-resources directly instead of {@link EvaluationContext#evaluate(EvaluationContext.EvaluationOwner, EvaluationContext.ScopedEvaluation)}
* to avoid extra allocations of lambda instances.
*/
protected static final class ProviderGuard<V> implements GuardedValueSupplier<ProviderGuard<V>>, GuardedData<ProviderInternal<V>> {
private final EvaluationContext.EvaluationOwner owner;
private final ProviderInternal<V> value;

public ProviderGuard(EvaluationContext.EvaluationOwner owner, ProviderInternal<V> value) {
this.owner = owner;
this.value = value;
}

@Override
public EvaluationContext.EvaluationOwner getOwner() {
return owner;
}

@Override
public ValueProducer getProducer() {
try (EvaluationContext.ScopeContext ignore = openScope()) {
return value.getProducer();
}
}

@Override
public boolean calculatePresence(ValueConsumer consumer) {
try (EvaluationContext.ScopeContext ignore = openScope()) {
return value.calculatePresence(consumer);
}
}

public Value<? extends V> calculateValue(ValueConsumer consumer) {
try (EvaluationContext.ScopeContext ignore = openScope()) {
return value.calculateValue(consumer);
}
}

public ExecutionTimeValue<? extends V> calculateExecutionTimeValue() {
try (EvaluationContext.ScopeContext ignore = openScope()) {
return value.calculateExecutionTimeValue();
}
}

@Override
public ProviderInternal<V> unsafeGet() {
return value;
}

@Override
public String toString() {
return value.toString();
}

public ProviderInternal<? extends V> withFinalValue(ValueConsumer consumer) {
try (EvaluationContext.ScopeContext ignore = openScope()) {
return value.withFinalValue(consumer);
}
}

@Nullable
public Class<V> getType() {
return value.getType();
}

private EvaluationContext.ScopeContext openScope() {
return EvaluationContext.current().open(owner);
}

@Override
public ProviderGuard<V> withOwner(EvaluationContext.EvaluationOwner newOwner) {
return new ProviderGuard<>(newOwner, value);
}
}
}

0 comments on commit 489a48f

Please sign in to comment.