diff --git a/CHANGES.d/20240416_113411_elikowa_batou_424_symlink_and_cleanup_add_systemd_run_task.md b/CHANGES.d/20240416_113411_elikowa_batou_424_symlink_and_cleanup_add_systemd_run_task.md new file mode 100644 index 0000000..c7bf830 --- /dev/null +++ b/CHANGES.d/20240416_113411_elikowa_batou_424_symlink_and_cleanup_add_systemd_run_task.md @@ -0,0 +1,6 @@ + +Add systemd-run async cleanup option for SymlinkAndCleanup removals diff --git a/src/batou_ext/file.py b/src/batou_ext/file.py index fa0e69f..fe590ed 100644 --- a/src/batou_ext/file.py +++ b/src/batou_ext/file.py @@ -2,6 +2,7 @@ import os import os.path import shutil +import subprocess import urllib.parse import batou @@ -25,6 +26,8 @@ class SymlinkAndCleanup(batou.component.Component): prefix = None + use_systemd_run_async_cleanup = False + def configure(self): self._current_link = ( f"{self.prefix}-current" if self.prefix else "current" @@ -97,13 +100,40 @@ def update(self): if current: batou.output.annotate("last -> {}".format(current)) os.symlink(current, self._last_link) - for el in self._list_removals(): - batou.output.annotate("Removing: {}".format(el)) + candidates = self._list_removals() + if self.use_systemd_run_async_cleanup: + # spawns a IOPS limited systemd-run cleanup job try: - if os.path.isdir(el): - shutil.rmtree(el) - else: - os.remove(el) - except OSError as e: - batou.output.error(f'Failed to remove "{el}": {e.strerror}') - pass + batou.output.annotate( + "Removing using systemd-run: {}".format(candidates) + ) + rm_cmd = [ + "rm", + "-rf", + *candidates, # consider: ARG_MAX is limited + ] + subprocess.run( + [ + "systemd-run", + "--unit", + f"batou-cleanup-{self.prefix}", + "--property=IOReadIOPSMax=100", + "--property=IOWriteIOPSMax=100", + *rm_cmd, + ], + check=True, + ) + except subprocess.CalledProcessError as e: + batou.output.error(f"Failed to remove: {e}") + else: + for el in candidates: + batou.output.annotate("Removing: {}".format(el)) + try: + if os.path.isdir(el): + shutil.rmtree(el) + else: + os.remove(el) + except OSError as e: + batou.output.error( + f'Failed to remove "{el}": {e.strerror}' + )