[jboss-user] [JBoss Cache] - Re: Unable to acquire lock on Fqn error

Ryan Hochstetler do-not-reply at jboss.com
Tue Jan 11 15:30:26 EST 2011

Ryan Hochstetler [http://community.jboss.org/people/ryanhos] created the discussion

"Re: Unable to acquire lock on Fqn error"

To view the discussion, visit: http://community.jboss.org/message/580136#580136

Those of you stuck on JBoss Cache 3.2.x that are running into this bug can use the following work-around to eliminate this issue.

The root of the problem is as previously mentioned, the "acquire lock lock on modification, but only acquire the remote lock on commit" pattern.  (actually, it's acquired on prepare(), but JBoss Cache's prepare() is called during JTA's commit()).  The answer is to prevent the deadlock from ever occurring by denying lock requests that would create such a deadlock.

1. Determine which LockManager your configuration uses.  Inspect LockManagerFactory and your current <jbosscache> XML or runtime configuration to determine which one gets constructed for you.
2. MyCompanyCustomLockManager extends JBCLockManagerFromStepOne.
3. Intercept every visible lock() method.  
4. Implement shouldLockBeGranted(Object, GlobalTransaction) throws CacheException, call it during each of the intercepted methods.  Throw CacheException when the lock should not be granted.
5. Install your custom Lock Manager.

On installing your custom lock manager:  If you are permitted to modify and repackage OSS code on your project, you're home free.  If not, keep reading.  JBoss Cache contains a homegrown DI framework.  You can abuse this DI framework to inject your own LockManager.

1. Implement MyCompanyCacheFactory extends DefaultCacheFactory.  Update your configuration/code to use this cache factory instead.
2. Make sure this is called before cache start(): componentRegistry.registerComponent(new MyCompanyCustomLockManager(), LockManager.class);
3. Annotate MyCompanyCustomLockManager as @NonVolatile.  This is a violation of the spirit of that annotation, but the component registry purges all volatile components during the cache start() phase.  The caveat here is that the LockManager will be fixed, regardless of changes to the <locking> portion of the configuration.  Remember, we're just trying to duct-tape a broken bit of software that we're stuck with, not make durable, maintainable software.

As for the algorithm of shouldLockBeGranted(Object, GlobalTransaction), I cannot give you the code.  It must be deterministic.  Each node must be able to calculate the superiority of one lock request over another without communicating with the other nodes.  You must create some artificial method of ordering GlobalTransactions.  The primary key of a GlobalTransaction is a JGroups Address and a java.lang.Long transaction ID.  That artificial ordering may not be fair, but it does at least allow one process to win, while the other one is told that it requested a write which would have ended in a deadlock.  (e.g. WriteLockDeniedException extends CacheException).

3 Cluster Nodes: A, B, and C.  2 Transactions: X and Y.  1 Cache node: "foo".
Assume that the artificial ordering of GlobalTransactions places Y before X (Y < X).

*A*: cache.put(foo, bar, bat);  "foo" is now locally locked for TX X.
*A*: Create GlobalTransaction X, associate with JTA transaction.
*C*: cache.put(foo, bar, boo);  "foo is now locally locked for TX Y.
*C*: Create GlobalTransaction Y, associate with JTA transaction.
(remember, time ordering doesn't matter here.  The TX ordering is artificial and not fair, just deterministic).
*A*: commit();
*B*: received request for lock on "foo" for TX X.  shouldLockBeGranted == X < null == true.  Granted.  (a lock request is always considred superior to "no existing lock", i.e. getWriteOwner() == null).
*C*: commit();
*B*: received request for lock on "foo" for TX Y.  shouldLockBeGranted == Y < X == true.  Granted, but waiting lockAcquisitionTimeout millis until lock is available.
*A*: received request for lock on "foo" for TX Y.  shouldLockBeGranted == Y < X == true.  Granted, but waiting.
*C*: received request for lock on "foo" from TX X.  shouldLockBeGranted == X < Y == FALSE.  Not granted.
*A*: prepare() failed on C.  Okay, tell everybody to abort, unlocking "foo"
*B*: got abort for TX X.  unlock "foo".
*B*: lock foo is now available for TX Y
*A*: everybody aborted, time for me to abort.  lock is now available for TX Y.
*C*: Got locks for TX Y from every other Node (A, B).  prepare() done.  commit().

*Caveats and notes: * 
* All of our transactions only involve a single cache node.  It's a cache, not a database...
* We use MVCC locking.  If you're not, YMMV.
* This was my best effort within the constraints of my project.  It works for me with 4 JBoss cluster nodes, 40 cache writes per second per cluster node, performed randomly on a pool of 500 cache nodes.  Some nodes win, some nodes get the WriteLockDeniedException.  It's better than everybody waiting 15 seconds for a TimeoutException.
* The inherent unfairness of the artificial ordering of transactions is mitigated by the fact that we use node.replace(key, oldValue, newValue) to guarantee that good cache state is not overwritten.

Reply to this message by going to Community

Start a new discussion in JBoss Cache at Community

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/jboss-user/attachments/20110111/1dd13ae5/attachment-0001.html 

More information about the jboss-user mailing list