X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;ds=sidebyside;f=yang%2Fyang-parser-reactor%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fparser%2Fstmt%2Freactor%2FReactorStmtCtx.java;h=da11198357174a1a0bda4652904feeafdd4babb5;hb=b58489321bc04d8c5ebca56bef8ecce3b4e964e2;hp=1f4ba0355af8413940a69d862db369f4dd40e0ac;hpb=111a53e3a3788854ae96e2d0f2d8b6679ed07c42;p=yangtools.git diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java index 1f4ba0355a..da11198357 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java @@ -7,6 +7,7 @@ */ package org.opendaylight.yangtools.yang.parser.stmt.reactor; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verify; import com.google.common.base.MoreObjects; @@ -19,11 +20,11 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; import org.opendaylight.yangtools.yang.common.YangVersion; import org.opendaylight.yangtools.yang.model.api.SchemaPath; import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement; import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement; -import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace; import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition; import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement; import org.opendaylight.yangtools.yang.model.api.stmt.ConfigEffectiveStatement; @@ -32,14 +33,18 @@ import org.opendaylight.yangtools.yang.model.api.stmt.RefineStatement; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier; import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement; import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType; +import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current; import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException; import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder; import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase; -import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement; +import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase.ExecutionOrder; import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.Registry; +import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils; +import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace; import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace.SupportedFeatures; import org.slf4j.Logger; @@ -53,13 +58,13 @@ import org.slf4j.LoggerFactory; * @param Effective Statement representation */ abstract class ReactorStmtCtx, E extends EffectiveStatement> - extends NamespaceStorageSupport implements Mutable { + extends NamespaceStorageSupport implements Mutable, Current { private static final Logger LOG = LoggerFactory.getLogger(ReactorStmtCtx.class); /** * Substatement refcount tracking. This mechanics deals with retaining substatements for the purposes of * instantiating their lazy copies in InferredStatementContext. It works in concert with {@link #buildEffective()} - * and {@link #buildDeclared()}: declared/effective statement views hold an implicit reference and refcount-based + * and {@link #declared()}: declared/effective statement views hold an implicit reference and refcount-based * sweep is not activated until they are done (or this statement is not {@link #isSupportedToBuildEffective}). * *

@@ -108,6 +113,11 @@ abstract class ReactorStmtCtx, E extends Effec */ private static final int REFCOUNT_SWEPT = Integer.MIN_VALUE; + /** + * Effective instance built from this context. This field as dual types. Under normal circumstances in matches the + * {@link #buildEffective()} instance. If this context is reused, it can be inflated to {@link EffectiveInstances} + * and also act as a common instance reuse site. + */ private @Nullable E effectiveInstance; // Master flag controlling whether this context can yield an effective statement @@ -115,36 +125,45 @@ abstract class ReactorStmtCtx, E extends Effec // of this flag -- eliminating the initial alignment shadow used by below gap-filler fields. private boolean isSupportedToBuildEffective = true; + // EffectiveConfig mapping + private static final int MASK_CONFIG = 0x03; + private static final int HAVE_CONFIG = 0x04; + // Effective instantiation mechanics for StatementContextBase: if this flag is set all substatements are known not + // change when instantiated. This includes context-independent statements as well as any statements which are + // ignored during copy instantiation. + private static final int ALL_INDEPENDENT = 0x08; // Flag bit assignments - private static final int IS_SUPPORTED_BY_FEATURES = 0x01; - private static final int HAVE_SUPPORTED_BY_FEATURES = 0x02; - private static final int IS_IGNORE_IF_FEATURE = 0x04; - private static final int HAVE_IGNORE_IF_FEATURE = 0x08; - // Note: these four are related - private static final int IS_IGNORE_CONFIG = 0x10; - private static final int HAVE_IGNORE_CONFIG = 0x20; - private static final int IS_CONFIGURATION = 0x40; - private static final int HAVE_CONFIGURATION = 0x80; - + private static final int IS_SUPPORTED_BY_FEATURES = 0x10; + private static final int HAVE_SUPPORTED_BY_FEATURES = 0x20; + private static final int IS_IGNORE_IF_FEATURE = 0x40; + private static final int HAVE_IGNORE_IF_FEATURE = 0x80; // Have-and-set flag constants, also used as masks - private static final int SET_SUPPORTED_BY_FEATURES = HAVE_SUPPORTED_BY_FEATURES | IS_SUPPORTED_BY_FEATURES; - private static final int SET_CONFIGURATION = HAVE_CONFIGURATION | IS_CONFIGURATION; - // Note: implies SET_CONFIGURATION, allowing fewer bit operations to be performed - private static final int SET_IGNORE_CONFIG = HAVE_IGNORE_CONFIG | IS_IGNORE_CONFIG | SET_CONFIGURATION; - private static final int SET_IGNORE_IF_FEATURE = HAVE_IGNORE_IF_FEATURE | IS_IGNORE_IF_FEATURE; + private static final int SET_SUPPORTED_BY_FEATURES = HAVE_SUPPORTED_BY_FEATURES | IS_SUPPORTED_BY_FEATURES; + private static final int SET_IGNORE_IF_FEATURE = HAVE_IGNORE_IF_FEATURE | IS_IGNORE_IF_FEATURE; + + private static final EffectiveConfig[] EFFECTIVE_CONFIGS; + + static { + final EffectiveConfig[] values = EffectiveConfig.values(); + final int length = values.length; + verify(length == 4, "Unexpected EffectiveConfig cardinality %s", length); + EFFECTIVE_CONFIGS = values; + } // Flags for use with SubstatementContext. These are hiding in the alignment shadow created by above boolean and // hence improve memory layout. private byte flags; - // Flag for use with AbstractResumedStatement. This is hiding in the alignment shadow created by above boolean + // Flag for use by AbstractResumedStatement, ReplicaStatementContext and InferredStatementContext. Each of them + // uses it to indicated a different condition. This is hiding in the alignment shadow created by + // 'isSupportedToBuildEffective'. // FIXME: move this out once we have JDK15+ - private boolean fullyDefined; + private boolean boolFlag; // SchemaPath cache for use with SubstatementContext and InferredStatementContext. This hurts RootStatementContext // a bit in terms of size -- but those are only a few and SchemaPath is on its way out anyway. - @Deprecated - private volatile SchemaPath schemaPath; + // FIXME: this should become 'QName' + private SchemaPath schemaPath; ReactorStmtCtx() { // Empty on purpose @@ -152,7 +171,13 @@ abstract class ReactorStmtCtx, E extends Effec ReactorStmtCtx(final ReactorStmtCtx original) { isSupportedToBuildEffective = original.isSupportedToBuildEffective; - fullyDefined = original.fullyDefined; + boolFlag = original.boolFlag; + flags = original.flags; + } + + // Used by ReplicaStatementContext only + ReactorStmtCtx(final ReactorStmtCtx original, final Void dummy) { + boolFlag = isSupportedToBuildEffective = original.isSupportedToBuildEffective; flags = original.flags; } @@ -187,11 +212,6 @@ abstract class ReactorStmtCtx, E extends Effec getRoot().setRootVersionImpl(version); } - @Override - public final void addMutableStmtToSeal(final MutableStatement mutableStatement) { - getRoot().addMutableStmtToSealImpl(mutableStatement); - } - @Override public final void addRequiredSource(final SourceIdentifier dependency) { getRoot().addRequiredSourceImpl(dependency); @@ -217,6 +237,65 @@ abstract class ReactorStmtCtx, E extends Effec return definition().getPublicView(); } + @Override + public final Parent effectiveParent() { + return getParentContext(); + } + + @Override + public final QName moduleName() { + final RootStatementContext root = getRoot(); + return QName.create(StmtContextUtils.getRootModuleQName(root), root.getRawArgument()); + } + + @Override + public final EffectiveStatement original() { + return getOriginalCtx().map(StmtContext::buildEffective).orElse(null); + } + + // + // In the next two methods we are looking for an effective statement. If we already have an effective instance, + // defer to it's implementation of the equivalent search. Otherwise we search our substatement contexts. + // + // Note that the search function is split, so as to allow InferredStatementContext to do its own thing first. + // + + @Override + public final > @NonNull Optional findSubstatementArgument( + final @NonNull Class type) { + final E existing = effectiveInstance; + return existing != null ? existing.findFirstEffectiveSubstatementArgument(type) + : findSubstatementArgumentImpl(type); + } + + @Override + public final boolean hasSubstatement(final @NonNull Class> type) { + final E existing = effectiveInstance; + return existing != null ? existing.findFirstEffectiveSubstatement(type).isPresent() : hasSubstatementImpl(type); + } + + // Visible due to InferredStatementContext's override. At this point we do not have an effective instance available. + > @NonNull Optional findSubstatementArgumentImpl( + final @NonNull Class type) { + return allSubstatementsStream() + .filter(ctx -> ctx.isSupportedToBuildEffective() && ctx.producesEffective(type)) + .findAny() + .map(ctx -> (X) ctx.getArgument()); + } + + // Visible due to InferredStatementContext's override. At this point we do not have an effective instance available. + boolean hasSubstatementImpl(final @NonNull Class> type) { + return allSubstatementsStream() + .anyMatch(ctx -> ctx.isSupportedToBuildEffective() && ctx.producesEffective(type)); + } + + @Override + @Deprecated + @SuppressWarnings("unchecked") + public final > StmtContext caerbannog() { + return (StmtContext) this; + } + @Override public final String toString() { return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString(); @@ -240,32 +319,57 @@ abstract class ReactorStmtCtx, E extends Effec // @Override - public final > V namespaceItem(final Class<@NonNull N> type, + public final > V namespaceItem(final Class<@NonNull N> type, final T key) { return getBehaviourRegistry().getNamespaceBehaviour(type).getFrom(this, key); } @Override - public final > Map namespace(final Class<@NonNull N> type) { + public final > Map namespace(final Class<@NonNull N> type) { return getNamespace(type); } @Override - public final > Map localNamespace(final Class<@NonNull N> type) { + public final > + Map localNamespacePortion(final Class<@NonNull N> type) { return getLocalNamespace(type); } @Override - protected final void checkLocalNamespaceAllowed(final Class> type) { + protected final void checkLocalNamespaceAllowed(final Class> type) { definition().checkNamespaceAllowed(type); } @Override - protected > void onNamespaceElementAdded(final Class type, final K key, + protected > void onNamespaceElementAdded(final Class type, final K key, final V value) { // definition().onNamespaceElementAdded(this, type, key, value); } + /** + * Return the effective statement view of a copy operation. This method may return one of: + *

    + *
  • {@code this}, when the effective view did not change
  • + *
  • an InferredStatementContext, when there is a need for inference-equivalent copy
  • + *
  • {@code null}, when the statement failed to materialize
  • + *
+ * + * @param parent Proposed new parent + * @param type Copy operation type + * @param targetModule New target module + * @return {@link ReactorStmtCtx} holding effective view + */ + abstract @Nullable ReactorStmtCtx asEffectiveChildOf(StatementContextBase parent, CopyType type, + QNameModule targetModule); + + @Override + public final ReactorStmtCtx replicaAsChildOf(final Mutable parent) { + checkArgument(parent instanceof StatementContextBase, "Unsupported parent %s", parent); + return replicaAsChildOf((StatementContextBase) parent); + } + + abstract @NonNull ReplicaStatementContext replicaAsChildOf(@NonNull StatementContextBase parent); + // // // Statement build entry points -- both public and package-private. @@ -278,14 +382,15 @@ abstract class ReactorStmtCtx, E extends Effec return (existing = effectiveInstance) != null ? existing : loadEffective(); } - private E loadEffective() { + private @NonNull E loadEffective() { // Creating an effective statement does not strictly require a declared instance -- there are statements like // 'input', which are implicitly defined. // Our implementation design makes an invariant assumption that buildDeclared() has been called by the time // we attempt to create effective statement: - buildDeclared(); + declared(); - final E ret = effectiveInstance = createEffective(); + final E ret = createEffective(); + effectiveInstance = ret; // we have called createEffective(), substatements are no longer guarded by us. Let's see if we can clear up // some residue. if (refcount == REFCOUNT_NONE) { @@ -296,6 +401,36 @@ abstract class ReactorStmtCtx, E extends Effec abstract @NonNull E createEffective(); + /** + * Walk this statement's copy history and return the statement closest to original which has not had its effective + * statements modified. This statement and returned substatement logically have the same set of substatements, hence + * share substatement-derived state. + * + * @return Closest {@link ReactorStmtCtx} with equivalent effective substatements + */ + abstract @NonNull ReactorStmtCtx unmodifiedEffectiveSource(); + + @Override + public final ModelProcessingPhase getCompletedPhase() { + return ModelProcessingPhase.ofExecutionOrder(executionOrder()); + } + + abstract byte executionOrder(); + + /** + * Try to execute current {@link ModelProcessingPhase} of source parsing. If the phase has already been executed, + * this method does nothing. This must not be called with {@link ExecutionOrder#NULL}. + * + * @param phase to be executed (completed) + * @return true if phase was successfully completed + * @throws SourceException when an error occurred in source parsing + */ + final boolean tryToCompletePhase(final byte executionOrder) { + return executionOrder() >= executionOrder || doTryToCompletePhase(executionOrder); + } + + abstract boolean doTryToCompletePhase(byte targetOrder); + // // // Flags-based mechanics. These include public interfaces as well as all the crud we have lurking in our alignment @@ -346,44 +481,46 @@ abstract class ReactorStmtCtx, E extends Effec /** * Config statements are not all that common which means we are performing a recursive search towards the root - * every time {@link #isConfiguration()} is invoked. This is quite expensive because it causes a linear search + * every time {@link #effectiveConfig()} is invoked. This is quite expensive because it causes a linear search * for the (usually non-existent) config statement. * *

* This method maintains a resolution cache, so once we have returned a result, we will keep on returning the same - * result without performing any lookups, solely to support {@link SubstatementContext#isConfiguration()}. + * result without performing any lookups, solely to support {@link #effectiveConfig()}. * *

* Note: use of this method implies that {@link #isIgnoringConfig()} is realized with * {@link #isIgnoringConfig(StatementContextBase)}. */ - final boolean isConfiguration(final StatementContextBase parent) { - final int fl = flags & SET_CONFIGURATION; - if (fl != 0) { - return fl == SET_CONFIGURATION; - } - if (isIgnoringConfig(parent)) { - // Note: SET_CONFIGURATION has been stored in flags - return true; - } + final @NonNull EffectiveConfig effectiveConfig(final ReactorStmtCtx parent) { + return (flags & HAVE_CONFIG) != 0 ? EFFECTIVE_CONFIGS[flags & MASK_CONFIG] : loadEffectiveConfig(parent); + } + + private @NonNull EffectiveConfig loadEffectiveConfig(final ReactorStmtCtx parent) { + final EffectiveConfig parentConfig = parent.effectiveConfig(); - final boolean isConfig; - final Optional optConfig = findSubstatementArgument(ConfigEffectiveStatement.class); - if (optConfig.isPresent()) { - isConfig = optConfig.orElseThrow(); - if (isConfig) { - // Validity check: if parent is config=false this cannot be a config=true - InferenceException.throwIf(!parent.isConfiguration(), sourceReference(), + final EffectiveConfig myConfig; + if (parentConfig != EffectiveConfig.IGNORED && !definition().support().isIgnoringConfig()) { + final Optional optConfig = findSubstatementArgument(ConfigEffectiveStatement.class); + if (optConfig.isPresent()) { + if (optConfig.orElseThrow()) { + // Validity check: if parent is config=false this cannot be a config=true + InferenceException.throwIf(parentConfig == EffectiveConfig.FALSE, this, "Parent node has config=false, this node must not be specifed as config=true"); + myConfig = EffectiveConfig.TRUE; + } else { + myConfig = EffectiveConfig.FALSE; + } + } else { + // If "config" statement is not specified, the default is the same as the parent's "config" value. + myConfig = parentConfig; } } else { - // If "config" statement is not specified, the default is the same as the parent's "config" value. - isConfig = parent.isConfiguration(); + myConfig = EffectiveConfig.IGNORED; } - // Resolved, make sure we cache this return - flags |= isConfig ? SET_CONFIGURATION : HAVE_CONFIGURATION; - return isConfig; + flags = (byte) (flags & ~MASK_CONFIG | HAVE_CONFIG | myConfig.ordinal()); + return myConfig; } protected abstract boolean isIgnoringConfig(); @@ -395,20 +532,10 @@ abstract class ReactorStmtCtx, E extends Effec * *

* Note: use of this method implies that {@link #isConfiguration()} is realized with - * {@link #isConfiguration(StatementContextBase)}. + * {@link #effectiveConfig(StatementContextBase)}. */ final boolean isIgnoringConfig(final StatementContextBase parent) { - final int fl = flags & SET_IGNORE_CONFIG; - if (fl != 0) { - return fl == SET_IGNORE_CONFIG; - } - if (definition().support().isIgnoringConfig() || parent.isIgnoringConfig()) { - flags |= SET_IGNORE_CONFIG; - return true; - } - - flags |= HAVE_IGNORE_CONFIG; - return false; + return EffectiveConfig.IGNORED == effectiveConfig(parent); } protected abstract boolean isIgnoringIfFeatures(); @@ -432,46 +559,84 @@ abstract class ReactorStmtCtx, E extends Effec return false; } - // These two exists only due to memory optimization, should live in AbstractResumedStatement. We are also reusing - // this for ReplicaStatementContext's refcount tracking. + // These two exist only due to memory optimization, should live in AbstractResumedStatement. final boolean fullyDefined() { - return fullyDefined; + return boolFlag; } final void setFullyDefined() { - fullyDefined = true; + boolFlag = true; + } + + // This exists only due to memory optimization, should live in ReplicaStatementContext. In this context the flag + // indicates the need to drop source's reference count when we are being swept. + final boolean haveSourceReference() { + return boolFlag; + } + + // These three exist due to memory optimization, should live in InferredStatementContext. In this context the flag + // indicates whether or not this statement's substatement file was modified, i.e. it is not quite the same as the + // prototype's file. + final boolean isModified() { + return boolFlag; + } + + final void setModified() { + boolFlag = true; + } + + final void setUnmodified() { + boolFlag = false; + } + + // These two exist only for StatementContextBase. Since we are squeezed for size, with only a single bit available + // in flags, we default to 'false' and only set the flag to true when we are absolutely sure -- and all other cases + // err on the side of caution by taking the time to evaluate each substatement separately. + final boolean allSubstatementsContextIndependent() { + return (flags & ALL_INDEPENDENT) != 0; + } + + final void setAllSubstatementsContextIndependent() { + flags |= ALL_INDEPENDENT; } // // - // Common SchemaPath cache. All of this is bound to be removed once YANGTOOLS-1066 is done. + // Various functionality from AbstractTypeStatementSupport. This used to work on top of SchemaPath, now it still + // lives here. Ultimate future is either proper graduation or (more likely) move to AbstractTypeStatementSupport. // // - abstract @NonNull Optional schemaPath(); + @Override + public final QName argumentAsTypeQName() { + return interpretAsQName(getRawArgument()); + } + + @Override + public final QNameModule effectiveNamespace() { + // FIXME: there has to be a better way to do this + return getSchemaPath().getLastComponent().getModule(); + } + + // + // + // Common SchemaPath cache. All of this is bound to be removed once YANGTOOLS-1066 is done. + // + // // Exists only to support {SubstatementContext,InferredStatementContext}.schemaPath() @Deprecated - final @NonNull Optional substatementGetSchemaPath() { - SchemaPath local = schemaPath; - if (local == null) { - synchronized (this) { - local = schemaPath; - if (local == null) { - schemaPath = local = createSchemaPath((StatementContextBase) coerceParentContext()); - } - } + final @Nullable SchemaPath substatementGetSchemaPath() { + if (schemaPath == null) { + schemaPath = createSchemaPath((StatementContextBase) coerceParentContext()); } - - return Optional.ofNullable(local); + return schemaPath; } + // FIXME: 7.0.0: this method's logic needs to be moved to the respective StatementSupport classes @Deprecated private SchemaPath createSchemaPath(final StatementContextBase parent) { - final Optional maybeParentPath = parent.schemaPath(); - verify(maybeParentPath.isPresent(), "Parent %s does not have a SchemaPath", parent); - final SchemaPath parentPath = maybeParentPath.get(); - + final SchemaPath parentPath = parent.getSchemaPath(); if (StmtContextUtils.isUnknownStatement(this)) { return parentPath.createChild(publicDefinition().getStatementName()); } @@ -479,16 +644,13 @@ abstract class ReactorStmtCtx, E extends Effec if (argument instanceof QName) { final QName qname = (QName) argument; if (producesDeclared(UsesStatement.class)) { - return maybeParentPath.orElse(null); + return parentPath; } return parentPath.createChild(qname); } if (argument instanceof String) { - // FIXME: This may yield illegal argument exceptions - final Optional> originalCtx = getOriginalCtx(); - final QName qname = StmtContextUtils.qnameFromArgument(originalCtx.orElse(this), (String) argument); - return parentPath.createChild(qname); + return parentPath.createChild(interpretAsQName((String) argument)); } if (argument instanceof SchemaNodeIdentifier && (producesDeclared(AugmentStatement.class) || producesDeclared(RefineStatement.class) @@ -497,8 +659,13 @@ abstract class ReactorStmtCtx, E extends Effec return parentPath.createChild(((SchemaNodeIdentifier) argument).getNodeIdentifiers()); } - // FIXME: this does not look right - return maybeParentPath.orElse(null); + // FIXME: this does not look right, investigate more? + return parentPath; + } + + private @NonNull QName interpretAsQName(final String argument) { + // FIXME: This may yield illegal argument exceptions + return StmtContextUtils.qnameFromArgument(getOriginalCtx().orElse(this), argument); } // @@ -508,6 +675,16 @@ abstract class ReactorStmtCtx, E extends Effec // // + /** + * Local knowledge of {@link #refcount} values up to statement root. We use this field to prevent recursive lookups + * in {@link #noParentRefs(StatementContextBase)} -- once we discover a parent reference once, we keep that + * knowledge and update it when {@link #sweep()} is invoked. + */ + private byte parentRef = PARENTREF_UNKNOWN; + private static final byte PARENTREF_UNKNOWN = -1; + private static final byte PARENTREF_ABSENT = 0; + private static final byte PARENTREF_PRESENT = 1; + /** * Acquire a reference on this context. As long as there is at least one reference outstanding, * {@link #buildEffective()} will not result in {@link #effectiveSubstatements()} being discarded. @@ -538,6 +715,7 @@ abstract class ReactorStmtCtx, E extends Effec } if (current <= REFCOUNT_NONE) { // Underflow, become defunct + // FIXME: add a global 'warn once' flag LOG.warn("Statement refcount underflow, reference counting disabled for {}", this, new Throwable()); refcount = REFCOUNT_DEFUNCT; return; @@ -545,28 +723,70 @@ abstract class ReactorStmtCtx, E extends Effec refcount = current - 1; LOG.trace("Refcount {} on {}", refcount, this); - if (isSweepable()) { - // We are no longer guarded by effective instance - sweepOnDecrement(); + + if (refcount == REFCOUNT_NONE) { + lastDecRef(); } } /** - * Sweep this statement context as a result of {@link #sweepSubstatements()}, i.e. when parent is also being swept. + * Return {@code true} if this context has no outstanding references. + * + * @return True if this context has no outstanding references. */ - private void sweep() { - if (isSweepable()) { - LOG.trace("Releasing {}", this); - sweepState(); + final boolean noRefs() { + final int local = refcount; + return local < REFCOUNT_NONE || local == REFCOUNT_NONE && noParentRef(); + } + + private void lastDecRef() { + if (noImplictRef()) { + // We are no longer guarded by effective instance + sweepOnDecrement(); + return; + } + + final byte prevRefs = parentRef; + if (prevRefs == PARENTREF_ABSENT) { + // We are the last reference towards root, any children who observed PARENTREF_PRESENT from us need to be + // updated + markNoParentRef(); + } else if (prevRefs == PARENTREF_UNKNOWN) { + // Noone observed our parentRef, just update it + loadParentRefcount(); } } + static final void markNoParentRef(final Collection> substatements) { + for (ReactorStmtCtx stmt : substatements) { + final byte prevRef = stmt.parentRef; + stmt.parentRef = PARENTREF_ABSENT; + if (prevRef == PARENTREF_PRESENT && stmt.refcount == REFCOUNT_NONE) { + // Child thinks it is pinned down, update its perspective + stmt.markNoParentRef(); + } + } + } + + abstract void markNoParentRef(); + static final void sweep(final Collection> substatements) { for (ReactorStmtCtx stmt : substatements) { stmt.sweep(); } } + /** + * Sweep this statement context as a result of {@link #sweepSubstatements()}, i.e. when parent is also being swept. + */ + private void sweep() { + parentRef = PARENTREF_ABSENT; + if (refcount == REFCOUNT_NONE && noImplictRef()) { + LOG.trace("Releasing {}", this); + sweepState(); + } + } + static final int countUnswept(final Collection> substatements) { int result = 0; for (ReactorStmtCtx stmt : substatements) { @@ -592,7 +812,7 @@ abstract class ReactorStmtCtx, E extends Effec // Called when this statement does not have an implicit reference and have reached REFCOUNT_NONE private void sweepOnDecrement() { LOG.trace("Sweeping on decrement {}", this); - if (noParentRefcount()) { + if (noParentRef()) { // No further parent references, sweep our state. sweepState(); } @@ -620,7 +840,7 @@ abstract class ReactorStmtCtx, E extends Effec } // parent is potentially reclaimable - if (noParentRefcount()) { + if (noParentRef()) { LOG.trace("Cleanup {} of parent {}", refcount, this); if (sweepState()) { final ReactorStmtCtx parent = getParentContext(); @@ -635,29 +855,40 @@ abstract class ReactorStmtCtx, E extends Effec return effectiveInstance != null || !isSupportedToBuildEffective(); } - // FIXME: cache the resolution of this - private boolean noParentRefcount() { + private boolean noParentRef() { + return parentRefcount() == PARENTREF_ABSENT; + } + + private byte parentRefcount() { + final byte refs; + return (refs = parentRef) != PARENTREF_UNKNOWN ? refs : loadParentRefcount(); + } + + private byte loadParentRefcount() { + return parentRef = calculateParentRefcount(); + } + + private byte calculateParentRefcount() { final ReactorStmtCtx parent = getParentContext(); - if (parent != null) { - // There are three possibilities: - // - REFCOUNT_NONE, in which case we need to search next parent - // - negative (< REFCOUNT_NONE), meaning parent is in some stage of sweeping, hence it does not have - // a reference to us - // - positive (> REFCOUNT_NONE), meaning parent has an explicit refcount which is holding us down - final int refs = parent.refcount; - return refs == REFCOUNT_NONE ? parent.noParentRefcount() : refs < REFCOUNT_NONE; + if (parent == null) { + return PARENTREF_ABSENT; + } + // There are three possibilities: + // - REFCOUNT_NONE, in which case we need to search next parent + // - negative (< REFCOUNT_NONE), meaning parent is in some stage of sweeping, hence it does not have + // a reference to us + // - positive (> REFCOUNT_NONE), meaning parent has an explicit refcount which is holding us down + final int refs = parent.refcount; + if (refs == REFCOUNT_NONE) { + return parent.parentRefcount(); } - return true; + return refs < REFCOUNT_NONE ? PARENTREF_ABSENT : PARENTREF_PRESENT; } private boolean isAwaitingChildren() { return refcount > REFCOUNT_SWEEPING && refcount < REFCOUNT_NONE; } - private boolean isSweepable() { - return refcount == REFCOUNT_NONE && noImplictRef(); - } - private void sweepOnChildDone() { LOG.trace("Sweeping on child done {}", this); final int current = refcount; @@ -694,6 +925,7 @@ abstract class ReactorStmtCtx, E extends Effec return true; } if (childRefs < 0 || childRefs >= REFCOUNT_DEFUNCT) { + // FIXME: add a global 'warn once' flag LOG.warn("Negative child refcount {} cannot be stored, reference counting disabled for {}", childRefs, this, new Throwable()); refcount = REFCOUNT_DEFUNCT;