Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mosquito committed Mar 10, 2021
1 parent 865eb2f commit 137cc45
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 99 deletions.
78 changes: 17 additions & 61 deletions aiomisc/cache/base.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,29 @@
import asyncio
import threading
from abc import ABC, abstractmethod
from typing import Any, Union, Hashable, Dict, Optional

from llist import dllist, dllistnode
from typing import Any, Hashable


class CacheBase(ABC):
def __init__(self, max_size: int = 0):
self._max_size: int = max_size
self._loop: Optional[asyncio.AbstractEventLoop] = None
self.usages: dllist = dllist()
self.cache: Dict[Hashable, Any] = dict()
self.lock = threading.RLock()

@property
def is_overflow(self) -> bool:
if self._max_size == 0:
return False

if self._max_size < len(self.usages):
return True
__slots__ = "max_size",

return False

@property
def loop(self) -> asyncio.AbstractEventLoop:
if self._loop is None:
self._loop = asyncio.get_event_loop()
return self._loop
def __init__(self, max_size: int = 0):
self.max_size = max_size

@abstractmethod
def _on_set(self, node: dllistnode) -> None:
pass

def _on_expires(self, node: dllistnode) -> None:
pass
def get(self, key: Hashable):
raise NotImplementedError

@abstractmethod
def _on_get(self, node: dllistnode) -> Any:
pass

def __contains__(self, item: Hashable) -> bool:
return item in self.cache
def remove(self, key: Hashable):
raise NotImplementedError

def get(self, item: Hashable) -> Any:
with self.lock:
node: dllistnode = self.cache[item]
self.loop.call_soon(self._on_get, node)
return node.value[1]

def expire(self, node: dllistnode):
with self.lock:
item, value = node.value
node: Optional[dllistnode] = self.cache.pop(item, None)

if node is None:
return

self.loop.call_soon(self._on_expires, node)
self.usages.remove(node)

def set(self, item: Hashable, value: Any,
expiration: Union[int, float] = None) -> None:
with self.lock:
node: dllistnode = self.usages.append((item, value))
self.cache[item] = node
@abstractmethod
def set(self, key: Hashable, value: Any):
raise NotImplementedError

self.loop.call_soon(self._on_set, node)
@abstractmethod
def __contains__(self, key: Hashable):
raise NotImplementedError

if expiration is not None:
self.loop.call_later(expiration, self.expire, node)
@abstractmethod
def __len__(self):
raise NotImplementedError
100 changes: 78 additions & 22 deletions aiomisc/cache/lfu.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from dataclasses import dataclass
from threading import RLock
from typing import Any, Hashable, Optional, Set
from typing import Any, Hashable, Optional, Dict, Set
from aiomisc.cache.base import CacheBase


# noinspection PyShadowingBuiltins
class Node:
__slots__ = ('prev', 'next', 'items')
__slots__ = 'prev', 'next', 'items'

def __init__(self, prev: "Node" = None, next: "Node" = None,
items: Optional[Set["Item"]] = None):
Expand All @@ -13,14 +14,16 @@ def __init__(self, prev: "Node" = None, next: "Node" = None,
self.items = items or set()


@dataclass(frozen=True)
class Item:
node: Node
key: Hashable
value: Any
__slots__ = 'node', 'key', 'value'

def __init__(self, node: Node, key: Hashable, value: Any):
self.node: Node = node
self.key: Hashable = key
self.value: Any = value

class LFUCache:

class LFUCache(CacheBase):
"""
LFU cache implementation
Expand All @@ -29,20 +32,37 @@ class LFUCache:
>>> assert "foo" in lfu
>>> lfu.get('foo')
'bar'
>>> lfu.remove('foo')
>>> assert "foo" not in lfu
>>> lfu.get("foo")
Traceback (most recent call last):
...
KeyError: 'foo'
>>> lfu.remove("foo")
>>> lfu.set("bar", "foo")
>>> lfu.set("spam", "egg")
>>> lfu.set("foo", "bar")
>>> lfu.get("foo")
'bar'
>>> lfu.get("spam")
'egg'
>>> assert len(lfu) == 3
>>> lfu.set("egg", "spam")
>>> assert len(lfu) == 3
"""

def __init__(self, max_size: int = 0):
self.cache = dict()
self.usages: Node = Node(prev=None, next=None, items=set())
self.lock = RLock()
self.size = 0
self.max_size = max_size
__slots__ = "cache", "usages", "lock"

def __init__(self, max_size: int):
super().__init__(max_size)
self.cache: Dict[Hashable, Item] = dict()
self.usages: Node = Node(prev=None, next=None)
self.lock: RLock = RLock()

def _create_node(self) -> Node:
node = Node(prev=self.usages, next=None, items=set())
node = Node(prev=self.usages, next=None)
self.usages.next = node
return node

Expand All @@ -55,11 +75,7 @@ def _update_usage(self, item: Item):
new_node = self._create_node()

old_node.items.remove(item)
item = Item(
node=new_node,
key=item.key,
value=item.value,
)
item.node = new_node
new_node.items.add(item)
self.cache[item.key] = item

Expand All @@ -69,11 +85,29 @@ def _update_usage(self, item: Item):
self.usages = new_node
self.usages.prev = None

def _remove_item(self, item: Item):
with self.lock:
if item.key in self.cache:
self.cache.pop(item.key, None)

if item in item.node.items:
item.node.items.remove(item)

item.node = None

def get(self, key: Hashable):
item: Item = self.cache[key]
self._update_usage(item)
return item.value

def remove(self, key: Hashable):
with self.lock:
item: Optional[Item] = self.cache.pop(key, None)
if item is None:
return

self._remove_item(item)

def set(self, key: Hashable, value: Any):
with self.lock:
node: Optional[Node] = self.usages
Expand All @@ -85,8 +119,30 @@ def set(self, key: Hashable, value: Any):
node.items.add(item)
self.cache[key] = item

def __contains__(self, key) -> Any:
if self._is_overflow():
self._on_overflow()

def _on_overflow(self):
with self.lock:
while self._is_overflow():
if not self.usages.items:
if self.usages.next is not None:
self.usages.next.prev = None
self.usages = self.usages.next
else:
self.usages = Node(prev=None, next=None)

item = self.usages.items.pop()
self._remove_item(item)

def _is_overflow(self) -> bool:
return len(self.cache) > self.max_size

def __contains__(self, key: Hashable) -> Any:
if key in self.cache:
self._update_usage(self.cache[key])
return True
return False

def __len__(self):
return len(self.cache)

0 comments on commit 137cc45

Please sign in to comment.