2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.parser.stmt.reactor;
10 import static com.google.common.base.Verify.verify;
12 import com.google.common.base.VerifyException;
13 import java.util.Collection;
14 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
15 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
16 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
21 * Real "core" reactor statement implementation of {@link Mutable}, supporting basic reactor lifecycle.
23 * @param <A> Argument type
24 * @param <D> Declared Statement representation
25 * @param <E> Effective Statement representation
27 abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
28 extends NamespaceStorageSupport implements Mutable<A, D, E> {
29 private static final Logger LOG = LoggerFactory.getLogger(ReactorStmtCtx.class);
32 * Substatement refcount tracking. This mechanics deals with retaining substatements for the purposes of
33 * instantiating their lazy copies in InferredStatementContext. It works in concert with {@link #buildEffective()}
34 * and {@link #buildDeclared()}: declared/effective statement views hold an implicit reference and refcount-based
35 * sweep is not activated until they are done (or this statement is not {@link #isSupportedToBuildEffective}).
38 * Reference count is hierarchical in that parent references also pin down their child statements and do not allow
42 * The counter's positive values are tracking incoming references via {@link #incRef()}/{@link #decRef()} methods.
43 * Once we transition to sweeping, this value becomes negative counting upwards to {@link #REFCOUNT_NONE} based on
44 * {@link #sweepOnChildDone()}. Once we reach that, we transition to {@link #REFCOUNT_SWEPT}.
46 private int refcount = REFCOUNT_NONE;
48 * No outstanding references, this statement is a potential candidate for sweeping, provided it has populated its
49 * declared and effective views and {@link #parentRef} is known to be absent.
51 private static final int REFCOUNT_NONE = 0;
53 * Reference count overflow or some other recoverable logic error. Do not rely on refcounts and do not sweep
57 * Note on value assignment:
58 * This allow our incRef() to naturally progress to being saturated. Others jump there directly.
59 * It also makes it it impossible to observe {@code Interger.MAX_VALUE} children, which we take advantage of for
60 * {@link #REFCOUNT_SWEEPING}.
62 private static final int REFCOUNT_DEFUNCT = Integer.MAX_VALUE;
64 * This statement is being actively swept. This is a transient value set when we are sweeping our children, so that
65 * we prevent re-entering this statement.
68 * Note on value assignment:
69 * The value is lower than any legal child refcount due to {@link #REFCOUNT_DEFUNCT} while still being higher than
70 * {@link #REFCOUNT_SWEPT}.
72 private static final int REFCOUNT_SWEEPING = -Integer.MAX_VALUE;
74 * This statement, along with its entire subtree has been swept and we positively know all our children have reached
75 * this state. We {@link #sweepNamespaces()} upon reaching this state.
78 * Note on value assignment:
79 * This is the lowest value observable, making it easier on checking others on equality.
81 private static final int REFCOUNT_SWEPT = Integer.MIN_VALUE;
84 public abstract StatementContextBase<?, ?, ?> getParentContext();
87 * Acquire a reference on this context. As long as there is at least one reference outstanding,
88 * {@link #buildEffective()} will not result in {@link #effectiveSubstatements()} being discarded.
90 * @throws VerifyException if {@link #effectiveSubstatements()} has already been discarded
93 final int current = refcount;
94 verify(current >= REFCOUNT_NONE, "Attempted to access reference count of %s", this);
95 if (current != REFCOUNT_DEFUNCT) {
96 // Note: can end up becoming REFCOUNT_DEFUNCT on overflow
97 refcount = current + 1;
99 LOG.debug("Disabled refcount increment of {}", this);
104 * Release a reference on this context. This call may result in {@link #effectiveSubstatements()} becoming
107 final void decRef() {
108 final int current = refcount;
109 if (current == REFCOUNT_DEFUNCT) {
111 LOG.debug("Disabled refcount decrement of {}", this);
114 if (current <= REFCOUNT_NONE) {
115 // Underflow, become defunct
116 LOG.warn("Statement refcount underflow, reference counting disabled for {}", this, new Throwable());
117 refcount = REFCOUNT_DEFUNCT;
121 refcount = current - 1;
122 LOG.trace("Refcount {} on {}", refcount, this);
124 // We are no longer guarded by effective instance
129 final void releaseImplicitRef() {
130 if (refcount == REFCOUNT_NONE) {
136 * Sweep this statement context as a result of {@link #sweepSubstatements()}, i.e. when parent is also being swept.
138 private void sweep() {
140 LOG.trace("Releasing {}", this);
145 static final void sweep(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
146 for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
151 static final int countUnswept(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
153 for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
154 if (stmt.refcount > REFCOUNT_NONE || !stmt.noImplictRef()) {
162 * Implementation-specific sweep action. This is expected to perform a recursive {@link #sweep(Collection)} on all
163 * {@link #declaredSubstatements()} and {@link #effectiveSubstatements()} and report the result of the sweep
167 * {@link #effectiveSubstatements()} as well as namespaces may become inoperable as a result of this operation.
169 * @return True if the entire tree has been completely swept, false otherwise.
171 abstract int sweepSubstatements();
173 abstract boolean noImplictRef();
175 // Called when this statement does not have an implicit reference and have reached REFCOUNT_NONE
176 private void sweepOnDecrement() {
177 LOG.trace("Sweeping on decrement {}", this);
178 if (noParentRefcount()) {
179 // No further parent references, sweep our state.
183 // Propagate towards parent if there is one
184 final ReactorStmtCtx<?, ?, ?> parent = getParentContext();
185 if (parent != null) {
186 parent.sweepOnChildDecrement();
190 // Called from child when it has lost its final reference
191 private void sweepOnChildDecrement() {
192 if (isAwaitingChildren()) {
193 // We are a child for which our parent is waiting. Notify it and we are done.
198 // Check parent reference count
199 final int refs = refcount;
200 if (refs > REFCOUNT_NONE || refs <= REFCOUNT_SWEEPING || !noImplictRef()) {
205 // parent is potentially reclaimable
206 if (noParentRefcount()) {
207 LOG.trace("Cleanup {} of parent {}", refcount, this);
209 final ReactorStmtCtx<?, ?, ?> parent = getParentContext();
210 if (parent != null) {
211 parent.sweepOnChildDecrement();
217 // FIXME: cache the resolution of this
218 private boolean noParentRefcount() {
219 final ReactorStmtCtx<?, ?, ?> parent = getParentContext();
220 if (parent != null) {
221 // There are three possibilities:
222 // - REFCOUNT_NONE, in which case we need to search next parent
223 // - negative (< REFCOUNT_NONE), meaning parent is in some stage of sweeping, hence it does not have
225 // - positive (> REFCOUNT_NONE), meaning parent has an explicit refcount which is holding us down
226 final int refs = parent.refcount;
227 return refs == REFCOUNT_NONE ? parent.noParentRefcount() : refs < REFCOUNT_NONE;
232 private boolean isAwaitingChildren() {
233 return refcount > REFCOUNT_SWEEPING && refcount < REFCOUNT_NONE;
236 private boolean isSweepable() {
237 return refcount == REFCOUNT_NONE && noImplictRef();
240 private void sweepOnChildDone() {
241 LOG.trace("Sweeping on child done {}", this);
242 final int current = refcount;
243 if (current >= REFCOUNT_NONE) {
244 // no-op, perhaps we want to handle some cases differently?
245 LOG.trace("Ignoring child sweep of {} for {}", this, current);
248 verify(current != REFCOUNT_SWEPT, "Attempt to sweep a child of swept %s", this);
250 refcount = current + 1;
251 LOG.trace("Child refcount {}", refcount);
252 if (refcount == REFCOUNT_NONE) {
254 final ReactorStmtCtx<?, ?, ?> parent = getParentContext();
255 LOG.trace("Propagating to parent {}", parent);
256 if (parent != null && parent.isAwaitingChildren()) {
257 parent.sweepOnChildDone();
262 private void sweepDone() {
263 LOG.trace("Sweep done for {}", this);
264 refcount = REFCOUNT_SWEPT;
268 private boolean sweepState() {
269 refcount = REFCOUNT_SWEEPING;
270 final int childRefs = sweepSubstatements();
271 if (childRefs == 0) {
275 if (childRefs < 0 || childRefs >= REFCOUNT_DEFUNCT) {
276 LOG.warn("Negative child refcount {} cannot be stored, reference counting disabled for {}", childRefs, this,
278 refcount = REFCOUNT_DEFUNCT;
280 LOG.trace("Still {} outstanding children of {}", childRefs, this);
281 refcount = -childRefs;