|
28 | 28 | TimeoutError,
|
29 | 29 | TryAgainError,
|
30 | 30 | )
|
| 31 | +from redis.lock import Lock |
31 | 32 | from redis.utils import (
|
32 | 33 | dict_merge,
|
33 | 34 | list_keys_to_dict,
|
@@ -742,6 +743,72 @@ def pipeline(self, transaction=None, shard_hint=None):
|
742 | 743 | reinitialize_steps=self.reinitialize_steps,
|
743 | 744 | )
|
744 | 745 |
|
| 746 | + def lock( |
| 747 | + self, |
| 748 | + name, |
| 749 | + timeout=None, |
| 750 | + sleep=0.1, |
| 751 | + blocking_timeout=None, |
| 752 | + lock_class=None, |
| 753 | + thread_local=True, |
| 754 | + ): |
| 755 | + """ |
| 756 | + Return a new Lock object using key ``name`` that mimics |
| 757 | + the behavior of threading.Lock. |
| 758 | +
|
| 759 | + If specified, ``timeout`` indicates a maximum life for the lock. |
| 760 | + By default, it will remain locked until release() is called. |
| 761 | +
|
| 762 | + ``sleep`` indicates the amount of time to sleep per loop iteration |
| 763 | + when the lock is in blocking mode and another client is currently |
| 764 | + holding the lock. |
| 765 | +
|
| 766 | + ``blocking_timeout`` indicates the maximum amount of time in seconds to |
| 767 | + spend trying to acquire the lock. A value of ``None`` indicates |
| 768 | + continue trying forever. ``blocking_timeout`` can be specified as a |
| 769 | + float or integer, both representing the number of seconds to wait. |
| 770 | +
|
| 771 | + ``lock_class`` forces the specified lock implementation. Note that as |
| 772 | + of redis-py 3.0, the only lock class we implement is ``Lock`` (which is |
| 773 | + a Lua-based lock). So, it's unlikely you'll need this parameter, unless |
| 774 | + you have created your own custom lock class. |
| 775 | +
|
| 776 | + ``thread_local`` indicates whether the lock token is placed in |
| 777 | + thread-local storage. By default, the token is placed in thread local |
| 778 | + storage so that a thread only sees its token, not a token set by |
| 779 | + another thread. Consider the following timeline: |
| 780 | +
|
| 781 | + time: 0, thread-1 acquires `my-lock`, with a timeout of 5 seconds. |
| 782 | + thread-1 sets the token to "abc" |
| 783 | + time: 1, thread-2 blocks trying to acquire `my-lock` using the |
| 784 | + Lock instance. |
| 785 | + time: 5, thread-1 has not yet completed. redis expires the lock |
| 786 | + key. |
| 787 | + time: 5, thread-2 acquired `my-lock` now that it's available. |
| 788 | + thread-2 sets the token to "xyz" |
| 789 | + time: 6, thread-1 finishes its work and calls release(). if the |
| 790 | + token is *not* stored in thread local storage, then |
| 791 | + thread-1 would see the token value as "xyz" and would be |
| 792 | + able to successfully release the thread-2's lock. |
| 793 | +
|
| 794 | + In some use cases it's necessary to disable thread local storage. For |
| 795 | + example, if you have code where one thread acquires a lock and passes |
| 796 | + that lock instance to a worker thread to release later. If thread |
| 797 | + local storage isn't disabled in this case, the worker thread won't see |
| 798 | + the token set by the thread that acquired the lock. Our assumption |
| 799 | + is that these cases aren't common and as such default to using |
| 800 | + thread local storage.""" |
| 801 | + if lock_class is None: |
| 802 | + lock_class = Lock |
| 803 | + return lock_class( |
| 804 | + self, |
| 805 | + name, |
| 806 | + timeout=timeout, |
| 807 | + sleep=sleep, |
| 808 | + blocking_timeout=blocking_timeout, |
| 809 | + thread_local=thread_local, |
| 810 | + ) |
| 811 | + |
745 | 812 | def _determine_nodes(self, *args, **kwargs):
|
746 | 813 | command = args[0]
|
747 | 814 | nodes_flag = kwargs.pop("nodes_flag", None)
|
|
0 commit comments