This is an issue with the way the LockManager attempts to acquire locks. The crux of the problem is that when the LM waits on a lock that is already held, while within the context of a transaction, the transaction manager may decide to abort that transaction. This could be for various reasons, including a transaction timing out. Typically this would involve the transaction manager calling XAResource.end() followed by XAResource.rollback(). Unfortunately, this will almost always happen on a separate maintenance thread, and when the transaction is cleaned up, the user's original thread - still waiting for a lock - is not notified. But the transaction is rolled back and as far as the transaction manager is concerned, all is well.
Subsequently, when the lock is released, the original thread waiting on the lock acquires the lock, and continues. However this transaction never completes since it has already "rolled back", leaving a stale lock in the system.
The solution is after a lock is acquired, always check if it is in a transactional context and that the transaction is valid; if not, release the lock and throw an exception.