Add a refcount mechanism for substatements 36/93936/42
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 26 Nov 2020 12:39:02 +0000 (13:39 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 2 Dec 2020 10:39:30 +0000 (11:39 +0100)
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 <robert.varga@pantheon.tech>
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/AbstractResumedStatement.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/InferredStatementContext.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/NamespaceStorageSupport.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java [new file with mode: 0644]
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java [new file with mode: 0644]
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/RootStatementContext.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SweptNamespace.java [new file with mode: 0644]

index 004676dfbb8e3636235c812019008a9a029c6760..0037d31c4a46e98e65bb683738f7ff51bb5fbb93 100644 (file)
@@ -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<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
         extends StatementContextBase<A, D, E> 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<A, D extends DeclaredStatement<A>, 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.
      *
index 3188c013c582cd385846a46b3dc1fcfe6b537564..b0b42a88876496352be32e03227d283895d5d041 100644 (file)
@@ -52,6 +52,9 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, E extend
         extends StatementContextBase<A, D, E> 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<A, D, E> prototype;
     private final @NonNull StatementContextBase<?, ?, ?> parent;
     private final @NonNull StmtContext<A, D, E> originalCtx;
@@ -60,11 +63,12 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, 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:
      * <ul>
      *   <li>it can be {@code null}, in which case no materialization has taken place</li>
      *   <li>it can be a {@link HashMap}, in which case partial materialization has taken place</li>
      *   <li>it can be a {@link List}, in which case full materialization has taken place</li>
+     *   <li>it can be {@link SWEPT_SUBSTATEMENTS}, in which case materialized state is no longer available</li>
      * </ul>
      */
     private Object substatements;
@@ -92,6 +96,9 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, 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<A, D extends DeclaredStatement<A>, 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<StatementContextBase<?, ?, ?>> ensureEffectiveSubstatements() {
+        accessSubstatements();
         return substatements instanceof List ? castEffective(substatements)
             : initializeSubstatements(castMaterialized(substatements));
     }
@@ -262,7 +270,9 @@ final class InferredStatementContext<A, D extends DeclaredStatement<A>, 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<A, D extends DeclaredStatement<A>, E extend
 
     @Override
     Stream<? extends StmtContext<?, ?, ?>> 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<StatementContextBase<?, ?, ?>> list = castEffective(local);
+            sweep(list);
+            count = countUnswept(list);
+        }
+        return count;
+    }
+
     private List<StatementContextBase<?, ?, ?>> initializeSubstatements(
             final Map<StmtContext<?, ?, ?>, StatementContextBase<?, ?, ?>> materializedSchemaTree) {
         final Collection<? extends StatementContextBase<?, ?, ?>> declared = prototype.mutableDeclaredSubstatements();
         final Collection<? extends Mutable<?, ?, ?>> effective = prototype.mutableEffectiveSubstatements();
-        final List<Mutable<?, ?, ?>> buffer = new ArrayList<>(declared.size() + effective.size());
 
+        final List<Mutable<?, ?, ?>> 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<A, D extends DeclaredStatement<A>, E extend
             buffer.size());
         ret.addAll((Collection) buffer);
         substatements = ret;
+
+        prototype.decRef();
         return ret;
     }
 
index ddf97aa68da1a64183e01668a7f4819584425445..94c74f25d1fc8a5caeb4d55de8e1a3cf1f0eb179 100644 (file)
@@ -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<Class<?>, Map<?,?>> namespaces = ImmutableMap.of();
+    private Map<Class<?>, Map<?, ?>> namespaces = ImmutableMap.of();
 
     /**
      * {@inheritDoc}
@@ -69,7 +74,7 @@ abstract class NamespaceStorageSupport implements NamespaceStorageNode {
 
     @SuppressWarnings("unchecked")
     final <K, V, N extends IdentifierNamespace<K, V>> Map<K, V> getLocalNamespace(final Class<N> type) {
-        return (Map<K, V>) namespaces.get(type);
+        return (Map<K, V>) accessNamespaces().get(type);
     }
 
     final <K, V, T extends K, U extends V, N extends IdentifierNamespace<K, V>> void addToNamespace(
@@ -104,20 +109,63 @@ abstract class NamespaceStorageSupport implements NamespaceStorageNode {
     @SuppressWarnings("unchecked")
     @Override
     public <K, V, N extends IdentifierNamespace<K, V>> V getFromLocalStorage(final Class<N> type, final K key) {
-        final Map<K, V> localNamespace = (Map<K, V>) namespaces.get(type);
+        final Map<K, V> localNamespace = (Map<K, V>) accessNamespaces().get(type);
         return localNamespace == null ? null : localNamespace.get(key);
     }
 
     @Override
     public <K, V, N extends IdentifierNamespace<K, V>> Map<K, V> getAllFromLocalStorage(final Class<N> type) {
         @SuppressWarnings("unchecked")
-        final Map<K, V> localNamespace = (Map<K, V>) namespaces.get(type);
+        final Map<K, V> localNamespace = (Map<K, V>) accessNamespaces().get(type);
         return localNamespace;
     }
 
+    @Override
+    public <K, V, N extends IdentifierNamespace<K, V>> V putToLocalStorage(final Class<N> type, final K key,
+            final V value) {
+        final V ret = ensureLocalNamespace(type).put(key, value);
+        onNamespaceElementAdded(type, key, value);
+        return ret;
+    }
+
+    @Override
+    public <K, V, N extends IdentifierNamespace<K, V>> V putToLocalStorageIfAbsent(final Class<N> 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<Class<?>, 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<Class<?>, Map<?, ?>> accessNamespaces() {
+        return verifyNotNull(namespaces, "Attempted to access swept namespaces of %s", this);
+    }
+
     private <K, V, N extends IdentifierNamespace<K, V>> Map<K, V> ensureLocalNamespace(final Class<N> type) {
         @SuppressWarnings("unchecked")
-        Map<K, V> ret = (Map<K,V>) namespaces.get(type);
+        Map<K, V> ret = (Map<K,V>) 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 <K, V, N extends IdentifierNamespace<K, V>> V putToLocalStorage(final Class<N> type, final K key,
-            final V value) {
-        final V ret = ensureLocalNamespace(type).put(key, value);
-        onNamespaceElementAdded(type, key, value);
-        return ret;
-    }
-
-    @Override
-    public <K, V, N extends IdentifierNamespace<K, V>> V putToLocalStorageIfAbsent(final Class<N> 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 (file)
index 0000000..18fbf9e
--- /dev/null
@@ -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 <A> Argument type
+ * @param <D> Declared Statement representation
+ * @param <E> Effective Statement representation
+ */
+abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
+        extends NamespaceStorageSupport implements Mutable<A, D, E> {
+    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}).
+     *
+     * <p>
+     * Reference count is hierarchical in that parent references also pin down their child statements and do not allow
+     * them to be swept.
+     *
+     * <p>
+     * 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.
+     *
+     * <p>
+     * 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.
+     *
+     * <p>
+     * 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.
+     *
+     * <p>
+     * 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<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
+        for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
+            stmt.sweep();
+        }
+    }
+
+    static final int countUnswept(final Collection<? extends ReactorStmtCtx<?, ?, ?>> 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.
+     *
+     * <p>
+     * {@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 (file)
index 0000000..8038d92
--- /dev/null
@@ -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<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
+        extends StatementContextBase<A, D, E> {
+    private static final Logger LOG = LoggerFactory.getLogger(ReplicaStatementContext.class);
+
+    private final StatementContextBase<?, ?, ?> parent;
+    private final StatementContextBase<A, D, E> source;
+
+    private final boolean haveRef;
+
+    ReplicaStatementContext(final StatementContextBase<?, ?, ?> parent, final StatementContextBase<A, D, E> 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<StmtContext<A, D, E>> getOriginalCtx() {
+        return source.getOriginalCtx();
+    }
+
+    @Override
+    public Collection<? extends StatementContextBase<?, ?, ?>> mutableDeclaredSubstatements() {
+        return source.mutableDeclaredSubstatements();
+    }
+
+    @Override
+    public Collection<? extends Mutable<?, ?, ?>> mutableEffectiveSubstatements() {
+        return source.mutableEffectiveSubstatements();
+    }
+
+    @Override
+    boolean hasEmptySubstatements() {
+        return source.hasEmptySubstatements();
+    }
+
+    @Override
+    Iterable<StatementContextBase<?, ?, ?>> effectiveChildrenToComplete() {
+        return ImmutableList.of();
+    }
+
+    @Override
+    int sweepSubstatements() {
+        if (haveRef) {
+            source.decRef();
+        }
+        return 0;
+    }
+
+    @Override
+    public Optional<StmtContext<A, D, E>> 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<? extends Mutable<?, ?, ?>> statements) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    Stream<? extends StmtContext<?, ?, ?>> streamDeclared() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    Stream<? extends StmtContext<?, ?, ?>> streamEffective() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    StatementContextBase<A, D, E> 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> 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();
+    }
+}
index 91c60419558f66c70b852c62d958e0cd90b1981e..a5333688f27a2f5fcbba6ebdba496f0e7c2042fb 100644 (file)
@@ -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<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
         extends AbstractResumedStatement<A, D, E> implements RootStmtContext.Mutable<A, D, E> {
-
     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<Class<?>, 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<A, D extends DeclaredStatement<A>, E ext
     StatementContextBase<A, D, E> 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);
+    }
 }
index 717074db9e5cf001fb2fe0e5c3ecb11de891ac1f..64e2f766a5f71d0bd14bed10b01da7832a84f9de 100644 (file)
@@ -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 <E> Effective Statement representation
  */
 public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
-        extends NamespaceStorageSupport implements Mutable<A, D, E> {
+        extends ReactorStmtCtx<A, D, E> {
     /**
      * Event listener when an item is added to model namespace.
      */
@@ -556,8 +555,23 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, 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<? extends StmtContext<?, ?, ?>> streamDeclared();
@@ -911,7 +925,11 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     @Override
     public final StatementContextBase<A, D, E> replicaAsChildOf(final Mutable<?, ?, ?> parent) {
         checkArgument(parent instanceof StatementContextBase, "Unsupported parent %s", parent);
-        return this;
+        return replicaAsChildOf((StatementContextBase<?, ?, ?>) parent);
+    }
+
+    final @NonNull StatementContextBase<A, D, E> replicaAsChildOf(final StatementContextBase<?, ?, ?> stmt) {
+        return new ReplicaStatementContext<>(stmt, this);
     }
 
     private static void checkEffectiveModelCompleted(final StmtContext<?, ?, ?> stmt) {
@@ -952,6 +970,16 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, 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 (file)
index 0000000..e3543da
--- /dev/null
@@ -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<Object, Object> {
+    private final Class<?> name;
+
+    SweptNamespace(final Class<?> name) {
+        this.name = requireNonNull(name);
+    }
+
+    @Override
+    public Set<Entry<Object, Object>> entrySet() {
+        throw new VerifyException("Attempted to access swept namespace " + name);
+    }
+}