package water; import water.nbhm.NonBlockingHashMap; /** * Get the given key from the remote node * * @author <a href="mailto:cliffc@h2o.ai"></a> * @version 1.0 */ public class TaskGetKey extends DTask<TaskGetKey> { Key _key; // Set by client/sender JVM, cleared by server JVM Value _val; // Set by server JVM, read by client JVM transient Key _xkey; // Set by client, read by client transient H2ONode _h2o; // Set by server JVM, read by server JVM on ACKACK // Unify multiple Key/Value fetches for the same Key from the same Node at // the "same time". Large key fetches are slow, and we'll get multiple // requests close in time. Batch them up. private static final NonBlockingHashMap<Key,RPC<TaskGetKey>> TGKS = new NonBlockingHashMap(); // Get a value from a named remote node static Value get( H2ONode target, Key key ) { return get(start(target,key)); } static Value get(RPC<TaskGetKey> rpc) { return rpc.get()._val; // Block for it } // Start an RPC to fetch a Value, handling short-cutting dup-fetches static RPC<TaskGetKey> start( H2ONode target, Key key ) { // Do we have an old TaskGetKey in-progress? RPC<TaskGetKey> old = TGKS.get(key); if( old != null ) return old; // Make a new TGK. RPC<TaskGetKey> rpc = new RPC(target,new TaskGetKey(key),1.0f); if( (old=TGKS.putIfMatchUnlocked(key,rpc,null)) != null ) return old; // Failed because an old exists rpc.setTaskNum().call(); // Start the op return rpc; // Successful install of a fresh RPC } private TaskGetKey( Key key ) { super(H2O.GET_KEY_PRIORITY); _key = _xkey = key; } // Top-level non-recursive invoke @Override public void dinvoke( H2ONode sender ) { _h2o = sender; Key k = _key; _key = null; // Not part of the return result assert k.home(); // Gets are always from home (less we do replication) // Shipping a result? Track replicas so we can invalidate. There's a // narrow race on a moving K/V mapping tracking this Value just as it gets // deleted - in which case, simply retry for another Value. do _val = Value.STORE_get(k); // The return result while( _val != null && !_val.setReplica(sender) ); tryComplete(); } @Override public void compute2() { throw H2O.fail(); } // Received an ACK; executes on the node asking&receiving the Value @Override public void onAck() { if( _val != null ) { // Set transient fields after deserializing assert !_xkey.home() && _val._key == null; _val._key = _xkey; } // Now update the local store, caching the result. // We only started down the TGK path because we missed locally, so we only // expect to find a NULL in the local store. If somebody else installed // another value (e.g. a racing TGK, or racing local Put) this value must // be more recent than our NULL - but is UNORDERED relative to the Value // returned from the Home. We'll take the local Value to preserve ordering // and rely on invalidates from Home to force refreshes as needed. // Hence we can do a blind putIfMatch here over a null or empty Value // If it fails, what is there is also the TGK result. Value old = H2O.STORE.get(_xkey); if( old != null && !old.isEmpty() ) old=null; Value res = H2O.putIfMatch(_xkey,_val,old); if( res != old ) _val = res; TGKS.remove(_xkey); // Clear from dup cache } // Received an ACKACK; executes on the node sending the Value @Override public void onAckAck() { if( _val != null ) _val.lowerActiveGetCount(_h2o); } }