Revision 3b7ed473
b/lib/locking.py | ||
---|---|---|
372 | 372 |
# Check we don't already own locks at this level |
373 | 373 |
assert not self._is_owned(), "Cannot acquire locks in the same set twice" |
374 | 374 |
|
375 |
if names is None: |
|
376 |
# If no names are given acquire the whole set by not letting new names |
|
377 |
# being added before we release, and getting the current list of names. |
|
378 |
# Some of them may then be deleted later, but we'll cope with this. |
|
379 |
# |
|
380 |
# We'd like to acquire this lock in a shared way, as it's nice if |
|
381 |
# everybody else can use the instances at the same time. If are acquiring |
|
382 |
# them exclusively though they won't be able to do this anyway, though, |
|
383 |
# so we'll get the list lock exclusively as well in order to be able to |
|
384 |
# do add() on the set while owning it. |
|
385 |
self.__lock.acquire(shared=shared) |
|
386 |
|
|
375 | 387 |
try: |
376 | 388 |
# Support passing in a single resource to acquire rather than many |
377 | 389 |
if isinstance(names, basestring): |
378 | 390 |
names = [names] |
379 | 391 |
else: |
392 |
if names is None: |
|
393 |
names = self.__names() |
|
380 | 394 |
names.sort() |
381 | 395 |
|
382 | 396 |
acquire_list = [] |
... | ... | |
388 | 402 |
lock = self.__lockdict[lname] # raises KeyError if the lock is not there |
389 | 403 |
acquire_list.append((lname, lock)) |
390 | 404 |
except (KeyError): |
391 |
raise errors.LockError('non-existing lock in set (%s)' % lname) |
|
405 |
if self.__lock._is_owned(): |
|
406 |
# We are acquiring all the set, it doesn't matter if this particular |
|
407 |
# element is not there anymore. |
|
408 |
continue |
|
409 |
else: |
|
410 |
raise errors.LockError('non-existing lock in set (%s)' % lname) |
|
392 | 411 |
|
393 | 412 |
# This will hold the locknames we effectively acquired. |
394 | 413 |
acquired = set() |
... | ... | |
411 | 430 |
raise |
412 | 431 |
|
413 | 432 |
except (errors.LockError): |
414 |
name_fail = lname |
|
415 |
for lname in self._list_owned(): |
|
416 |
self.__lockdict[lname].release() |
|
417 |
self._del_owned(lname) |
|
418 |
raise errors.LockError('non-existing lock in set (%s)' % name_fail) |
|
433 |
if self.__lock._is_owned(): |
|
434 |
# We are acquiring all the set, it doesn't matter if this particular |
|
435 |
# element is not there anymore. |
|
436 |
continue |
|
437 |
else: |
|
438 |
name_fail = lname |
|
439 |
for lname in self._list_owned(): |
|
440 |
self.__lockdict[lname].release() |
|
441 |
self._del_owned(lname) |
|
442 |
raise errors.LockError('non-existing lock in set (%s)' % name_fail) |
|
419 | 443 |
|
420 | 444 |
except: |
445 |
# If something went wrong and we had the set-lock let's release it... |
|
446 |
if self.__lock._is_owned(): |
|
447 |
self.__lock.release() |
|
421 | 448 |
raise |
422 | 449 |
|
423 | 450 |
return acquired |
... | ... | |
448 | 475 |
"release() on unheld resources %s" % |
449 | 476 |
names.difference(self._list_owned())) |
450 | 477 |
|
478 |
# First of all let's release the "all elements" lock, if set. |
|
479 |
# After this 'add' can work again |
|
480 |
if self.__lock._is_owned(): |
|
481 |
self.__lock.release() |
|
482 |
|
|
451 | 483 |
for lockname in names: |
452 | 484 |
# If we are sure the lock doesn't leave __lockdict without being |
453 | 485 |
# exclusively held we can do this... |
... | ... | |
463 | 495 |
shared: is the pre-acquisition shared? |
464 | 496 |
|
465 | 497 |
""" |
498 |
|
|
499 |
assert not self.__lock._is_owned(shared=1), ( |
|
500 |
"Cannot add new elements while sharing the set-lock") |
|
501 |
|
|
466 | 502 |
# Support passing in a single resource to add rather than many |
467 | 503 |
if isinstance(names, basestring): |
468 | 504 |
names = [names] |
469 | 505 |
|
470 |
# Acquire the internal lock in an exclusive way, so there cannot be a |
|
471 |
# conflicting add() |
|
472 |
self.__lock.acquire() |
|
506 |
# If we don't already own the set-level lock acquire it in an exclusive way |
|
507 |
# we'll get it and note we need to release it later. |
|
508 |
release_lock = False |
|
509 |
if not self.__lock._is_owned(): |
|
510 |
release_lock = True |
|
511 |
self.__lock.acquire() |
|
512 |
|
|
473 | 513 |
try: |
474 | 514 |
invalid_names = set(self.__names()).intersection(names) |
475 | 515 |
if invalid_names: |
... | ... | |
499 | 539 |
self.__lockdict[lockname] = lock |
500 | 540 |
|
501 | 541 |
finally: |
502 |
self.__lock.release() |
|
542 |
# Only release __lock if we were not holding it previously. |
|
543 |
if release_lock: |
|
544 |
self.__lock.release() |
|
503 | 545 |
|
504 | 546 |
return True |
505 | 547 |
|
b/test/ganeti.locking_unittest.py | ||
---|---|---|
331 | 331 |
# Cannot remove 'three' as we are sharing it |
332 | 332 |
self.assertRaises(AssertionError, self.ls.remove, 'three') |
333 | 333 |
|
334 |
def testAcquireSetLock(self): |
|
335 |
# acquire the set-lock exclusively |
|
336 |
self.assertEquals(self.ls.acquire(None), set(['one', 'two', 'three'])) |
|
337 |
# I can still add/remove elements... |
|
338 |
self.assertEquals(self.ls.remove(['two', 'three']), ['two', 'three']) |
|
339 |
self.assert_(self.ls.add('six')) |
|
340 |
self.ls.release() |
|
341 |
# share the set-lock |
|
342 |
self.assertEquals(self.ls.acquire(None, shared=1), set(['one', 'six'])) |
|
343 |
# adding new elements is not possible |
|
344 |
self.assertRaises(AssertionError, self.ls.add, 'five') |
|
345 |
self.ls.release() |
|
346 |
|
|
334 | 347 |
def _doLockSet(self, set, shared): |
335 | 348 |
try: |
336 | 349 |
self.ls.acquire(set, shared=shared) |
... | ... | |
339 | 352 |
except errors.LockError: |
340 | 353 |
self.done.put('ERR') |
341 | 354 |
|
355 |
def _doAddSet(self, set): |
|
356 |
try: |
|
357 |
self.ls.add(set, acquired=1) |
|
358 |
self.done.put('DONE') |
|
359 |
self.ls.release() |
|
360 |
except errors.LockError: |
|
361 |
self.done.put('ERR') |
|
362 |
|
|
342 | 363 |
def _doRemoveSet(self, set): |
343 | 364 |
self.done.put(self.ls.remove(set)) |
344 | 365 |
|
... | ... | |
412 | 433 |
self.assertEqual(self.done.get(True, 1), ['two']) |
413 | 434 |
self.ls.release() |
414 | 435 |
|
436 |
def testConcurrentSharedSetLock(self): |
|
437 |
# share the set-lock... |
|
438 |
self.ls.acquire(None, shared=1) |
|
439 |
# ...another thread can share it too |
|
440 |
Thread(target=self._doLockSet, args=(None, 1)).start() |
|
441 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
442 |
# ...or just share some elements |
|
443 |
Thread(target=self._doLockSet, args=(['one', 'three'], 1)).start() |
|
444 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
445 |
# ...but not add new ones or remove any |
|
446 |
Thread(target=self._doAddSet, args=(['nine'])).start() |
|
447 |
Thread(target=self._doRemoveSet, args=(['two'], )).start() |
|
448 |
self.assertRaises(Queue.Empty, self.done.get, True, 0.2) |
|
449 |
# this just releases the set-lock |
|
450 |
self.ls.release([]) |
|
451 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
452 |
# release the lock on the actual elements so remove() can proceed too |
|
453 |
self.ls.release() |
|
454 |
self.assertEqual(self.done.get(True, 1), ['two']) |
|
455 |
|
|
456 |
def testConcurrentExclusiveSetLock(self): |
|
457 |
# acquire the set-lock... |
|
458 |
self.ls.acquire(None, shared=0) |
|
459 |
# ...no one can do anything else |
|
460 |
Thread(target=self._doLockSet, args=(None, 1)).start() |
|
461 |
Thread(target=self._doLockSet, args=(None, 0)).start() |
|
462 |
Thread(target=self._doLockSet, args=(['three'], 0)).start() |
|
463 |
Thread(target=self._doLockSet, args=(['two'], 1)).start() |
|
464 |
Thread(target=self._doAddSet, args=(['nine'])).start() |
|
465 |
self.ls.release() |
|
466 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
467 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
468 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
469 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
470 |
self.assertEqual(self.done.get(True, 1), 'DONE') |
|
471 |
|
|
415 | 472 |
|
416 | 473 |
class TestGanetiLockManager(unittest.TestCase): |
417 | 474 |
|
Also available in: Unified diff