Make sweep-on-decrement more aggressive
[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.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;
20
21 /**
22  * Real "core" reactor statement implementation of {@link Mutable}, supporting basic reactor lifecycle.
23  *
24  * @param <A> Argument type
25  * @param <D> Declared Statement representation
26  * @param <E> Effective Statement representation
27  */
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);
31
32     /**
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}).
37      *
38      * <p>
39      * Reference count is hierarchical in that parent references also pin down their child statements and do not allow
40      * them to be swept.
41      *
42      * <p>
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}.
46      */
47     private int refcount = REFCOUNT_NONE;
48     /**
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.
51      */
52     private static final int REFCOUNT_NONE = 0;
53     /**
54      * Reference count overflow or some other recoverable logic error. Do not rely on refcounts and do not sweep
55      * anything.
56      *
57      * <p>
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}.
62      */
63     private static final int REFCOUNT_DEFUNCT = Integer.MAX_VALUE;
64     /**
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.
67      *
68      * <p>
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}.
72      */
73     private static final int REFCOUNT_SWEEPING = -Integer.MAX_VALUE;
74     /**
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.
77      *
78      * <p>
79      * Note on value assignment:
80      * This is the lowest value observable, making it easier on checking others on equality.
81      */
82     private static final int REFCOUNT_SWEPT = Integer.MIN_VALUE;
83
84     /**
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.
87      *
88      * @throws VerifyException if {@link #effectiveSubstatements()} has already been discarded
89      */
90     final void incRef() {
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;
96         } else {
97             LOG.debug("Disabled refcount increment of {}", this);
98         }
99     }
100
101     /**
102      * Release a reference on this context. This call may result in {@link #effectiveSubstatements()} becoming
103      * unavailable.
104      */
105     final void decRef() {
106         final int current = refcount;
107         if (current == REFCOUNT_DEFUNCT) {
108             // no-op
109             LOG.debug("Disabled refcount decrement of {}", this);
110             return;
111         }
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;
116             return;
117         }
118
119         refcount = current - 1;
120         LOG.trace("Refcount {} on {}", refcount, this);
121         if (isSweepable()) {
122             // We are no longer guarded by effective instance
123             sweepOnDecrement();
124         }
125     }
126
127     final void releaseImplicitRef() {
128         if (refcount == REFCOUNT_NONE) {
129             sweepOnDecrement();
130         }
131     }
132
133     /**
134      * Sweep this statement context as a result of {@link #sweepSubstatements()}, i.e. when parent is also being swept.
135      */
136     private void sweep() {
137         if (isSweepable()) {
138             LOG.trace("Releasing {}", this);
139             sweepState();
140         }
141     }
142
143     static final void sweep(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
144         for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
145             stmt.sweep();
146         }
147     }
148
149     static final int countUnswept(final Collection<? extends ReactorStmtCtx<?, ?, ?>> substatements) {
150         int result = 0;
151         for (ReactorStmtCtx<?, ?, ?> stmt : substatements) {
152             if (stmt.refcount > REFCOUNT_NONE || !stmt.noImplictRef()) {
153                 result++;
154             }
155         }
156         return result;
157     }
158
159     /**
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
162      * operation.
163      *
164      * <p>
165      * {@link #effectiveSubstatements()} as well as namespaces may become inoperable as a result of this operation.
166      *
167      * @return True if the entire tree has been completely swept, false otherwise.
168      */
169     abstract int sweepSubstatements();
170
171     abstract boolean noImplictRef();
172
173     abstract @Nullable ReactorStmtCtx<?, ?, ?> parentStmtCtx();
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 = parentStmtCtx();
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 = parentStmtCtx();
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 = parentStmtCtx();
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 = parentStmtCtx();
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 }