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