diff options
author | Maxim Devaev <[email protected]> | 2024-10-28 10:46:12 +0200 |
---|---|---|
committer | Maxim Devaev <[email protected]> | 2024-10-28 10:46:12 +0200 |
commit | 60f413c1f4c5d2d69df03a2eea01eccf63c76976 (patch) | |
tree | ca108c2e3fa39d796a9b32a00646c400136a2316 | |
parent | a84242c9bc5215c7230dd9552cbbeec786060ec3 (diff) |
refactoring
-rw-r--r-- | kvmd/plugins/msd/__init__.py | 12 | ||||
-rw-r--r-- | kvmd/plugins/msd/otg/__init__.py | 187 |
2 files changed, 111 insertions, 88 deletions
diff --git a/kvmd/plugins/msd/__init__.py b/kvmd/plugins/msd/__init__.py index 11cd8be5..8b5fa8e2 100644 --- a/kvmd/plugins/msd/__init__.py +++ b/kvmd/plugins/msd/__init__.py @@ -266,9 +266,6 @@ class MsdFileWriter(BaseMsdWriter): # pylint: disable=too-many-instance-attribu return self.__written - def is_complete(self) -> bool: - return (self.__written >= self.__file_size) - async def open(self) -> "MsdFileWriter": assert self.__file is None get_logger(1).info("Writing %r image (%d bytes) to MSD ...", self.__name, self.__file_size) @@ -276,6 +273,10 @@ class MsdFileWriter(BaseMsdWriter): # pylint: disable=too-many-instance-attribu self.__file = await aiofiles.open(self.__path, mode="w+b", buffering=0) # type: ignore return self + async def finish(self) -> bool: + await self.__sync() + return (self.__written >= self.__file_size) + async def close(self) -> None: assert self.__file is not None logger = get_logger() @@ -288,10 +289,7 @@ class MsdFileWriter(BaseMsdWriter): # pylint: disable=too-many-instance-attribu else: # written > size (log, result) = (logger.warning, "OVERFLOW") log("Written %d of %d bytes to MSD image %r: %s", self.__written, self.__file_size, self.__name, result) - try: - await self.__sync() - finally: - await self.__file.close() # type: ignore + await self.__file.close() # type: ignore except Exception: logger.exception("Can't close image writer") diff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py index 203e0b95..99de95e0 100644 --- a/kvmd/plugins/msd/otg/__init__.py +++ b/kvmd/plugins/msd/otg/__init__.py @@ -25,7 +25,6 @@ import contextlib import dataclasses import functools import copy -import time from typing import AsyncGenerator @@ -96,15 +95,17 @@ class _State: @contextlib.asynccontextmanager async def busy(self, check_online: bool=True) -> AsyncGenerator[None, None]: - with self._region: - async with self._lock: - self.__notifier.notify() - if check_online: - if self.vd is None: - raise MsdOfflineError() - assert self.storage - yield - self.__notifier.notify() + try: + with self._region: + async with self._lock: + self.__notifier.notify() + if check_online: + if self.vd is None: + raise MsdOfflineError() + assert self.storage + yield + finally: + self.__notifier.notify() def is_busy(self) -> bool: return self._region.is_busy() @@ -143,7 +144,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes logger = get_logger(0) logger.info("Using OTG gadget %r as MSD", gadget) - aiotools.run_sync(self.__reload_state(notify=False)) + aiotools.run_sync(self.__unsafe_reload_state()) @classmethod def get_plugin_options(cls) -> dict: @@ -163,13 +164,15 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes }, } + # ===== + async def get_state(self) -> dict: async with self.__state._lock: # pylint: disable=protected-access storage: (dict | None) = None if self.__state.storage: if self.__writer: # При загрузке файла показываем актуальную статистику вручную - await self.__storage.reload_parts_info() + await aiotools.shield_fg(self.__storage.reload_parts_info()) storage = dataclasses.asdict(self.__state.storage) for name in list(storage["images"]): @@ -212,9 +215,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes prev = copy.deepcopy(new) yield new - async def systask(self) -> None: - await self.__watch_inotify() - @aiotools.atomic_fg async def reset(self) -> None: async with self.__state.busy(check_online=False): @@ -226,11 +226,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes except Exception: get_logger(0).exception("Can't reset MSD properly") - @aiotools.atomic_fg - async def cleanup(self) -> None: - await self.__close_reader() - await self.__close_writer() - # ===== @aiotools.atomic_fg @@ -297,6 +292,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes async with self.__state._lock: # pylint: disable=protected-access self.__notifier.notify() self.__STATE_check_disconnected() + image = await self.__STATE_get_storage_image(name) self.__reader = await MsdFileReader( notifier=self.__notifier, @@ -304,7 +300,10 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes path=image.path, chunk_size=self.__read_chunk_size, ).open() + + self.__notifier.notify() yield self.__reader + finally: await aiotools.shield_fg(self.__close_reader()) finally: @@ -312,18 +311,37 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes @contextlib.asynccontextmanager async def write_image(self, name: str, size: int, remove_incomplete: (bool | None)) -> AsyncGenerator[MsdFileWriter, None]: + image: (Image | None) = None + complete = False + + async def finish_writing() -> None: + # Делаем под блокировкой, чтобы эвент айнотифи не был обработан + # до того, как мы не закончим все процедуры. + async with self.__state._lock: # pylint: disable=protected-access + try: + if image: + await image.set_complete(complete) + finally: + try: + if image and remove_incomplete and not complete: + await image.remove(fatal=False) + finally: + try: + await self.__close_writer() + finally: + if image: + await image.remount_rw(False, fatal=False) + try: with self.__state._region: # pylint: disable=protected-access - image: (Image | None) = None try: async with self.__state._lock: # pylint: disable=protected-access self.__notifier.notify() self.__STATE_check_disconnected() - image = await self.__STORAGE_create_new_image(name) + image = await self.__STORAGE_create_new_image(name) await image.remount_rw(True) await image.set_complete(False) - self.__writer = await MsdFileWriter( notifier=self.__notifier, name=image.name, @@ -335,22 +353,12 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes self.__notifier.notify() yield self.__writer - await image.set_complete(self.__writer.is_complete()) + complete = await self.__writer.finish() finally: - try: - if image and remove_incomplete and self.__writer and not self.__writer.is_complete(): - await image.remove(fatal=False) - finally: - try: - await aiotools.shield_fg(self.__close_writer()) - finally: - if image: - await aiotools.shield_fg(image.remount_rw(False, fatal=False)) + await aiotools.shield_fg(finish_writing()) finally: - # Между закрытием файла и эвентом айнотифи состояние может быть не обновлено, - # так что форсим обновление вручную, чтобы получить актуальное состояние. - await aiotools.shield_fg(self.__reload_state()) + self.__notifier.notify() @aiotools.atomic_fg async def remove(self, name: str) -> None: @@ -400,17 +408,26 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes async def __close_reader(self) -> None: if self.__reader: - await self.__reader.close() - self.__reader = None + try: + await self.__reader.close() + finally: + self.__reader = None async def __close_writer(self) -> None: if self.__writer: - await self.__writer.close() - self.__writer = None + try: + await self.__writer.close() + finally: + self.__writer = None # ===== - async def __watch_inotify(self) -> None: + @aiotools.atomic_fg + async def cleanup(self) -> None: + await self.__close_reader() + await self.__close_writer() + + async def systask(self) -> None: logger = get_logger(0) while True: try: @@ -425,7 +442,11 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes await inotify.watch_all_modify(*self.__storage.get_watchable_paths()) await inotify.watch_all_modify(*self.__drive.get_watchable_paths()) - # После установки вотчеров еще раз проверяем стейт, чтобы ничего не потерять + # После установки вотчеров еще раз проверяем стейт, + # чтобы не потерять состояние привода. + # Из-за гонки между первым релоадом и установкой вотчеров, + # мы можем потерять какие-то каталоги стораджа, но это допустимо, + # так как всегда есть ручной перезапуск. await self.__reload_state() while self.__state.vd: # Если живы после предыдущей проверки @@ -445,54 +466,58 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes await self.__reload_state() except Exception: logger.exception("Unexpected MSD watcher error") - time.sleep(1) + await asyncio.sleep(1) - async def __reload_state(self, notify: bool=True) -> None: - logger = get_logger(0) + async def __reload_state(self) -> None: async with self.__state._lock: # pylint: disable=protected-access - try: - path = self.__drive.get_image_path() - drive_state = _DriveState( - image=((await self.__storage.make_image_by_path(path)) if path else None), - cdrom=self.__drive.get_cdrom_flag(), - rw=self.__drive.get_rw_flag(), - ) - - await self.__storage.reload() - - if self.__state.vd is None and drive_state.image is None: - # Если только что включились и образ не подключен - попробовать - # перемонтировать хранилище (и создать images и meta). - logger.info("Probing to remount storage ...") - await self.__storage.remount_rw(True) - await self.__storage.remount_rw(False) - await self.__setup_initial() + await self.__unsafe_reload_state() + self.__notifier.notify() - except Exception: - logger.exception("Error while reloading MSD state; switching to offline") - self.__state.storage = None - self.__state.vd = None + # ===== Don't call this directly ==== + async def __unsafe_reload_state(self) -> None: + logger = get_logger(0) + try: + path = self.__drive.get_image_path() + drive_state = _DriveState( + image=((await self.__storage.make_image_by_path(path)) if path else None), + cdrom=self.__drive.get_cdrom_flag(), + rw=self.__drive.get_rw_flag(), + ) + + await self.__storage.reload() + + if self.__state.vd is None and drive_state.image is None: + # Если только что включились и образ не подключен - попробовать + # перемонтировать хранилище (и создать images и meta). + logger.info("Probing to remount storage ...") + await self.__storage.remount_rw(True) + await self.__storage.remount_rw(False) + await self.__unsafe_setup_initial() + + except Exception: + logger.exception("Error while reloading MSD state; switching to offline") + self.__state.storage = None + self.__state.vd = None + + else: + self.__state.storage = self.__storage + if drive_state.image: + # При подключенном образе виртуальный стейт заменяется реальным + self.__state.vd = _VirtualDriveState.from_drive_state(drive_state) else: - self.__state.storage = self.__storage - if drive_state.image: - # При подключенном образе виртуальный стейт заменяется реальным + if self.__state.vd is None: + # Если раньше MSD был отключен self.__state.vd = _VirtualDriveState.from_drive_state(drive_state) - else: - if self.__state.vd is None: - # Если раньше MSD был отключен - self.__state.vd = _VirtualDriveState.from_drive_state(drive_state) - image = self.__state.vd.image - if image and (not image.in_storage or not (await image.exists())): - # Если только что отключили ручной образ вне хранилища или ранее выбранный образ был удален - self.__state.vd.image = None + image = self.__state.vd.image + if image and (not image.in_storage or not (await image.exists())): + # Если только что отключили ручной образ вне хранилища или ранее выбранный образ был удален + self.__state.vd.image = None - self.__state.vd.connected = False - if notify: - self.__notifier.notify() + self.__state.vd.connected = False - async def __setup_initial(self) -> None: + async def __unsafe_setup_initial(self) -> None: if self.__initial_image: logger = get_logger(0) image = await self.__storage.make_image_by_name(self.__initial_image) |