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.eclipse.jdt.annotation.Nullable;
15 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
16 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
17 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
22 * Real "core" reactor statement implementation of {@link Mutable}, supporting basic reactor lifecycle.
24 * @param <A> Argument type
25 * @param <D> Declared Statement representation
26 * @param <E> Effective Statement representation
28 abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
29 extends NamespaceStorageSupport implements Mutable<A, D, E> {
30 private static final Logger LOG = LoggerFactory.getLogger(ReactorStmtCtx.class);
33 * Substatement refcount tracking. This mechanics deals with retaining substatements for the purposes of
34 * instantiating their lazy copies in InferredStatementContext. It works in concert with {@link #buildEffective()}
35 * and {@link #buildDeclared()}: declared/effective statement views hold an implicit reference and refcount-based
36 * sweep is not activated until they are done (or this statement is not {@link #isSupportedToBuildEffective}).
39 * Reference count is hierarchical in that parent references also pin down their child statements and do not allow
43 * The counter's positive values are tracking incoming references via {@link #incRef()}/{@link #decRef()} methods.
44 * Once we transition to sweeping, this value becomes negative counting upwards to {@link #REFCOUNT_NONE} based on
45 * {@link #sweepOnChildDone()}. Once we reach that, we transition to {@link #REFCOUNT_SWEPT}.
47 private int refcount = REFCOUNT_NONE;
49 * No outstanding references, this statement is a potential candidate for sweeping, provided it has populated its
50 * declared and effective views and {@link #parentRef} is known to be absent.
52 private static final int REFCOUNT_NONE = 0;
54 * Reference count overflow or some other recoverable logic error. Do not rely on refcounts and do not sweep
58 * Note on value assignment:
59 * This allow our incRef() to naturally progress to being saturated. Others jump there directly.
60 * It also makes it it impossible to observe {@code Interger.MAX_VALUE} children, which we take advantage of for
61 * {@link #REFCOUNT_SWEEPING}.
63 private static final int REFCOUNT_DEFUNCT = Integer.MAX_VALUE;
65 * This statement is being actively swept. This is a transient value set when we are sweeping our children, so that
66 * we prevent re-entering this statement.
69 * Note on value assignment:
70 * The value is lower than any legal child refcount due to {@link #REFCOUNT_DEFUNCT} while still being higher than
71 * {@link #REFCOUNT_SWEPT}.
73 private static final int REFCOUNT_SWEEPING = -Integer.MAX_VALUE;
75 * This statement, along with its entire subtree has been swept and we positively know all our children have reached
76 * this state. We {@link #sweepNamespaces()} upon reaching this state.
79 * Note on value assignment:
80 * This is the lowest value observable, making it easier on checking others on equality.
82 private static final int REFCOUNT_SWEPT = Integer.MIN_VALUE;
85 * Acquire a reference on this context. As long as there is at least one reference outstanding,
86 * {@link #buildEffective()} will not result in {@link #effectiveSubstatements()} being discarded.
88 * @throws VerifyException if {@link #effectiveSubstatements()} has already been discarded
91 final int current = refcount;
92 verify(current >= REFCOUNT_NONE, "Attempted to access reference count of %s", this);
93 if (current != REFCOUNT_DEFUNCT) {
94 // Note: can end up becoming REFCOUNT_DEFUNCT on overflow
95 refcount = current + 1;
97 LOG.debug("Disabled refcount increment of {}", this);
102 * Release a reference on this context. This call may result in {@link #effectiveSubstatements()} becoming
105 final void decRef() {
106 final int current = refcount;
107 if (current == REFCOUNT_DEFUNCT) {
109 LOG.debug("Disabled refcount decrement of {}", this);
112 if (current <= REFCOUNT_NONE) {
113 // Underflow, become defunct
114 LOG.warn("Statement refcount underflow, reference counting disabled for {}", this, new Throwable());
115 refcount = REFCOUNT_DEFUNCT;
119 refcount = current - 1;
120 LOG.trace("Refcount {} on {}", refcount, this);
122 // We are no longer guarded by effective instance
127 final void releaseImplicitRef() {
128 if (refcount == REFCOUNT_NONE) {
134 * Sweep this statement context as a result of {@link #sweepSubstatements()}, i.e. when parent is also being swept.
136 private void sweep() {
138 LOG.trace("Releasing {}", this);
143 static final void sweep(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
144 for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
149 static final int countUnswept(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
151 for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
152 if (stmt.refcount > REFCOUNT_NONE || !stmt.noImplictRef()) {
160 * Implementation-specific sweep action. This is expected to perform a recursive {@link #sweep(Collection)} on all
161 * {@link #declaredSubstatements()} and {@link #effectiveSubstatements()} and report the result of the sweep
165 * {@link #effectiveSubstatements()} as well as namespaces may become inoperable as a result of this operation.
167 * @return True if the entire tree has been completely swept, false otherwise.
169 abstract int sweepSubstatements();
171 abstract boolean noImplictRef();
173 abstract @Nullable ReactorStmtCtx<?, ?, ?> parentStmtCtx();
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 final ReactorStmtCtx<?, ?, ?> parent = parentStmtCtx();
179 if (parent == null) {
180 // We are the top-level object and have lost a reference. Trigger sweep if possible and we are done.
183 parent.sweepOnChildDecrement();
187 // Called from child when it has lost its final reference
188 private void sweepOnChildDecrement() {
189 if (isAwaitingChildren()) {
190 // We are a child for which our parent is waiting. Notify it and we are done.
195 // Check parent reference count
196 final int refs = refcount;
197 if (refs > REFCOUNT_NONE || refs <= REFCOUNT_SWEEPING || !noImplictRef()) {
202 // parent is potentially reclaimable
203 if (noParentRefcount()) {
204 LOG.trace("Cleanup {} of parent {}", refcount, this);
206 final ReactorStmtCtx<?, ?, ?> parent = parentStmtCtx();
207 if (parent != null) {
208 parent.sweepOnChildDecrement();
214 // FIXME: cache the resolution of this
215 private boolean noParentRefcount() {
216 final ReactorStmtCtx<?, ?, ?> parent = parentStmtCtx();
217 if (parent != null) {
218 // There are three possibilities:
219 // - REFCOUNT_NONE, in which case we need to search next parent
220 // - negative (< REFCOUNT_NONE), meaning parent is in some stage of sweeping, hence it does not have
222 // - positive (> REFCOUNT_NONE), meaning parent has an explicit refcount which is holding us down
223 final int refs = parent.refcount;
224 return refs == REFCOUNT_NONE ? parent.noParentRefcount() : refs < REFCOUNT_NONE;
229 private boolean isAwaitingChildren() {
230 return refcount > REFCOUNT_SWEEPING && refcount < REFCOUNT_NONE;
233 private boolean isSweepable() {
234 return refcount == REFCOUNT_NONE && noImplictRef();
237 private void sweepOnChildDone() {
238 LOG.trace("Sweeping on child done {}", this);
239 final int current = refcount;
240 if (current >= REFCOUNT_NONE) {
241 // no-op, perhaps we want to handle some cases differently?
242 LOG.trace("Ignoring child sweep of {} for {}", this, current);
245 verify(current != REFCOUNT_SWEPT, "Attempt to sweep a child of swept %s", this);
247 refcount = current + 1;
248 LOG.trace("Child refcount {}", refcount);
249 if (refcount == REFCOUNT_NONE) {
251 final ReactorStmtCtx<?, ?, ?> parent = parentStmtCtx();
252 LOG.trace("Propagating to parent {}", parent);
253 if (parent != null && parent.isAwaitingChildren()) {
254 parent.sweepOnChildDone();
259 private void sweepDone() {
260 LOG.trace("Sweep done for {}", this);
261 refcount = REFCOUNT_SWEPT;
265 private boolean sweepState() {
266 refcount = REFCOUNT_SWEEPING;
267 final int childRefs = sweepSubstatements();
268 if (childRefs == 0) {
272 if (childRefs < 0 || childRefs >= REFCOUNT_DEFUNCT) {
273 LOG.warn("Negative child refcount {} cannot be stored, reference counting disabled for {}", childRefs, this,
275 refcount = REFCOUNT_DEFUNCT;
277 LOG.trace("Still {} outstanding children of {}", childRefs, this);
278 refcount = -childRefs;