Revision 5aab242c lib/locking.py

b/lib/locking.py
632 632
ALL_SET = None
633 633

  
634 634

  
635
class _AcquireTimeout(Exception):
636
  """Internal exception to abort an acquire on a timeout.
637

  
638
  """
639

  
640

  
635 641
class LockSet:
636 642
  """Implements a set of locks.
637 643

  
......
702 708
    else:
703 709
      return set()
704 710

  
711
  def _release_and_delete_owned(self):
712
    """Release and delete all resources owned by the current thread"""
713
    for lname in self._list_owned():
714
      self.__lockdict[lname].release()
715
      self._del_owned(name=lname)
716

  
705 717
  def __names(self):
706 718
    """Return the current set of names.
707 719

  
......
730 742
        self.__lock.release()
731 743
    return set(result)
732 744

  
733
  def acquire(self, names, timeout=None, shared=0):
745
  def acquire(self, names, timeout=None, shared=0, test_notify=None):
734 746
    """Acquire a set of resource locks.
735 747

  
736 748
    @param names: the names of the locks which shall be acquired
737 749
        (special lock names, or instance/node names)
738 750
    @param shared: whether to acquire in shared mode; by default an
739 751
        exclusive lock will be acquired
740
    @type timeout: float
752
    @type timeout: float or None
741 753
    @param timeout: Maximum time to acquire all locks
754
    @type test_notify: callable or None
755
    @param test_notify: Special callback function for unittesting
742 756

  
743
    @return: True when all the locks are successfully acquired
757
    @return: Set of all locks successfully acquired or None in case of timeout
744 758

  
745 759
    @raise errors.LockError: when any lock we try to acquire has
746 760
        been deleted before we succeed. In this case none of the
747 761
        locks requested will be acquired.
748 762

  
749 763
    """
750
    if timeout is not None:
751
      raise NotImplementedError
764
    assert timeout is None or timeout >= 0.0
752 765

  
753 766
    # Check we don't already own locks at this level
754 767
    assert not self._is_owned(), "Cannot acquire locks in the same set twice"
755 768

  
756
    if names is None:
769
    # We need to keep track of how long we spent waiting for a lock. The
770
    # timeout passed to this function is over all lock acquires.
771
    remaining_timeout = timeout
772
    if timeout is None:
773
      start = None
774
      calc_remaining_timeout = lambda: None
775
    else:
776
      start = time.time()
777
      calc_remaining_timeout = lambda: (start + timeout) - time.time()
778

  
779
    want_all = names is None
780

  
781
    if want_all:
757 782
      # If no names are given acquire the whole set by not letting new names
758 783
      # being added before we release, and getting the current list of names.
759 784
      # Some of them may then be deleted later, but we'll cope with this.
......
763 788
      # them exclusively though they won't be able to do this anyway, though,
764 789
      # so we'll get the list lock exclusively as well in order to be able to
765 790
      # do add() on the set while owning it.
766
      self.__lock.acquire(shared=shared)
791
      self.__lock.acquire(shared=shared, timeout=remaining_timeout)
767 792
      try:
768 793
        # note we own the set-lock
769 794
        self._add_owned()
......
775 800
        self.__lock.release()
776 801
        raise
777 802

  
803
      # Re-calculate timeout
804
      remaining_timeout = calc_remaining_timeout()
805

  
778 806
    try:
779
      # Support passing in a single resource to acquire rather than many
780
      if isinstance(names, basestring):
781
        names = [names]
782
      else:
783
        names = sorted(names)
784

  
785
      acquire_list = []
786
      # First we look the locks up on __lockdict. We have no way of being sure
787
      # they will still be there after, but this makes it a lot faster should
788
      # just one of them be the already wrong
789
      for lname in utils.UniqueSequence(names):
790
        try:
791
          lock = self.__lockdict[lname] # raises KeyError if lock is not there
792
          acquire_list.append((lname, lock))
793
        except (KeyError):
794
          if self.__lock._is_owned():
795
            # We are acquiring all the set, it doesn't matter if this
796
            # particular element is not there anymore.
797
            continue
798
          else:
799
            raise errors.LockError('non-existing lock in set (%s)' % lname)
800

  
801
      # This will hold the locknames we effectively acquired.
802
      acquired = set()
803
      # Now acquire_list contains a sorted list of resources and locks we want.
804
      # In order to get them we loop on this (private) list and acquire() them.
805
      # We gave no real guarantee they will still exist till this is done but
806
      # .acquire() itself is safe and will alert us if the lock gets deleted.
807
      for (lname, lock) in acquire_list:
808
        try:
809
          lock.acquire(shared=shared) # raises LockError if the lock is deleted
810
          # now the lock cannot be deleted, we have it!
811
          self._add_owned(name=lname)
812
          acquired.add(lname)
813
        except (errors.LockError):
814
          if self.__lock._is_owned():
815
            # We are acquiring all the set, it doesn't matter if this
816
            # particular element is not there anymore.
817
            continue
807
      try:
808
        # Support passing in a single resource to acquire rather than many
809
        if isinstance(names, basestring):
810
          names = [names]
811
        else:
812
          names = sorted(names)
813

  
814
        acquire_list = []
815
        # First we look the locks up on __lockdict. We have no way of being sure
816
        # they will still be there after, but this makes it a lot faster should
817
        # just one of them be the already wrong
818
        for lname in utils.UniqueSequence(names):
819
          try:
820
            lock = self.__lockdict[lname] # raises KeyError if lock is not there
821
            acquire_list.append((lname, lock))
822
          except KeyError:
823
            if want_all:
824
              # We are acquiring all the set, it doesn't matter if this
825
              # particular element is not there anymore.
826
              continue
827
            else:
828
              raise errors.LockError("Non-existing lock in set (%s)" % lname)
829

  
830
        # This will hold the locknames we effectively acquired.
831
        acquired = set()
832

  
833
        # Now acquire_list contains a sorted list of resources and locks we
834
        # want.  In order to get them we loop on this (private) list and
835
        # acquire() them.  We gave no real guarantee they will still exist till
836
        # this is done but .acquire() itself is safe and will alert us if the
837
        # lock gets deleted.
838
        for (lname, lock) in acquire_list:
839
          if __debug__ and callable(test_notify):
840
            test_notify_fn = lambda: test_notify(lname)
818 841
          else:
819
            name_fail = lname
820
            for lname in self._list_owned():
821
              self.__lockdict[lname].release()
822
              self._del_owned(name=lname)
823
            raise errors.LockError('non-existing lock in set (%s)' % name_fail)
824
        except:
825
          # We shouldn't have problems adding the lock to the owners list, but
826
          # if we did we'll try to release this lock and re-raise exception.
827
          # Of course something is going to be really wrong, after this.
828
          if lock._is_owned():
829
            lock.release()
830
          raise
842
            test_notify_fn = None
831 843

  
832
    except:
833
      # If something went wrong and we had the set-lock let's release it...
834
      if self.__lock._is_owned():
835
        self.__lock.release()
836
      raise
844
          try:
845
            if timeout is not None and remaining_timeout < 0:
846
              raise _AcquireTimeout()
847

  
848
            # raises LockError if the lock was deleted
849
            if not lock.acquire(shared=shared, timeout=remaining_timeout,
850
                                test_notify=test_notify_fn):
851
              # Couldn't get lock or timeout occurred
852
              if timeout is None:
853
                # This shouldn't happen as SharedLock.acquire(timeout=None) is
854
                # blocking.
855
                raise errors.LockError("Failed to get lock %s" % lname)
856

  
857
              raise _AcquireTimeout()
858

  
859
            # Re-calculate timeout
860
            remaining_timeout = calc_remaining_timeout()
861

  
862
            # now the lock cannot be deleted, we have it!
863
            self._add_owned(name=lname)
864
            acquired.add(lname)
865

  
866
          except _AcquireTimeout:
867
            # Release all acquired locks
868
            self._release_and_delete_owned()
869
            raise
870

  
871
          except errors.LockError:
872
            if want_all:
873
              # We are acquiring all the set, it doesn't matter if this
874
              # particular element is not there anymore.
875
              continue
876

  
877
            self._release_and_delete_owned()
878

  
879
            raise errors.LockError("Non-existing lock in set (%s)" % lname)
880

  
881
          except:
882
            # We shouldn't have problems adding the lock to the owners list, but
883
            # if we did we'll try to release this lock and re-raise exception.
884
            # Of course something is going to be really wrong, after this.
885
            if lock._is_owned():
886
              lock.release()
887
            raise
888

  
889
      except:
890
        # If something went wrong and we had the set-lock let's release it...
891
        if want_all:
892
          self.__lock.release()
893
        raise
894

  
895
    except _AcquireTimeout:
896
      if want_all:
897
        self._del_owned()
898

  
899
      return None
837 900

  
838 901
    return acquired
839 902

  
......
939 1002

  
940 1003
    @param names: names of the resource to remove.
941 1004

  
942
    @return:: a list of locks which we removed; the list is always
1005
    @return: a list of locks which we removed; the list is always
943 1006
        equal to the names list if we were holding all the locks
944 1007
        exclusively
945 1008

  

Also available in: Unified diff