Skip to content

Commit 357c62c

Browse files
authored
Merge pull request #973 from pganssle/strong_cache_race
Lock around strong cache updates
2 parents 2e36ff7 + 6e92f21 commit 357c62c

2 files changed

Lines changed: 19 additions & 11 deletions

File tree

changelog.d/973.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed a race condition in the ``tzoffset`` and ``tzstr`` "strong" caches on Python 2.7. Reported by @kainjow (gh issue #901).

dateutil/tz/_factories.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import weakref
33
from collections import OrderedDict
44

5+
from six.moves import _thread
6+
57

68
class _TzSingleton(type):
79
def __init__(cls, *args, **kwargs):
@@ -26,6 +28,8 @@ def __init__(cls, *args, **kwargs):
2628
cls.__strong_cache = OrderedDict()
2729
cls.__strong_cache_size = 8
2830

31+
cls._cache_lock = _thread.allocate_lock()
32+
2933
def __call__(cls, name, offset):
3034
if isinstance(offset, timedelta):
3135
key = (name, offset.total_seconds())
@@ -37,12 +41,13 @@ def __call__(cls, name, offset):
3741
instance = cls.__instances.setdefault(key,
3842
cls.instance(name, offset))
3943

40-
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
44+
# This lock may not be necessary in Python 3. See GH issue #901
45+
with cls._cache_lock:
46+
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
4147

42-
# Remove an item if the strong cache is overpopulated
43-
# TODO: Maybe this should be under a lock?
44-
if len(cls.__strong_cache) > cls.__strong_cache_size:
45-
cls.__strong_cache.popitem(last=False)
48+
# Remove an item if the strong cache is overpopulated
49+
if len(cls.__strong_cache) > cls.__strong_cache_size:
50+
cls.__strong_cache.popitem(last=False)
4651

4752
return instance
4853

@@ -53,6 +58,8 @@ def __init__(cls, *args, **kwargs):
5358
cls.__strong_cache = OrderedDict()
5459
cls.__strong_cache_size = 8
5560

61+
cls.__cache_lock = _thread.allocate_lock()
62+
5663
def __call__(cls, s, posix_offset=False):
5764
key = (s, posix_offset)
5865
instance = cls.__instances.get(key, None)
@@ -61,13 +68,13 @@ def __call__(cls, s, posix_offset=False):
6168
instance = cls.__instances.setdefault(key,
6269
cls.instance(s, posix_offset))
6370

64-
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
65-
71+
# This lock may not be necessary in Python 3. See GH issue #901
72+
with cls.__cache_lock:
73+
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
6674

67-
# Remove an item if the strong cache is overpopulated
68-
# TODO: Maybe this should be under a lock?
69-
if len(cls.__strong_cache) > cls.__strong_cache_size:
70-
cls.__strong_cache.popitem(last=False)
75+
# Remove an item if the strong cache is overpopulated
76+
if len(cls.__strong_cache) > cls.__strong_cache_size:
77+
cls.__strong_cache.popitem(last=False)
7178

7279
return instance
7380

0 commit comments

Comments
 (0)