[infinispan-dev] Re: Locking
Manik Surtani
manik at jboss.org
Tue Apr 28 05:16:19 EDT 2009
Hi Vladimir
I haven't spent too much time thinking about eager locking yet -
swamped with the finishing touches on 4.0.0.ALPHA2 and getting a
public splash ready. :-)
Also, infinispan-dev (now cc'd!) is pretty informal so don't worry
about getting a perfect, distilled design. The purpose of the list is
to distil the design.
I've included my initial thoughts inline, below.
On 28 Apr 2009, at 03:18, Vladimir Blagojevic wrote:
> Manik,
>
> Have you thought about if we need non-eager locking? I have put
> sketches of requirements for locking. Maybe you can put final
> touches and we can post it to infinispan-dev.
>
> Regards,
> Vladimir
>
>
> Introduction
>
> The difference between eager and non-eager locking is related to
> timing and method of lock acquisition across the cluster. Eager
> locking is used by a user who wants to lock set of keys prior to
> invoking a batch of operations on those keys. If locks are
> successfully obtained on all keys across all cluster nodes user is
> sure that he/she will not get lock acquisition exception during
> those batch operations.
>
> As far as implementation goes eager locking is executed by
> synchronously replicating lock command across the network. Depending
> on a cache mode, lock acquisitions are attempted on all specified
> keys on all cluster nodes or on a subset of cluster nodes - in case
> of DIST cache set-up. Lock operation either succeeds or fails. Eager
> locking can be used with or without transactions.
>
All makes sense, except that we need to think who the lock 'owner' is
going to be if we are not running in a tx. The way the cache works,
locks are held by the thread (if not in a tx) or the associated
GlobalTransaction object (if in a tx).
Let's take these cases one at a time. Let's start with (what I think)
is the most pertinent:
A. Transactions + Eager locking
---------------------------------
when you do the following:
1. tx.begin()
2. cache.put(K, V)
3. tx.commit()
what happens is, at 1., a new GTX instance is associated with the
transaction. At 2. locks are acquired on K on the *local* cache, the
owner being GTX. At 3., a prepare() is broadcast and the locks on K
are acquired on all remote participating caches, the owner being GTX
again.
The GTX is propagated with the PrepareCommand as it is a field in
AbstractTransactionBoundaryCommand.
So now, an explicit lock in the scope of a transaction would also need
to propagate the GTX - just so we know who the lock owner should be.
E.g.,
1. tx.begin()
2. cache.lock(K)
3. // read K,V and calculate V2 which is a function of V. E.g., V2 =
V + 1
4. cache.put(K, V2)
5. tx.commit()
In the above scenario, step 2 broadcasts a LockControlCommand which is
constructed with the GTX and the necessary key(s). Remote caches
acquire the locks using the GTX as the owner, and responds
positively. (this RPC call would *have* to be sync regardless of
cache mode).
The way I see it, cache.unlock() should be *optional*. If unlock() is
not called, when the transaction completes all locks associated with
the transaction are released anyway.
If unlock() is used, it would need some special behaviour. Consider:
1. tx.begin()
2. cache.lock(K)
3. cache.put(K2, V2)
4. cache.unlock(K)
5. tx.commit()
In the above case, unlock() would release locks at step 4 since the tx
has not actually modified K. If, however, we have:
1. tx.begin()
2. cache.lock(K)
3. cache.put(K, V)
4. cache.unlock(K)
5. tx.commit()
then the unlock() should be a no-op (maybe don't even bother with the
RPC) since we know that the tx has modified K and we can only feasibly
release the lock once the transaction completes in 5.
B. Transactions + Non-eager locking
---------------------------------
So I guess non-eager would be that the acquisition of remote locks are
deferred to when the prepare() broadcasts, and any potential lock
acquisition failures happen at the time of prepare(). This is what we
have in place right now anyway, I need to think about whether lock()
and unlock() makes sense in a non-eager context.
C. No transaction + Eager locking
---------------------------------
This gets a bit more tricky, because in this case the lock owner is
the Thread. And this does not translate to anything meaningful
cluster-wide. Assume:
1. cache.lock(K)
2. cache.get(K)
3. cache.put(K, V)
4. cache.unlock()
Here, we notice that explicit use of unlock() is needed to release
locks acquired by lock(), unlike the case where a committing (or
rolling back) transaction would do this for you.
The tricky thing here is, who holds lock ownership across a
cluster? :-) If this is standalone mode, this is simple - the Thread-
owner paradigm works fine. Perhaps we need to change the locking code
so that the non-tx lock owner is a combination of thread-name + cache
address? I can see this leading to tricky stale locks though if we
have a join/rejoin midway during a code sequence like above.
D. No transaction + Non-eager locking
---------------------------------
Just as in B., I'm not sure if this is a valid use case. I.e., how is
this useful and what is its purpose. (perhaps just acquire local
locks on lock() and only acquire the remote locks when the actual
put() is performed?)
Anyway, to sum things up: the way I see it, B and D are cases that
still need further thought (i.e., the non-eager cases). C needs a lot
more in-depth thought as it has the potential to break a lot of stuff
as it changes lock ownership for all non-transactional cases. So I am
inclined to suggest that lock() and unlock() only works for case A.
For other cases, we do not support the use of these methods. At least
for now, until we think of C in greater detail.
What do you think? Apart from that, the detailed changes you
mentioned below all make sense (except that I would add lockOwner as a
field on LockControlCommand, etc).
Cheers
Manik
>
> Example usage
>
> Cache.lock(k1, k2, k3)
> lots of rw operations with k1, k2, k3 while being sure no lock
> acquisition exceptions will be raisedCache.unlock(k1, k2, k3)
>
>
>
> Implementation
>
>
>
> 1. Add the following API to AdvancedCache and implementation to
> CacheDelegate
>
> void lock(K key, boolean eager);
> void lock(Collection<? extends K> keys, boolean eager);
> void unlock(K key);
> void unlock(Collection<? extends K> keys);
>
>
>
> 2. Add the following API to CommandsFactory along with
> implementation in CommandsFactoryImpl
>
> public LockControlCommand buildLockControlCommand();
>
>
>
> 3. LockControlCommand has following fields:
>
> a boolean to indicate whether this is a lock or unlock
> a boolean to indicate whether this is an eager lock
> an array of keys to be locked/unlocked
>
>
>
> 4. Add LockingInterceptor that intercepts LockControlCommand and
> handles acquiring/releasing of locks as needed. Lock acquisition/
> release is implemented by using already existing LockManager. If
> LockManager is able to lock all required keys true is returned,
> otherwise false.
>
>
>
>
>
>
>
>
>
>
>
>
>
>
--
Manik Surtani
manik at jboss.org
Lead, Infinispan
Lead, JBoss Cache
http://www.infinispan.org
http://www.jbosscache.org
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/infinispan-dev/attachments/20090428/4617c820/attachment-0004.html
More information about the infinispan-dev
mailing list