package water; /** * Atomic update of a Key * * @author <a href="mailto:cliffc@h2o.ai"></a> * @version 1.0 */ abstract public class Atomic<T extends Atomic> extends DTask<T> { protected Key _key; // Transaction key public Atomic(){ super(H2O.ATOMIC_PRIORITY); } public Atomic(H2O.H2OCountedCompleter completer){super(completer,H2O.ATOMIC_PRIORITY);} // User's function to be run atomically. The Key's Value is fetched from the // home STORE and passed in. The returned Value is atomically installed as // the new Value (and the function is retried until it runs atomically). The // original Value is supposed to be read-only. If the original Key misses // (no Value), one is created with 0 length and wrong Value._type to allow // the Key to passed in (as part of the Value) abstract protected Value atomic( Value val ); /** Executed on the transaction key's <em>home</em> node after any successful * atomic update. Override this if you need to perform some action after * the update succeeds (eg cleanup). */ protected void onSuccess( Value old ){} /** Block until it completes, even if run remotely */ public final Atomic<T> invoke( Key key ) { RPC<Atomic<T>> rpc = fork(key); return (rpc == null ? this : rpc.get()); // Block for it } // Fork off public final RPC<Atomic<T>> fork(Key key) { _key = key; if( key.home() ) { // Key is home? compute2(); // Also, run it blocking/now return null; } else { // Else run it remotely return RPC.call(key.home_node(),this); } } // The (remote) workhorse: @Override public final void compute2() { assert _key.home() : "Atomic on wrong node; SELF="+H2O.SELF+ ", key_home="+_key.home_node()+", key_is_home="+_key.home()+", class="+getClass(); Futures fs = new Futures(); // Must block on all invalidates eventually Value val1 = DKV.get(_key); while( true ) { // Run users' function. This is supposed to read-only from val1 and // return new val2 to atomically install. Value val2 = atomic(val1); if( val2 == null ) { // ABORT: they gave up // Strongly order XTNs on same key, EVEN if aborting. Generally abort // means some interesting condition is already met, but perhaps met by // the exactly proceeding XTN whose invalidates are still roaming about // the system. If we do not block, the Atomic.invoke might complete // before the invalidates, and the invoker might then do a DKV.get() // and get his original value - instead of inval & fetching afresh. if (val1 != null) val1.blockTillNoReaders(); // Prior XTN that made val1 may not yet have settled out; block for it break; } assert val1 != val2; // No returning the same Value // Attempt atomic update Value res = DKV.DputIfMatch(_key,val2,val1,fs); if( res == val1 ) { // Success? fs.blockForPending(); // Block for any pending invalidates on the atomic update onSuccess(val1); // Call user's post-XTN function break; } val1 = res; // Otherwise try again with the current value } // and retry _key = null; // No need for key no more, don't send it back tryComplete(); } }