Skip to content

Fix lock not released in _iter_cached causing potential deadlock#1455

Open
bysiber wants to merge 1 commit intodateutil:masterfrom
bysiber:fix/rrule-cache-lock-not-released
Open

Fix lock not released in _iter_cached causing potential deadlock#1455
bysiber wants to merge 1 commit intodateutil:masterfrom
bysiber:fix/rrule-cache-lock-not-released

Conversation

@bysiber
Copy link
Copy Markdown

@bysiber bysiber commented Feb 20, 2026

Summary

rrulebase._iter_cached can deadlock because the cache lock is not released on two of the three exit paths from the refill block.

Problem

The lock is acquired at the start of the cache refill section, but both break paths exit the while loop without calling release():

acquire()
if self._cache_complete:
    break                    # ← lock NOT released

try:
    for j in range(10):
        cache.append(advance_iterator(gen))
except StopIteration:
    self._cache_gen = gen = None
    self._cache_complete = True
    break                    # ← lock NOT released

release()                    # only reached on normal iteration path

Path 1: When _cache_complete is True (another thread finished caching while we waited for the lock), break exits without release.

Path 2: When StopIteration is caught (generator exhausted), break exits without release.

Any subsequent thread calling _iter_cached on the same instance will hang indefinitely on acquire().

Reproduction

import threading
from dateutil.rrule import rrule, DAILY
from datetime import datetime

r = rrule(DAILY, count=5, dtstart=datetime(2020, 1, 1), cache=True)

# Exhaust the cached iterator — triggers the StopIteration break path
list(r)

# At this point, the lock is held and never released.
# A second thread trying to iterate the same cached rrule will deadlock.

Fix

Add release() before both break statements to ensure the lock is always released when exiting the refill block.

The cache lock is acquired at the start of the refill block, but both
break paths (cache_complete check and StopIteration handler) exit the
while loop without releasing it. Any subsequent call to _iter_cached
on the same instance from another thread will deadlock on acquire().

Add release() calls before both break statements.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant