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