net.spy.memcached.CASMutator Maven / Gradle / Ivy
The newest version!
package net.spy.memcached;
import net.spy.memcached.compat.SpyObject;
import net.spy.memcached.transcoders.Transcoder;
/**
* Object that provides mutation via CAS over a given memcache client.
*
* Example usage (reinventing incr):
*
* {@code
* // Get or create a client.
* MemcachedClient client=[...];
*
* // Get a Transcoder.
* Transcoder tc = new LongTranscoder();
*
* // Get a mutator instance that uses that client.
* CASMutator mutator=new CASMutator(client, tc);
*
* // Get a mutation that knows what to do when a value is found.
* CASMutation mutation=new CASMutation() {
* public Long getNewValue(Long current) {
* return current + 1;
* }
* };
*
* // Do a mutation.
* long currentValue=mutator.cas(someKey, 0L, 0, mutation);
* }
*/
public class CASMutator extends SpyObject {
private static final int MAX_TRIES = 8192;
private final MemcachedClientIF client;
private final Transcoder 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(MemcachedClientIF c, Transcoder 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
*/
public CASMutator(MemcachedClientIF c, Transcoder tc) {
this(c, tc, MAX_TRIES);
}
/**
* CAS a new value in for a key.
*
*
* Note that if initial is null, this method will only update existing
* values.
*
*
* @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 m) throws Exception {
T rv = initial;
boolean done = false;
for (int i = 0; !done && i < max; i++) {
CASValue 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) {
// Declaring this impossible since the only way current can
// be non-null is if casval was set.
assert casval != null : "casval was null with a current value";
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(), initialExp, rv, transcoder)
== CASResponse.OK) {
done = true;
}
} else {
// No value found, try an add.
if (initial == null) {
done = true;
rv = null;
} else 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;
}
}