Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright (C) 2014 Zach Melamed
   * 
   * Latest version available online at https://github.com/zach-m/tectonica-commons
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package com.tectonica.gae;
 
 
 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.

Each lock object is associated with a globally-unique name, so that when separate threads (on possibly different instances) try to acquire a lock with the same name, only one at the time succeeds.

The usage is straightforward

 Lock lock = GaeMemcacheLock.getLock("LOCK_NAME", true);
 lock.lock();
 try {
    ...
 }
 finally {
    lock.unlock();
 }
 
Each lock attained with getLock(String, boolean) must be eventually released with disposeLock(String). It's possible however to attain a lock that's set for automatic disposal (upon its unlock()).

NOTE: 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 static final int LOCK_AUTO_EXPIRATION_MS = 30000;
 	private static final long SLEEP_BETWEEN_RETRIES_MS = 50L;
 
 	private final MemcacheService mc;
 	private final String globalName;
 	private final boolean disposeWhenUnlocked;
 	private final long sleepBetweenRetriesMS;
 	private final ReentrantLock localLock;
 	{
 		protected Integer initialValue()
 		{
 			return 0;
 		}
 	};
 
 	private GaeMemcacheLock(String globalNameboolean disposeWhenUnlockedString namespaceboolean locallyFair,
 			long sleepBetweenRetriesMS)
 	{
 		this. = MemcacheServiceFactory.getMemcacheService(namespace);
 		this. = globalName;
 		this. = disposeWhenUnlocked;
 		this. = sleepBetweenRetriesMS;
 		this. = new ReentrantLock(locallyFair);
 	}
 
 	public void lockInterruptibly() throws InterruptedException
 	{
 		lockGlobally(null);
 	}
 
	public void lock()
	{
		try
		{
		}
		{
			throw new RuntimeException(e);
		}
	}
	public void unlock()
	{
	}
	public boolean tryLock()
	{
			return false;
		{
			.unlock(); // since we weren't able to lock globally, no point in holding the local lock we just acquired
			return false;
		}
		return true;
	}
	public boolean tryLock(long timeTimeUnit unitthrows InterruptedException
	{
		long timeout = System.currentTimeMillis() + unit.toMillis(time);
		if (.tryLock(timeunit))
			return lockGlobally(timeout);
		return false;
	}
	{
	}

Blocks until the global lock is acquired, a timeout is reached, or an interruption occurs. It does so using an infinite loop which checks a shared Memcache entry once in every short-while
	private boolean lockGlobally(Long timeoutthrows InterruptedException
	{
		while (true)
		{
			if (Thread.interrupted())
				throw new InterruptedException();
			long before = System.currentTimeMillis();
				return true;
			// check if we timed out
			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;
				}
			}
		}
	}

Performs a single attempt to acquire the global lock. Supports reentrancy.

Returns:
true if the global lock was acquired, false otherwise (i.e. lock is acquired by another instance)
	private boolean tryLockGlobally()
	{
		int depth = .get().intValue();
		boolean acquired;
		if (depth > 0)
			acquired = true;
		else
			acquired = .put("X", Expiration.byDeltaMillis(), SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
		if (acquired)
			.set(depth + 1);
		return acquired;
	}

Guaranteed to run before releasing the local lock, this method releases the global lock first. It uses a reference counting methodology to support reentrancy
	private void unlockGlobally()
	{
		int depth = .get().intValue() - 1;
		if (depth == 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);
	}
	// //////////////////////////////////////////////////////////////////////////////////////////
	private static AutoEvictMap<StringGaeMemcacheLocklocks = new AutoEvictMap<>();

see getLock(String, boolean, String, boolean, long)
	public static GaeMemcacheLock getLock(String globalNameboolean disposeWhenUnlocked)
	{
		return getLock(globalNamedisposeWhenUnlockednullfalse);
	}

Returns a Lock object, for global locking within all App-Engine instances (or more accurately, all that share a given namespace). When the lock is no longer needed, invoke disposeLock(String). Alternatively, you may pass disposeWhenUnlocked = true here to have the dispose invoked automatically upon Lock.unlock().

NOTE: The returned lock does not support newCondition().

Parameters:
globalName unique name among all the App Engine instances
disposeWhenUnlocked if true, disposes the returned lock automatically upon Lock.unlock()
namespace if not null, uses a namespaced Memcache service
locallyFair indicates whether it's important to treat the local threads waiting on the lock fairly
sleepBetweenRetriesMS delay between attempts to acquire the Memcache lock
Returns:
	public static GaeMemcacheLock getLock(final String globalNamefinal boolean disposeWhenUnlockedfinal String namespace,
			final boolean locallyFairfinal long sleepBetweenRetriesMS)
	{
		try
		{
			return .acquire(globalNamenew AutoEvictMap.Factory<StringGaeMemcacheLock>()
			{
				{
					return new GaeMemcacheLock(globalNamedisposeWhenUnlockednamespacelocallyFairsleepBetweenRetriesMS);
				}
			});
		}
		{
			throw new RuntimeException(e);
		}
	}
	public static void disposeLock(String globalName)
	{
		.release(globalName);
	}
New to GrepCode? Check out our FAQ X