From 862bcc04d84ef4d90dc81a8e82e8e812c94da3dd Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Thu, 26 Nov 2020 13:39:02 +0100 Subject: [PATCH] Add a refcount mechanism for substatements We need to defer cleaning substatements until there is no way they could be referenced again. This can happen, for example, when we make a copy through uses: source grouping can be built independently, but needs to retain its effective statements until all its copies are built as well. This expands InferredStatementContext 8 bytes, which we will need to reclaim/justify somehow. At this point we end up with negligble post-build residue, but peak usage remains high (and goes up a bit). We'll build on top of that in further patches. JIRA: YANGTOOLS-1184 Change-Id: I5d26846ee1a5ce705671d9e66d45137a3d1084cf Signed-off-by: Robert Varga --- .../reactor/AbstractResumedStatement.java | 21 ++ .../reactor/InferredStatementContext.java | 37 ++- .../stmt/reactor/NamespaceStorageSupport.java | 76 +++-- .../parser/stmt/reactor/ReactorStmtCtx.java | 287 ++++++++++++++++++ .../stmt/reactor/ReplicaStatementContext.java | 202 ++++++++++++ .../stmt/reactor/RootStatementContext.java | 20 +- .../stmt/reactor/StatementContextBase.java | 38 ++- .../parser/stmt/reactor/SweptNamespace.java | 31 ++ 8 files changed, 679 insertions(+), 33 deletions(-) create mode 100644 yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java create mode 100644 yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java create mode 100644 yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SweptNamespace.java diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/AbstractResumedStatement.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/AbstractResumedStatement.java index 004676dfbb..0037d31c4a 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/AbstractResumedStatement.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/AbstractResumedStatement.java @@ -28,6 +28,8 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; import org.opendaylight.yangtools.yang.parser.spi.source.ImplicitSubstatement; import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference; import org.opendaylight.yangtools.yang.parser.spi.source.StatementWriter.ResumedStatement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Intermediate subclass of StatementContextBase facing the parser stream via implementation of ResumedStatement. This @@ -39,6 +41,8 @@ import org.opendaylight.yangtools.yang.parser.spi.source.StatementWriter.Resumed */ abstract class AbstractResumedStatement, E extends EffectiveStatement> extends StatementContextBase implements ResumedStatement { + private static final Logger LOG = LoggerFactory.getLogger(AbstractResumedStatement.class); + private final @NonNull StatementSourceReference statementDeclSource; private final String rawArgument; @@ -202,6 +206,23 @@ abstract class AbstractResumedStatement, E ext return effective.stream(); } + @Override + final int sweepSubstatements() { + // First we need to sweep all statements, which may trigger sweeps all across the place, for example: + // - 'effective' member sweeping a 'substatements' member + // - 'substatements' member sweeping a 'substatements' member which came before it during iteration + // We then iterate once again, counting what remains unswept + sweep(substatements); + sweep(effective); + final int count = countUnswept(substatements) + countUnswept(effective); + if (count != 0) { + LOG.debug("{} children left to sweep from {}", count, this); + } + substatements = null; + effective = null; + return count; + } + /** * Lookup substatement by its offset in this statement. * diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/InferredStatementContext.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/InferredStatementContext.java index 3188c013c5..b0b42a8887 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/InferredStatementContext.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/InferredStatementContext.java @@ -52,6 +52,9 @@ final class InferredStatementContext, E extend extends StatementContextBase implements OnDemandSchemaTreeStorageNode { private static final Logger LOG = LoggerFactory.getLogger(InferredStatementContext.class); + // Sentinel object for 'substatements' + private static final Object SWEPT_SUBSTATEMENTS = new Object(); + private final @NonNull StatementContextBase prototype; private final @NonNull StatementContextBase parent; private final @NonNull StmtContext originalCtx; @@ -60,11 +63,12 @@ final class InferredStatementContext, E extend private final A argument; /** - * Effective substatements, lazily materialized. This field can have three states: + * Effective substatements, lazily materialized. This field can have four states: *
    *
  • it can be {@code null}, in which case no materialization has taken place
  • *
  • it can be a {@link HashMap}, in which case partial materialization has taken place
  • *
  • it can be a {@link List}, in which case full materialization has taken place
  • + *
  • it can be {@link SWEPT_SUBSTATEMENTS}, in which case materialized state is no longer available
  • *
*/ private Object substatements; @@ -92,6 +96,9 @@ final class InferredStatementContext, E extend this.childCopyType = requireNonNull(childCopyType); this.targetModule = targetModule; this.originalCtx = prototype.getOriginalCtx().orElse(prototype); + + // Mark prototype as blocking statement cleanup + prototype.incRef(); } @Override @@ -252,6 +259,7 @@ final class InferredStatementContext, E extend // Instantiate this statement's effective substatements. Note this method has side-effects in namespaces and overall // BuildGlobalContext, hence it must be called at most once. private List> ensureEffectiveSubstatements() { + accessSubstatements(); return substatements instanceof List ? castEffective(substatements) : initializeSubstatements(castMaterialized(substatements)); } @@ -262,7 +270,9 @@ final class InferredStatementContext, E extend // from prototype (which is already at ModelProcessingPhase.EFFECTIVE_MODEL). if (substatements == null) { return ImmutableList.of(); - } else if (substatements instanceof HashMap) { + } + accessSubstatements(); + if (substatements instanceof HashMap) { return castMaterialized(substatements).values(); } else { return castEffective(substatements); @@ -276,16 +286,33 @@ final class InferredStatementContext, E extend @Override Stream> streamEffective() { - // FIXME: YANGTOOLS-1184: do not force initialization + accessSubstatements(); return ensureEffectiveSubstatements().stream(); } + private void accessSubstatements() { + verify(substatements != SWEPT_SUBSTATEMENTS, "Attempted to access substatements of %s", this); + } + + @Override + int sweepSubstatements() { + final Object local = substatements; + substatements = SWEPT_SUBSTATEMENTS; + int count = 0; + if (local != null) { + final List> list = castEffective(local); + sweep(list); + count = countUnswept(list); + } + return count; + } + private List> initializeSubstatements( final Map, StatementContextBase> materializedSchemaTree) { final Collection> declared = prototype.mutableDeclaredSubstatements(); final Collection> effective = prototype.mutableEffectiveSubstatements(); - final List> buffer = new ArrayList<>(declared.size() + effective.size()); + final List> buffer = new ArrayList<>(declared.size() + effective.size()); for (final Mutable stmtContext : declared) { if (stmtContext.isSupportedByFeatures()) { copySubstatement(stmtContext, buffer, materializedSchemaTree); @@ -299,6 +326,8 @@ final class InferredStatementContext, E extend buffer.size()); ret.addAll((Collection) buffer); substatements = ret; + + prototype.decRef(); return ret; } diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/NamespaceStorageSupport.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/NamespaceStorageSupport.java index ddf97aa68d..94c74f25d1 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/NamespaceStorageSupport.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/NamespaceStorageSupport.java @@ -7,6 +7,8 @@ */ package org.opendaylight.yangtools.yang.parser.stmt.reactor; +import static com.google.common.base.Verify.verifyNotNull; + import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; @@ -21,10 +23,13 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceNotAvailableExce import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; abstract class NamespaceStorageSupport implements NamespaceStorageNode { + private static final Logger LOG = LoggerFactory.getLogger(NamespaceStorageSupport.class); - private Map, Map> namespaces = ImmutableMap.of(); + private Map, Map> namespaces = ImmutableMap.of(); /** * {@inheritDoc} @@ -69,7 +74,7 @@ abstract class NamespaceStorageSupport implements NamespaceStorageNode { @SuppressWarnings("unchecked") final > Map getLocalNamespace(final Class type) { - return (Map) namespaces.get(type); + return (Map) accessNamespaces().get(type); } final > void addToNamespace( @@ -104,20 +109,63 @@ abstract class NamespaceStorageSupport implements NamespaceStorageNode { @SuppressWarnings("unchecked") @Override public > V getFromLocalStorage(final Class type, final K key) { - final Map localNamespace = (Map) namespaces.get(type); + final Map localNamespace = (Map) accessNamespaces().get(type); return localNamespace == null ? null : localNamespace.get(key); } @Override public > Map getAllFromLocalStorage(final Class type) { @SuppressWarnings("unchecked") - final Map localNamespace = (Map) namespaces.get(type); + final Map localNamespace = (Map) accessNamespaces().get(type); return localNamespace; } + @Override + public > V putToLocalStorage(final Class type, final K key, + final V value) { + final V ret = ensureLocalNamespace(type).put(key, value); + onNamespaceElementAdded(type, key, value); + return ret; + } + + @Override + public > V putToLocalStorageIfAbsent(final Class type, final K key, + final V value) { + final V ret = ensureLocalNamespace(type).putIfAbsent(key, value); + if (ret == null) { + onNamespaceElementAdded(type, key, value); + } + return ret; + } + + void sweepNamespaces() { + namespaces = null; + LOG.debug("Swept namespace storages of {}", this); + } + + void sweepNamespaces(final Map, SweptNamespace> toWipe) { + switch (namespaces.size()) { + case 0: + namespaces = ImmutableMap.copyOf(toWipe); + return; + case 1: + namespaces = new HashMap<>(namespaces); + break; + default: + // No-op, we are ready + } + + namespaces.putAll(toWipe); + LOG.debug("Trimmed namespace storages of {} to {}", this, namespaces.keySet()); + } + + private Map, Map> accessNamespaces() { + return verifyNotNull(namespaces, "Attempted to access swept namespaces of %s", this); + } + private > Map ensureLocalNamespace(final Class type) { @SuppressWarnings("unchecked") - Map ret = (Map) namespaces.get(type); + Map ret = (Map) accessNamespaces().get(type); if (ret == null) { checkLocalNamespaceAllowed(type); ret = new HashMap<>(1); @@ -142,22 +190,4 @@ abstract class NamespaceStorageSupport implements NamespaceStorageNode { return ret; } - - @Override - public > V putToLocalStorage(final Class type, final K key, - final V value) { - final V ret = ensureLocalNamespace(type).put(key, value); - onNamespaceElementAdded(type, key, value); - return ret; - } - - @Override - public > V putToLocalStorageIfAbsent(final Class type, final K key, - final V value) { - final V ret = ensureLocalNamespace(type).putIfAbsent(key, value); - if (ret == null) { - onNamespaceElementAdded(type, key, value); - } - return ret; - } } 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 new file mode 100644 index 0000000000..18fbf9e918 --- /dev/null +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.stmt.reactor; + +import static com.google.common.base.Verify.verify; + +import com.google.common.base.VerifyException; +import java.util.Collection; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement; +import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement; +import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Real "core" reactor statement implementation of {@link Mutable}, supporting basic reactor lifecycle. + * + * @param Argument type + * @param Declared Statement representation + * @param Effective Statement representation + */ +abstract class ReactorStmtCtx, E extends EffectiveStatement> + extends NamespaceStorageSupport implements Mutable { + 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 + * sweep is not activated until they are done (or this statement is not {@link #isSupportedToBuildEffective}). + * + *

+ * Reference count is hierarchical in that parent references also pin down their child statements and do not allow + * them to be swept. + * + *

+ * The counter's positive values are tracking incoming references via {@link #incRef()}/{@link #decRef()} methods. + * Once we transition to sweeping, this value becomes negative counting upwards to {@link #REFCOUNT_NONE} based on + * {@link #sweepOnChildDone()}. Once we reach that, we transition to {@link #REFCOUNT_SWEPT}. + */ + private int refcount = REFCOUNT_NONE; + /** + * No outstanding references, this statement is a potential candidate for sweeping, provided it has populated its + * declared and effective views and {@link #parentRef} is known to be absent. + */ + private static final int REFCOUNT_NONE = 0; + /** + * Reference count overflow or some other recoverable logic error. Do not rely on refcounts and do not sweep + * anything. + * + *

+ * Note on value assignment: + * This allow our incRef() to naturally progress to being saturated. Others jump there directly. + * It also makes it it impossible to observe {@code Interger.MAX_VALUE} children, which we take advantage of for + * {@link #REFCOUNT_SWEEPING}. + */ + private static final int REFCOUNT_DEFUNCT = Integer.MAX_VALUE; + /** + * This statement is being actively swept. This is a transient value set when we are sweeping our children, so that + * we prevent re-entering this statement. + * + *

+ * Note on value assignment: + * The value is lower than any legal child refcount due to {@link #REFCOUNT_DEFUNCT} while still being higher than + * {@link #REFCOUNT_SWEPT}. + */ + private static final int REFCOUNT_SWEEPING = -Integer.MAX_VALUE; + /** + * This statement, along with its entire subtree has been swept and we positively know all our children have reached + * this state. We {@link #sweepNamespaces()} upon reaching this state. + * + *

+ * Note on value assignment: + * This is the lowest value observable, making it easier on checking others on equality. + */ + private static final int REFCOUNT_SWEPT = Integer.MIN_VALUE; + + /** + * 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. + * + * @throws VerifyException if {@link #effectiveSubstatements()} has already been discarded + */ + final void incRef() { + final int current = refcount; + verify(current >= REFCOUNT_NONE, "Attempted to access reference count of %s", this); + if (current != REFCOUNT_DEFUNCT) { + // Note: can end up becoming REFCOUNT_DEFUNCT on overflow + refcount = current + 1; + } else { + LOG.debug("Disabled refcount increment of {}", this); + } + } + + /** + * Release a reference on this context. This call may result in {@link #effectiveSubstatements()} becoming + * unavailable. + */ + final void decRef() { + final int current = refcount; + if (current == REFCOUNT_DEFUNCT) { + // no-op + LOG.debug("Disabled refcount decrement of {}", this); + return; + } + if (current <= REFCOUNT_NONE) { + // Underflow, become defunct + LOG.warn("Statement refcount underflow, reference counting disabled for {}", this, new Throwable()); + refcount = REFCOUNT_DEFUNCT; + return; + } + + refcount = current - 1; + LOG.trace("Refcount {} on {}", refcount, this); + if (isSweepable()) { + // We are no longer guarded by effective instance + sweepOnDecrement(); + } + } + + final void releaseImplicitRef() { + if (refcount == REFCOUNT_NONE) { + sweepOnDecrement(); + } + } + + /** + * Sweep this statement context as a result of {@link #sweepSubstatements()}, i.e. when parent is also being swept. + */ + private void sweep() { + if (isSweepable()) { + LOG.trace("Releasing {}", this); + sweepState(); + } + } + + static final void sweep(final Collection> substatements) { + for (ReactorStmtCtx stmt : substatements) { + stmt.sweep(); + } + } + + static final int countUnswept(final Collection> substatements) { + int result = 0; + for (ReactorStmtCtx stmt : substatements) { + if (stmt.refcount > REFCOUNT_NONE || !stmt.noImplictRef()) { + result++; + } + } + return result; + } + + /** + * Implementation-specific sweep action. This is expected to perform a recursive {@link #sweep(Collection)} on all + * {@link #declaredSubstatements()} and {@link #effectiveSubstatements()} and report the result of the sweep + * operation. + * + *

+ * {@link #effectiveSubstatements()} as well as namespaces may become inoperable as a result of this operation. + * + * @return True if the entire tree has been completely swept, false otherwise. + */ + abstract int sweepSubstatements(); + + abstract boolean noImplictRef(); + + abstract @Nullable ReactorStmtCtx parentStmtCtx(); + + // Called when this statement does not have an implicit reference and have reached REFCOUNT_NONE + private void sweepOnDecrement() { + LOG.trace("Sweeping on decrement {}", this); + final ReactorStmtCtx parent = parentStmtCtx(); + if (parent == null) { + // We are the top-level object and have lost a reference. Trigger sweep if possible and we are done. + sweepState(); + return; + } + + parent.sweepOnChildDecrement(); + } + + // Called from child when it has lost its final reference + private void sweepOnChildDecrement() { + if (isAwaitingChildren()) { + // We are a child for which our parent is waiting. Notify it and we are done. + sweepOnChildDone(); + return; + } + + // Check parent reference count + final int refs = refcount; + if (refs > REFCOUNT_NONE || refs <= REFCOUNT_SWEEPING || !noImplictRef()) { + // No-op + return; + } + + // parent is potentially reclaimable + if (noParentRefs()) { + LOG.trace("Cleanup {} of parent {}", refcount, this); + sweepState(); + } + } + + // FIXME: cache the resolution of this + private boolean noParentRefs() { + final ReactorStmtCtx parent = parentStmtCtx(); + if (parent != null) { + final int refs = parent.refcount; + // FIXME: 'noImplicitRef' is too strict here? + if (refs > REFCOUNT_NONE || !parent.noImplictRef()) { + // parent with refcount or protected by views + return false; + } + if (refs < REFCOUNT_NONE) { + // parent is being swept already + return true; + } + // REFCOUNT_NONE and reclaimable, look forward + return parent.noParentRefs(); + } + return true; + } + + 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; + if (current >= REFCOUNT_NONE) { + // no-op, perhaps we want to handle some cases differently? + LOG.trace("Ignoring child sweep of {} for {}", this, current); + return; + } + verify(current != REFCOUNT_SWEPT, "Attempt to sweep a child of swept %s", this); + + refcount = current + 1; + LOG.trace("Child refcount {}", refcount); + if (refcount == REFCOUNT_NONE) { + sweepDone(); + sweepParent(); + } + } + + private void sweepParent() { + final ReactorStmtCtx parent = parentStmtCtx(); + LOG.trace("Propagating to parent {}", parent); + if (parent != null && parent.isAwaitingChildren()) { + parent.sweepOnChildDone(); + } + } + + private void sweepDone() { + LOG.trace("Sweep done for {}", this); + refcount = REFCOUNT_SWEPT; + sweepNamespaces(); + } + + private boolean sweepState() { + refcount = REFCOUNT_SWEEPING; + final int childRefs = sweepSubstatements(); + if (childRefs == 0) { + sweepDone(); + return true; + } + if (childRefs < 0 || childRefs >= REFCOUNT_DEFUNCT) { + LOG.warn("Negative child refcount {} cannot be stored, reference counting disabled for {}", childRefs, this, + new Throwable()); + refcount = REFCOUNT_DEFUNCT; + } else { + LOG.trace("Still {} outstanding children of {}", childRefs, this); + refcount = -childRefs; + } + return false; + } +} diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java new file mode 100644 index 0000000000..8038d92f2e --- /dev/null +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.stmt.reactor; + +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.ImmutableList; +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Stream; +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.StatementDefinition; +import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType; +import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; +import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A replica of a different statement. It does not allow modification, but produces an effective statement from a + * designated source. + */ +final class ReplicaStatementContext, E extends EffectiveStatement> + extends StatementContextBase { + private static final Logger LOG = LoggerFactory.getLogger(ReplicaStatementContext.class); + + private final StatementContextBase parent; + private final StatementContextBase source; + + private final boolean haveRef; + + ReplicaStatementContext(final StatementContextBase parent, final StatementContextBase source) { + super(source); + this.parent = requireNonNull(parent); + this.source = requireNonNull(source); + if (source.isSupportedToBuildEffective()) { + source.incRef(); + haveRef = true; + } else { + setIsSupportedToBuildEffective(false); + haveRef = false; + } + } + + @Override + E createEffective() { + return source.buildEffective(); + } + + @Override + public boolean isConfiguration() { + return source.isConfiguration(); + } + + @Override + public D buildDeclared() { + return source.buildDeclared(); + } + + @Override + public A argument() { + return source.argument(); + } + + @Override + public StatementSourceReference sourceReference() { + return source.sourceReference(); + } + + @Override + public String rawArgument() { + return source.rawArgument(); + } + + @Override + public Optional> getOriginalCtx() { + return source.getOriginalCtx(); + } + + @Override + public Collection> mutableDeclaredSubstatements() { + return source.mutableDeclaredSubstatements(); + } + + @Override + public Collection> mutableEffectiveSubstatements() { + return source.mutableEffectiveSubstatements(); + } + + @Override + boolean hasEmptySubstatements() { + return source.hasEmptySubstatements(); + } + + @Override + Iterable> effectiveChildrenToComplete() { + return ImmutableList.of(); + } + + @Override + int sweepSubstatements() { + if (haveRef) { + source.decRef(); + } + return 0; + } + + @Override + public Optional> getPreviousCopyCtx() { + throw new UnsupportedOperationException(); + } + + @Override + public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef, + final String statementArg) { + throw new UnsupportedOperationException(); + } + + @Override + public void addEffectiveSubstatement(final Mutable substatement) { + throw new UnsupportedOperationException(); + } + + @Override + void addEffectiveSubstatementsImpl(final Collection> statements) { + throw new UnsupportedOperationException(); + } + + @Override + Stream> streamDeclared() { + throw new UnsupportedOperationException(); + } + + @Override + Stream> streamEffective() { + throw new UnsupportedOperationException(); + } + + @Override + StatementContextBase reparent(final StatementContextBase newParent) { + throw new UnsupportedOperationException(); + } + + /* + * KEEP THINGS ORGANIZED! + * + * below methods exist in the same form in InferredStatementContext/SubstatementContext. If any adjustment is made + * here, make sure it is properly updated there. + */ + @Override + @Deprecated + Optional schemaPath() { + return substatementGetSchemaPath(); + } + + @Override + public StatementContextBase getParentContext() { + return parent; + } + + @Override + public StorageNodeType getStorageNodeType() { + return StorageNodeType.STATEMENT_LOCAL; + } + + @Override + public StatementContextBase getParentNamespaceStorage() { + return parent; + } + + @Override + public RootStatementContext getRoot() { + return parent.getRoot(); + } + + @Override + protected boolean isIgnoringIfFeatures() { + return isIgnoringIfFeatures(parent); + } + + @Override + protected boolean isIgnoringConfig() { + return isIgnoringConfig(parent); + } + + @Override + protected boolean isParentSupportedByFeatures() { + return parent.isSupportedByFeatures(); + } +} diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java index 91c6041955..a5333688f2 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java @@ -13,6 +13,7 @@ import static com.google.common.base.Verify.verify; import static java.util.Objects.requireNonNull; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Collection; @@ -29,6 +30,7 @@ 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.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace; 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.NamespaceBehaviour.NamespaceStorageNode; @@ -37,6 +39,8 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.Storag import org.opendaylight.yangtools.yang.parser.spi.meta.RootStmtContext; import org.opendaylight.yangtools.yang.parser.spi.source.IncludedModuleContext; import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Root statement class for a YANG source. All statements defined in that YANG source are mapped underneath an instance @@ -44,9 +48,17 @@ import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReferenc */ public final class RootStatementContext, E extends EffectiveStatement> extends AbstractResumedStatement implements RootStmtContext.Mutable { - public static final YangVersion DEFAULT_VERSION = YangVersion.VERSION_1; + private static final Logger LOG = LoggerFactory.getLogger(RootStatementContext.class); + // These namespaces are well-known and not needed after the root is cleaned up + private static final Map, SweptNamespace> SWEPT_NAMESPACES = ImmutableMap.of( + // FIXME: somehow these two end up being referenced post-sweep + // this probably means we have a cross-module reference we do not account for +// GroupingNamespace.class, new SweptNamespace(GroupingNamespace.class), +// TypeNamespace.class, new SweptNamespace(TypeNamespace.class), + SchemaTreeNamespace.class, new SweptNamespace(SchemaTreeNamespace.class)); + private final @NonNull SourceSpecificContext sourceContext; private final A argument; @@ -257,4 +269,10 @@ public final class RootStatementContext, E ext StatementContextBase reparent(final StatementContextBase newParent) { throw new UnsupportedOperationException("Root statement cannot be reparented to" + newParent); } + + @Override + void sweepNamespaces() { + LOG.trace("Sweeping root {}", this); + sweepNamespaces(SWEPT_NAMESPACES); + } } diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java index 717074db9e..64e2f766a5 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java @@ -65,7 +65,6 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace; import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport; import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport.CopyPolicy; 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.ImplicitSubstatement; import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; @@ -84,7 +83,7 @@ import org.slf4j.LoggerFactory; * @param Effective Statement representation */ public abstract class StatementContextBase, E extends EffectiveStatement> - extends NamespaceStorageSupport implements Mutable { + extends ReactorStmtCtx { /** * Event listener when an item is added to model namespace. */ @@ -556,8 +555,23 @@ public abstract class StatementContextBase, E } private E loadEffective() { - return effectiveInstance = definition.getFactory().createEffective(new BaseCurrentEffectiveStmtCtx<>(this), - streamDeclared(), streamEffective()); + // 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(); + + final E ret = effectiveInstance = createEffective(); + // we have called createEffective(), substatements are no longer guarded by us. Let's see if we can clear up + // some residue. + releaseImplicitRef(); + return ret; + } + + // Exposed for ReplicaStatementContext + E createEffective() { + return definition.getFactory().createEffective(new BaseCurrentEffectiveStmtCtx<>(this), streamDeclared(), + streamEffective()); } abstract Stream> streamDeclared(); @@ -911,7 +925,11 @@ public abstract class StatementContextBase, E @Override public final StatementContextBase replicaAsChildOf(final Mutable parent) { checkArgument(parent instanceof StatementContextBase, "Unsupported parent %s", parent); - return this; + return replicaAsChildOf((StatementContextBase) parent); + } + + final @NonNull StatementContextBase replicaAsChildOf(final StatementContextBase stmt) { + return new ReplicaStatementContext<>(stmt, this); } private static void checkEffectiveModelCompleted(final StmtContext stmt) { @@ -952,6 +970,16 @@ public abstract class StatementContextBase, E */ abstract boolean hasEmptySubstatements(); + @Override + final boolean noImplictRef() { + return effectiveInstance != null || !isSupportedToBuildEffective(); + } + + @Override + final ReactorStmtCtx parentStmtCtx() { + return getParentContext(); + } + /** * 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 diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SweptNamespace.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SweptNamespace.java new file mode 100644 index 0000000000..e3543da274 --- /dev/null +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SweptNamespace.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.parser.stmt.reactor; + +import static java.util.Objects.requireNonNull; + +import com.google.common.base.VerifyException; +import java.util.AbstractMap; +import java.util.Set; + +/** + * Placeholder namespace map which does not allow access and acts as a sentinel for namespaces which have been + * explicitly removed from {@link NamespaceStorageSupport}. + */ +final class SweptNamespace extends AbstractMap { + private final Class name; + + SweptNamespace(final Class name) { + this.name = requireNonNull(name); + } + + @Override + public Set> entrySet() { + throw new VerifyException("Attempted to access swept namespace " + name); + } +} -- 2.36.6