Skip to content

Commit 1f01adf

Browse files
authored
remove awaitable, future is enough and faster (#19)
1 parent d85a594 commit 1f01adf

File tree

2 files changed

+29
-60
lines changed

2 files changed

+29
-60
lines changed

cacheme/core.py

+29-24
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from cacheme.interfaces import DoorKeeper, Metrics, Serializer, Node
2424
from cacheme.models import (
2525
Cache,
26-
CachedAwaitable,
2726
DynamicNode,
2827
Fetcher,
2928
_add_node,
@@ -46,9 +45,7 @@ def __init__(self):
4645
self.value = None
4746

4847

49-
# temp storage for futures which are loading from source now,
50-
# will removed automatically when loading done
51-
_awaits: Dict[str, CachedAwaitable] = {}
48+
_awaits: Dict[str, Future] = {}
5249

5350

5451
def _awaits_len():
@@ -100,14 +97,25 @@ async def get(node: Node, load_fn=None):
10097
# remote storages are slow and asynchronous, use tmp cached awaitables to avoid thundering herd
10198
if result is sentinel:
10299
key = node.full_key()
103-
awaitable = _awaits.get(key, None)
104-
if awaitable is None:
105-
awaitable = CachedAwaitable(
106-
_load_from_caches(node, remote_caches, miss, load_fn), metrics
107-
)
108-
_awaits[node.full_key()] = awaitable
109-
# wait
110-
result = await awaitable
100+
future = _awaits.get(key, None)
101+
if future is None:
102+
metrics._miss_count += 1
103+
future = Future()
104+
_awaits[node.full_key()] = future
105+
now = time_ns()
106+
try:
107+
result = await _load_from_caches(node, remote_caches, miss, load_fn)
108+
except Exception as e:
109+
metrics._load_failure_count += 1
110+
metrics._total_load_time += time_ns() - now
111+
_awaits.pop(node.full_key(), None)
112+
raise (e)
113+
metrics._load_success_count += 1
114+
metrics._total_load_time += time_ns() - now
115+
future.set_result(result)
116+
else:
117+
metrics._hit_count += 1
118+
result = await future
111119

112120
# fill missing caches
113121
for cache in miss:
@@ -180,35 +188,32 @@ async def get_all(nodes: Sequence[Node[R]]) -> List[R]:
180188
fetch: Dict[str, Node] = {} # missing nodes, need to load from source
181189
if len(pending) > 0:
182190
wait: List[
183-
Tuple[str, CachedAwaitable]
191+
Tuple[str, Future]
184192
] = [] # nodes already loading by others, only need to wait here
185193
for node in pending.values():
186-
awaitable = _awaits.get(node.full_key(), None)
187-
if awaitable is None:
194+
future = _awaits.get(node.full_key(), None)
195+
if future is None:
188196
fetch[node.full_key()] = node
189197
else:
190-
wait.append((node.full_key(), awaitable))
198+
wait.append((node.full_key(), future))
191199

192200
# update metrics
193201
metrics._miss_count += len(fetch)
194202
metrics._hit_count += len(nodes) - len(fetch)
195203

196204
if len(fetch) > 0:
197205
fetcher = Fetcher()
198-
aws: List[Tuple[str, CachedAwaitable]] = []
206+
aws: List[Tuple[str, Future]] = []
199207
for key, node in fetch.items():
200-
awaitable = CachedAwaitable(Future(), metrics)
201-
# set event directly
202-
awaitable.event = Event()
203-
_awaits[key] = awaitable
204-
aws.append((key, awaitable))
208+
future = Future()
209+
_awaits[key] = future
210+
aws.append((key, future))
205211
fetcher.data = await _get_multi(
206212
nodes[0], remote_caches, fetch, missing, metrics
207213
)
208214
# load done, set all events and results
209215
for aw in aws:
210-
cast(Event, aw[1].event).set()
211-
aw[1].result = fetcher.data[aw[0]]
216+
aw[1].set_result(fetcher.data[aw[0]])
212217
for ks, vs in fetcher.data.items():
213218
results[ks] = vs
214219
for w in wait:

cacheme/models.py

-36
Original file line numberDiff line numberDiff line change
@@ -135,42 +135,6 @@ def key(self) -> str:
135135
return self.key_str
136136

137137

138-
# https://github.com/python/cpython/issues/90780
139-
# use event to protect from thundering herd
140-
class CachedAwaitable:
141-
def __init__(self, awaitable, metrics: Metrics):
142-
self.awaitable = awaitable
143-
self.event: Optional[asyncio.Event] = None
144-
self.result = sentinel
145-
self.metrics = metrics
146-
147-
def __await__(self):
148-
if self.result is not sentinel:
149-
self.metrics._hit_count += 1
150-
return self.result
151-
152-
if self.event is None:
153-
self.metrics._miss_count += 1
154-
self.event = asyncio.Event()
155-
now = time_ns()
156-
try:
157-
result = yield from self.awaitable.__await__()
158-
except Exception as e:
159-
self.metrics._load_failure_count += 1
160-
self.metrics._total_load_time += time_ns() - now
161-
raise (e)
162-
self.metrics._load_success_count += 1
163-
self.metrics._total_load_time += time_ns() - now
164-
self.result = result
165-
self.event.set()
166-
self.event = None
167-
return result
168-
else:
169-
self.metrics._hit_count += 1
170-
yield from self.event.wait().__await__()
171-
return self.result
172-
173-
174138
class Fetcher:
175139
def __init__(self):
176140
self.data: Dict[str, Any] = {}

0 commit comments

Comments
 (0)