Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  package com.tectonica.gae;
  
  import java.util.Map;
 
 import  com.google.appengine.api.memcache.Expiration;
 import  com.google.appengine.api.memcache.MemcacheService;
 import  com.google.appengine.api.memcache.MemcacheService.SetPolicy;
 import  com.google.appengine.api.memcache.MemcacheServiceFactory;

a Lock implementation for Google App Engine applications, which uses the Memcache service to ensure global locking (i.e. among all the instances of the application). The lock is re-entrant (i.e. can be locked and then re-locked in a nested method by the same thread) and minimizes the amount of calls to the Memcache service by having all threads from a single instance share the Memcache polling loop.

Locking with Memcache is not a bullet-proof solution, as unexpected eviction of the cache may result in a situation where one instance acquires a lock that is in fact taken by another. However, the risk is very minimal especially if using the Dedicated Plan from Google (see here). Also, a lock entry in Memcache is very unlikely to be evicted due to LRU considerations, as locks are either short-lived or very frequently updated (in high contention).

Author(s):
Zach Melamed
 
 public class GaeMemcacheLock implements Lock
 {
 	private final static MemcacheService mc = MemcacheServiceFactory.getMemcacheService();
 
 	private static final int LOCK_AUTO_EXPIRATION_MS = 30000;
 	private static final long SLEEP_BETWEEN_RETRIES_MS = 50L;
 
 	private final String globalName;
 	private final boolean disposeWhenUnlocked;
 	private final long sleepBetweenRetriesMS;
 	private final ReentrantLock localLock;
 	{
 		protected Integer initialValue()
 		{
 			return 0;
 		}
 	};
 
 	// //////////////////////////////////////////////////////////////////////////////////////////
 
 	private AtomicInteger refCount = new AtomicInteger(0);
 	private static Map<StringGaeMemcacheLocklocks = new ConcurrentHashMap<>();
 
 	public static GaeMemcacheLock getLock(String globalNameboolean disposeWhenUnlocked)
 	{
 		return getLock(globalNamedisposeWhenUnlockedfalse);
 	}
 
 	public static GaeMemcacheLock getLock(String globalNameboolean disposeWhenUnlockedboolean locallyFairlong sleepBetweenRetriesMS)
 	{
 		synchronized ()
 		{
 			GaeMemcacheLock lock = .get(globalName);
 			if (lock == null)
 				.put(globalNamelock = new GaeMemcacheLock(globalNamedisposeWhenUnlockedlocallyFairsleepBetweenRetriesMS));
 			lock.refCount.incrementAndGet();
 			return lock;
 		}
 	}
 
 	public static void disposeLock(String globalName)
 	{
 		synchronized ()
 		{
 			GaeMemcacheLock lock = .get(globalName);
 			if (lock != null)
 			{
 				if (lock.refCount.decrementAndGet() == 0)
 					.remove(globalName);
 			}
 		}
 	}
 
 	// //////////////////////////////////////////////////////////////////////////////////////////
 
 	private GaeMemcacheLock(String globalNameboolean disposeWhenUnlockedboolean locallyFairlong sleepBetweenRetriesMS)
 	{
 		this. = globalName;
 		this. = disposeWhenUnlocked;
 		this. = sleepBetweenRetriesMS;
 		 = new ReentrantLock(locallyFair);
 	}
 
 	public void lockInterruptibly() throws InterruptedException
 	{
 	}
	public void lock()
	{
		try
		{
		}
		{
			throw new RuntimeException(e);
		}
	}
	public void unlock()
	{
	}
	public boolean tryLock()
	{
			return false;
		if (!lockGlobally())
		{
			return false;
		}
		return true;
	}
	public boolean tryLock(long timeTimeUnit unitthrows InterruptedException
	{
		long timeout = System.currentTimeMillis() + unit.toMillis(time);
		if (.tryLock(timeunit))
			return waitForGlobalLock(timeout);
		return false;
	}
	{
	}

this method blocks execution until it acquires the global lock (or gets interrupted). it does so using an infinite loop which checks a shared Memcache entry once in every short-while, until it succeeds in acquiring the lock
	private boolean waitForGlobalLock(Long timeoutthrows InterruptedException
	{
		while (true)
		{
			long before = System.currentTimeMillis();
				return true// don't wait if we got the lock
			// check for timeout
			long after = System.currentTimeMillis();
			if (timeout != null && after >= timeout.longValue())
				return false;
			// calculate the exact amount of sleep needed before the next retry
			long roudtripMS = after - before;
			long actualSleep = Math.max(0L,  - roudtripMS);
			if (actualSleep > 0L)
			{
				if (timeout != null)
				{
					// if we sleep as calculated, how much will we overflow beyond the timeout?
					long overflow = Math.max(0L, after + actualSleep - timeout.longValue());
					if ((actualSleep -= overflow) <= 0L)
						continue;
				}
			}
		}
	}

guaranteed to run only after acquiring the local lock, this method attempts to acquire the global lock too. it uses a reference counting methodology to support reentrancy.
	private boolean lockGlobally()
	{
		int holdCount = .get().intValue();
		boolean acquired;
		if (holdCount > 0)
			acquired = true;
		else
			acquired = .put("X", Expiration.byDeltaMillis(), SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
		if (acquired)
			.set(holdCount + 1);
		return acquired;
	}

guaranteed to run before releasing the local lock, this releases the global lock first.it uses a reference counting methodology to support reentrancy
	private void unlockGlobally()
	{
		int holdCount = .get().intValue() - 1;
		.set(holdCount);
		if (holdCount == 0)
			.delete();
	}
	public int hashCode()
	{
	}
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		return .equals(other.globalName);
	}
New to GrepCode? Check out our FAQ X