a69a26852e
* Cut down unnecessary logging * fix format etc * fix checks * fix tests
97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
import asyncio
|
|
from enum import IntEnum
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
|
|
|
|
class RequestPriority(IntEnum):
|
|
USER_INITIATED = 0
|
|
IMAGE_FETCH = 1
|
|
PREFETCH_VISIBLE = 2
|
|
BACKGROUND_SYNC = 3
|
|
OPPORTUNISTIC = 4
|
|
|
|
|
|
class PriorityQueueManager:
|
|
_instance: Optional['PriorityQueueManager'] = None
|
|
|
|
def __new__(cls):
|
|
if cls._instance is None:
|
|
cls._instance = super().__new__(cls)
|
|
cls._instance._initialized = False
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
if self._initialized:
|
|
return
|
|
|
|
self._user_semaphore = asyncio.Semaphore(20)
|
|
self._image_semaphore = asyncio.Semaphore(10)
|
|
self._background_semaphore = asyncio.Semaphore(5)
|
|
self._user_activity_flag = False
|
|
self._user_activity_timestamp = 0.0
|
|
self._user_activity_timeout = 2.0
|
|
self._user_activity_event = asyncio.Event()
|
|
self._background_waiters = 0
|
|
self._initialized = True
|
|
|
|
async def acquire_slot(self, priority: RequestPriority) -> asyncio.Semaphore:
|
|
if priority == RequestPriority.USER_INITIATED:
|
|
self._mark_user_activity()
|
|
return self._user_semaphore
|
|
elif priority == RequestPriority.IMAGE_FETCH:
|
|
return self._image_semaphore
|
|
else:
|
|
await self._wait_for_user_inactivity()
|
|
return self._background_semaphore
|
|
|
|
def _mark_user_activity(self):
|
|
self._user_activity_flag = True
|
|
self._user_activity_timestamp = datetime.now().timestamp()
|
|
self._user_activity_event.clear()
|
|
|
|
async def _wait_for_user_inactivity(self):
|
|
self._background_waiters += 1
|
|
try:
|
|
while self._user_activity_flag:
|
|
current = datetime.now().timestamp()
|
|
elapsed = current - self._user_activity_timestamp
|
|
|
|
if elapsed >= self._user_activity_timeout:
|
|
self._user_activity_flag = False
|
|
self._user_activity_event.set()
|
|
break
|
|
|
|
wait_time = self._user_activity_timeout - elapsed
|
|
try:
|
|
await asyncio.wait_for(
|
|
self._user_activity_event.wait(),
|
|
timeout=wait_time + 0.1
|
|
)
|
|
except asyncio.TimeoutError:
|
|
pass
|
|
finally:
|
|
self._background_waiters -= 1
|
|
|
|
def mark_user_activity(self):
|
|
self._mark_user_activity()
|
|
|
|
def is_user_active(self) -> bool:
|
|
current = datetime.now().timestamp()
|
|
if current - self._user_activity_timestamp > self._user_activity_timeout:
|
|
self._user_activity_flag = False
|
|
return self._user_activity_flag
|
|
|
|
def get_stats(self) -> dict:
|
|
return {
|
|
'user_slots_available': self._user_semaphore._value,
|
|
'image_slots_available': self._image_semaphore._value,
|
|
'background_slots_available': self._background_semaphore._value,
|
|
'user_active': self.is_user_active(),
|
|
'background_waiters': self._background_waiters
|
|
}
|
|
|
|
|
|
def get_priority_queue() -> PriorityQueueManager:
|
|
return PriorityQueueManager()
|