A semaphore used to coordinate sequential resource access. This class is similar to the stdlib BoundedSemaphore: * It's initialized with a count. * Each call to ``acquire()`` decrements the counter. * If the count is at zero, then ``acquire()`` will either block until the cou
| 652 | |
| 653 | |
| 654 | class SlidingWindowSemaphore(TaskSemaphore): |
| 655 | """A semaphore used to coordinate sequential resource access. |
| 656 | |
| 657 | This class is similar to the stdlib BoundedSemaphore: |
| 658 | |
| 659 | * It's initialized with a count. |
| 660 | * Each call to ``acquire()`` decrements the counter. |
| 661 | * If the count is at zero, then ``acquire()`` will either block until the |
| 662 | count increases, or if ``blocking=False``, then it will raise |
| 663 | a NoResourcesAvailable exception indicating that it failed to acquire the |
| 664 | semaphore. |
| 665 | |
| 666 | The main difference is that this semaphore is used to limit |
| 667 | access to a resource that requires sequential access. For example, |
| 668 | if I want to access resource R that has 20 subresources R_0 - R_19, |
| 669 | this semaphore can also enforce that you only have a max range of |
| 670 | 10 at any given point in time. You must also specify a tag name |
| 671 | when you acquire the semaphore. The sliding window semantics apply |
| 672 | on a per tag basis. The internal count will only be incremented |
| 673 | when the minimum sequence number for a tag is released. |
| 674 | |
| 675 | """ |
| 676 | |
| 677 | def __init__(self, count): |
| 678 | self._count = count |
| 679 | # Dict[tag, next_sequence_number]. |
| 680 | self._tag_sequences = defaultdict(int) |
| 681 | self._lowest_sequence = {} |
| 682 | self._lock = threading.Lock() |
| 683 | self._condition = threading.Condition(self._lock) |
| 684 | # Dict[tag, List[sequence_number]] |
| 685 | self._pending_release = {} |
| 686 | |
| 687 | def current_count(self): |
| 688 | with self._lock: |
| 689 | return self._count |
| 690 | |
| 691 | def acquire(self, tag, blocking=True): |
| 692 | logger.debug("Acquiring %s", tag) |
| 693 | self._condition.acquire() |
| 694 | try: |
| 695 | if self._count == 0: |
| 696 | if not blocking: |
| 697 | raise NoResourcesAvailable(f"Cannot acquire tag '{tag}'") |
| 698 | else: |
| 699 | while self._count == 0: |
| 700 | self._condition.wait() |
| 701 | # self._count is no longer zero. |
| 702 | # First, check if this is the first time we're seeing this tag. |
| 703 | sequence_number = self._tag_sequences[tag] |
| 704 | if sequence_number == 0: |
| 705 | # First time seeing the tag, so record we're at 0. |
| 706 | self._lowest_sequence[tag] = sequence_number |
| 707 | self._tag_sequences[tag] += 1 |
| 708 | self._count -= 1 |
| 709 | return sequence_number |
| 710 | finally: |
| 711 | self._condition.release() |
no outgoing calls