package water; import water.fvec.Frame; import water.fvec.Vec; import java.util.*; /** A "scope" for tracking Key lifetimes; an experimental API. * * <p>A Scope defines a <em>SINGLE THREADED</em> local lifetime management context, * stored in Thread Local Storage. Scopes can be explicitly entered or exited. * User keys created by this thread are tracked, and deleted when the scope is * exited. Since enter & exit are explicit, failure to exit means the Keys * leak (there is no reliable thread-on-exit cleanup action). You must call * <code>Scope.exit()</code> at some point. Only user keys & Vec keys are tracked.</p> * * <p>Scopes support nesting. Scopes support partial cleanup: you can list Keys * you'd like to keep in the exit() call. These will be "bumped up" to the * higher nested scope - or escaped and become untracked at the top-level.</p> */ public class Scope { // Thread-based Key lifetime tracking static private final ThreadLocal<Scope> _scope = new ThreadLocal<Scope>() { @Override protected Scope initialValue() { return new Scope(); } }; private final Stack<HashSet<Key>> _keys = new Stack<>(); /** Enter a new Scope */ static public void enter() { _scope.get()._keys.push(new HashSet<Key>()); } /** Exit the inner-most Scope, remove all Keys created since the matching * enter call except for the listed Keys. * @return Returns the list of kept keys. */ static public Key[] exit(Key... keep) { List<Key> keylist = new ArrayList<>(); if( keep != null ) for( Key k : keep ) if (k != null) keylist.add(k); Object[] arrkeep = keylist.toArray(); Arrays.sort(arrkeep); Stack<HashSet<Key>> keys = _scope.get()._keys; if (keys.size() > 0) { Futures fs = new Futures(); for (Key key : keys.pop()) { int found = Arrays.binarySearch(arrkeep, key); if ((arrkeep.length == 0 || found < 0) && key != null) Keyed.remove(key, fs); } fs.blockForPending(); } return keep; } /** Pop-scope (same as exit-scope) but return all keys that are tracked (and * would have been deleted). */ static public Key[] pop() { Stack<HashSet<Key>> keys = _scope.get()._keys; return keys.size() > 0 ? keys.pop().toArray(new Key[0]) : null; } static void track_internal( Key k ) { if( k.user_allowed() || !k.isVec() ) return; // Not tracked Scope scope = _scope.get(); // Pay the price of T.L.S. lookup if (scope == null) return; track_impl(scope, k); } static public <T extends Keyed> Keyed<T> track_generic(Keyed<T> keyed) { Scope scope = _scope.get(); // Pay the price of T.L.S. lookup assert scope != null; track_impl(scope, keyed._key); return keyed; } static public Vec track( Vec vec ) { Scope scope = _scope.get(); // Pay the price of T.L.S. lookup assert scope != null; track_impl(scope, vec._key); return vec; } /** * Track one or more {@link Frame}s, and return the first one. The tracked * frames will be removed from DKV when {@link Scope#exit(Key[])} is called. */ public static Frame track(Frame... frames) { for (Frame fr : frames) { Scope scope = _scope.get(); assert scope != null; track_impl(scope, fr._key); for (Vec vec : fr.vecs()) track_impl(scope, vec._key); } return frames[0]; } static private void track_impl(Scope scope, Key key) { // key size is 0 when tracked in the past, but no scope now if (scope._keys.size() > 0 && !scope._keys.peek().contains(key)) scope._keys.peek().add(key); // Track key } static public void untrack(Key<Vec>... keys) { Scope scope = _scope.get(); // Pay the price of T.L.S. lookup if (scope == null) return; // Not tracking this thread if (scope._keys.size() == 0) return; // Tracked in the past, but no scope now HashSet<Key> xkeys = scope._keys.peek(); for (Key<Vec> key : keys) xkeys.remove(key); // Untrack key } static public void untrack(Iterable<Key<Vec>> keys) { Scope scope = _scope.get(); // Pay the price of T.L.S. lookup if (scope == null) return; // Not tracking this thread if (scope._keys.size() == 0) return; // Tracked in the past, but no scope now HashSet<Key> xkeys = scope._keys.peek(); for (Key<Vec> key : keys) xkeys.remove(key); // Untrack key } }