import asyncio import logging import threading from collections import deque from collections.abc import AsyncIterator from functools import partial from typing import Callable, TypeVar from uuid import UUID from weakref import finalize from .reader import JournalOpenMode, JournalReader, JournalEntry A = TypeVar("A") R = TypeVar("R") log = logging.getLogger("cysystemd.async_reader") class Base: def __init__(self, loop=None, executor=None): self._executor = executor self._loop = loop or asyncio.get_event_loop() async def _exec(self, func: Callable[[A], R], *args, **kwargs) -> R: # noinspection PyTypeChecker return await self._loop.run_in_executor( self._executor, partial(func, *args, **kwargs) ) class AsyncJournalReader(Base): def __init__(self, executor=None, loop=None): super().__init__(loop=loop, executor=executor) self.__reader = JournalReader() self.__flags = None self.__wait_lock = asyncio.Lock() self.__iterator = None async def wait(self): async with self.__wait_lock: loop = self._loop reader = self.__reader event = asyncio.Event() loop.add_reader(reader.fd, event.set) try: await event.wait() finally: loop.remove_reader(reader.fd) reader.process_events() return True def open(self, flags=JournalOpenMode.CURRENT_USER): self.__flags = flags return self._exec(self.__reader.open, flags=flags) def open_directory(self, path): return self._exec(self.__reader.open_directory, path) def open_files(self, *file_names): return self._exec(self.__reader.open_files, *file_names) @property def data_threshold(self): return self.__reader.data_threshold @data_threshold.setter def data_threshold(self, size): self.__reader.data_threshold = size @property def closed(self): return self.__reader.closed @property def locked(self): return self.__reader.locked @property def idle(self): return self.__reader.idle def seek_head(self): return self._exec(self.__reader.seek_head) def __repr__(self): return "<%s[%s]: %s>" % ( self.__class__.__name__, self.__flags, "closed" if self.closed else "opened", ) @property def fd(self): return self.__reader.fd @property def events(self): return self.__reader.events @property def timeout(self): return self.__reader.timeout def get_catalog(self): return self._exec(self.__reader.get_catalog) def get_catalog_for_message_id(self, message_id): return self._exec( self.__reader.get_catalog_for_message_id, message_id ) def seek_tail(self): return self._exec(self.__reader.seek_tail) def seek_monotonic_usec(self, boot_id: UUID, usec): return self._exec( self.__reader.seek_monotonic_usec, boot_id, usec ) def seek_realtime_usec(self, usec): return self._exec(self.__reader.seek_realtime_usec, usec) def seek_cursor(self, cursor): return self._exec(self.__reader.seek_cursor, cursor) def skip_next(self, skip): return self._exec(self.__reader.skip_next, skip) def previous(self, skip=0): return self._exec(self.__reader.previous, skip) def skip_previous(self, skip): return self._exec(self.__reader.skip_previous, skip) def add_filter(self, rule): return self._exec(self.__reader.add_filter, rule) def clear_filter(self): return self._exec(self.__reader.clear_filter) def next(self, skip=0): return self._exec(self.__reader.next, skip) def __aiter__(self) -> "AsyncReaderIterator": if self.__iterator is not None: self.__iterator.close() self.__iterator = None iterator = AsyncReaderIterator( loop=self._loop, executor=self._executor, reader=self.__reader ) finalize(self, iterator.close) self.__iterator = iterator return iterator class AsyncReaderIterator(Base, AsyncIterator): __slots__ = "reader", "queue", "queue_full", "event", "lock", "closed" QUEUE_SIZE = 2 WRITE_EVENT_WAIT_TIME = 0.1 def __init__(self, *, reader, loop, executor): super().__init__(loop=loop, executor=executor) self.reader = reader self.lock = asyncio.Lock() self.queue = deque() self.read_event = asyncio.Event() self.write_event = threading.Semaphore(self.QUEUE_SIZE) self.close_event = threading.Event() self._loop.create_task(self._exec(self._journal_reader)) def close(self): self.close_event.set() self.__set_read_event() def __del__(self): self.close() def __set_read_event(self): if self._loop.is_closed(): return self._loop.call_soon_threadsafe(self.read_event.set) def _journal_reader(self): try: for item in self.reader: while not self.close_event.is_set(): if self.write_event.acquire( timeout=self.WRITE_EVENT_WAIT_TIME ): break else: return self.queue.append(item) self.__set_read_event() finally: self.close() async def __anext__(self) -> JournalEntry: async with self.lock: if self.close_event.is_set() and len(self.queue) == 0: raise StopAsyncIteration while True: try: item = self.queue.popleft() except IndexError: await self.read_event.wait() self.read_event.clear() continue else: self.write_event.release() return item