package net.spy.memcached;
import net.spy.SpyObject;
import net.spy.memcached.transcoders.Transcoder;
/**
* Object that provides mutation via CAS over a given memcache client.
*
* <p>Example usage (reinventing incr):</p>
*
* <pre>
* // Get or create a client.
* MemcachedClient client=[...];
*
* // Get a Transcoder.
* Transcoder<Long> tc = new LongTranscoder();
*
* // Get a mutator instance that uses that client.
* CASMutator<Long> mutator=new CASMutator<Long>(client, tc);
*
* // Get a mutation that knows what to do when a value is found.
* CASMutation<Long> mutation=new CASMutation<Long>() {
* public Long getNewValue(Long current) {
* return current + 1;
* }
* };
*
* // Do a mutation.
* long currentValue=mutator.cas(someKey, 0L, 0, mutation);
* </pre>
*/
public class CASMutator<T> extends SpyObject {
private static final int MAX_TRIES=8192;
private final MemcachedClient client;
private final Transcoder<T> transcoder;
private final int max;
/**
* Construct a CASMutator that uses the given client.
*
* @param c the client
* @param tc the Transcoder to use
* @param max_tries the maximum number of attempts to get a CAS to succeed
*/
public CASMutator(MemcachedClient c, Transcoder<T> tc, int max_tries) {
super();
client=c;
transcoder=tc;
max=max_tries;
}
/**
* Construct a CASMutator that uses the given client.
*
* @param c the client
* @param tc the Transcoder to use
* @param max_tries the maximum number of attempts to get a CAS to succeed
*/
public CASMutator(MemcachedClient c, Transcoder<T> tc) {
this(c, tc, MAX_TRIES);
}
/**
* CAS a new value in for a key.
*
* @param key the key to be CASed
* @param initial the value to use when the object is not cached
* @param initialExp the expiration time to use when initializing
* @param m the mutation to perform on an object if a value exists for the
* key
* @return the new value that was set
*/
public T cas(final String key, final T initial, int initialExp,
final CASMutation<T> m) throws Exception {
T rv=initial;
boolean done=false;
for(int i=0; !done && i<max; i++) {
CASValue<T> casval=client.gets(key, transcoder);
T current=null;
// If there were a CAS value, check to see if it's compatible.
if(casval != null) {
T tmp = casval.getValue();
current=tmp;
}
// If we have anything mutate and CAS, else add.
if(current != null) {
rv=m.getNewValue(current);
// There are three possibilities here:
// 1) It worked and we're done.
// 2) It collided and we need to reload and try again.
// 3) It disappeared between our fetch and our cas.
// We're ignoring #3 because it's *extremely* unlikely and the
// behavior will be fine in this code -- we'll do another gets
// and follow it up with either an add or another cas depending
// on whether it exists the next time.
if(client.cas(key, casval.getCas(), rv, transcoder)
== CASResponse.OK) {
done=true;
}
} else {
// No value found, try an add.
if(client.add(key, initialExp, initial, transcoder).get()) {
done=true;
rv=initial;
}
}
}
if(!done) {
throw new RuntimeException("Couldn't get a CAS in " + max
+ " attempts");
}
return rv;
}
}