7cde7fdf741619be5926ff0b64fee0225b635069
[yangtools.git] / yang / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / ReactorStmtCtx.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.parser.stmt.reactor;
9
10 import static com.google.common.base.Verify.verify;
11
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;
19
20 /**
21  * Real "core" reactor statement implementation of {@link Mutable}, supporting basic reactor lifecycle.
22  *
23  * @param <A> Argument type
24  * @param <D> Declared Statement representation
25  * @param <E> Effective Statement representation
26  */
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);
30
31     /**
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}).
36      *
37      * <p>
38      * Reference count is hierarchical in that parent references also pin down their child statements and do not allow
39      * them to be swept.
40      *
41      * <p>
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}.
45      */
46     private int refcount = REFCOUNT_NONE;
47     /**
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.
50      */
51     private static final int REFCOUNT_NONE = 0;
52     /**
53      * Reference count overflow or some other recoverable logic error. Do not rely on refcounts and do not sweep
54      * anything.
55      *
56      * <p>
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}.
61      */
62     private static final int REFCOUNT_DEFUNCT = Integer.MAX_VALUE;
63     /**
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.
66      *
67      * <p>
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}.
71      */
72     private static final int REFCOUNT_SWEEPING = -Integer.MAX_VALUE;
73     /**
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.
76      *
77      * <p>
78      * Note on value assignment:
79      * This is the lowest value observable, making it easier on checking others on equality.
80      */
81     private static final int REFCOUNT_SWEPT = Integer.MIN_VALUE;
82
83     @Override
84     public abstract StatementContextBase<?, ?, ?> getParentContext();
85
86     /**
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.
89      *
90      * @throws VerifyException if {@link #effectiveSubstatements()} has already been discarded
91      */
92     final void incRef() {
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;
98         } else {
99             LOG.debug("Disabled refcount increment of {}", this);
100         }
101     }
102
103     /**
104      * Release a reference on this context. This call may result in {@link #effectiveSubstatements()} becoming
105      * unavailable.
106      */
107     final void decRef() {
108         final int current = refcount;
109         if (current == REFCOUNT_DEFUNCT) {
110             // no-op
111             LOG.debug("Disabled refcount decrement of {}", this);
112             return;
113         }
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;
118             return;
119         }
120
121         refcount = current - 1;
122         LOG.trace("Refcount {} on {}", refcount, this);
123         if (isSweepable()) {
124             // We are no longer guarded by effective instance
125             sweepOnDecrement();
126         }
127     }
128
129     final void releaseImplicitRef() {
130         if (refcount == REFCOUNT_NONE) {
131             sweepOnDecrement();
132         }
133     }
134
135     /**
136      * Sweep this statement context as a result of {@link #sweepSubstatements()}, i.e. when parent is also being swept.
137      */
138     private void sweep() {
139         if (isSweepable()) {
140             LOG.trace("Releasing {}", this);
141             sweepState();
142         }
143     }
144
145     static final void sweep(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
146         for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
147             stmt.sweep();
148         }
149     }
150
151     static final int countUnswept(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
152         int result = 0;
153         for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
154             if (stmt.refcount > REFCOUNT_NONE || !stmt.noImplictRef()) {
155                 result++;
156             }
157         }
158         return result;
159     }
160
161     /**
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
164      * operation.
165      *
166      * <p>
167      * {@link #effectiveSubstatements()} as well as namespaces may become inoperable as a result of this operation.
168      *
169      * @return True if the entire tree has been completely swept, false otherwise.
170      */
171     abstract int sweepSubstatements();
172
173     abstract boolean noImplictRef();
174
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.
180             sweepState();
181         }
182
183         // Propagate towards parent if there is one
184         final ReactorStmtCtx<?, ?, ?> parent = getParentContext();
185         if (parent != null) {
186             parent.sweepOnChildDecrement();
187         }
188     }
189
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.
194             sweepOnChildDone();
195             return;
196         }
197
198         // Check parent reference count
199         final int refs = refcount;
200         if (refs > REFCOUNT_NONE || refs <= REFCOUNT_SWEEPING || !noImplictRef()) {
201             // No-op
202             return;
203         }
204
205         // parent is potentially reclaimable
206         if (noParentRefcount()) {
207             LOG.trace("Cleanup {} of parent {}", refcount, this);
208             if (sweepState()) {
209                 final ReactorStmtCtx<?, ?, ?> parent = getParentContext();
210                 if (parent != null) {
211                     parent.sweepOnChildDecrement();
212                 }
213             }
214         }
215     }
216
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
224             //   a reference to us
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;
228         }
229         return true;
230     }
231
232     private boolean isAwaitingChildren() {
233         return refcount > REFCOUNT_SWEEPING && refcount < REFCOUNT_NONE;
234     }
235
236     private boolean isSweepable() {
237         return refcount == REFCOUNT_NONE && noImplictRef();
238     }
239
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);
246             return;
247         }
248         verify(current != REFCOUNT_SWEPT, "Attempt to sweep a child of swept %s", this);
249
250         refcount = current + 1;
251         LOG.trace("Child refcount {}", refcount);
252         if (refcount == REFCOUNT_NONE) {
253             sweepDone();
254             final ReactorStmtCtx<?, ?, ?> parent = getParentContext();
255             LOG.trace("Propagating to parent {}", parent);
256             if (parent != null && parent.isAwaitingChildren()) {
257                 parent.sweepOnChildDone();
258             }
259         }
260     }
261
262     private void sweepDone() {
263         LOG.trace("Sweep done for {}", this);
264         refcount = REFCOUNT_SWEPT;
265         sweepNamespaces();
266     }
267
268     private boolean sweepState() {
269         refcount = REFCOUNT_SWEEPING;
270         final int childRefs = sweepSubstatements();
271         if (childRefs == 0) {
272             sweepDone();
273             return true;
274         }
275         if (childRefs < 0 || childRefs >= REFCOUNT_DEFUNCT) {
276             LOG.warn("Negative child refcount {} cannot be stored, reference counting disabled for {}", childRefs, this,
277                 new Throwable());
278             refcount = REFCOUNT_DEFUNCT;
279         } else {
280             LOG.trace("Still {} outstanding children of {}", childRefs, this);
281             refcount = -childRefs;
282         }
283         return false;
284     }
285 }