From ceb770db8db18a1eaf649c0b438eee4db604e86f Mon Sep 17 00:00:00 2001 From: I-am-PUID-0 <36779668+I-am-PUID-0@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:47:17 -0400 Subject: [PATCH] Version [5.3.1] --- CHANGELOG.md | 7 +++++++ README.md | 8 ++++---- docker-compose.yml | 2 +- main.py | 4 ++-- utils/processes.py | 43 +++++++++++++++++++++++++++++-------------- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 130dc0b..57bfe95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## Version [5.3.1] - 2024-10-15 🚀 + +### Fixed 🛠️ + +- [Issue #59](https://github.com/I-am-PUID-0/DMB/issues/59) Zombie dotnet Processes Accumulating Over Time 🐛 + + ## Version [5.3.0] - 2024-10-03 🚀 ### Added ✨ diff --git a/README.md b/README.md index f646c7c..a092176 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ services: - /home/username/docker/DMB/Riven/data:/riven/backend/data ## Location for Riven backend data - /home/username/docker/DMB/Riven/mnt:/mnt ## Location for Riven symlinks - /home/username/docker/DMB/PostgreSQL/data:/postgres_data ## Location for PostgreSQL database - - /home/username/docker/pgAdmin4/data:/pgadmin/data ## Location for pgAdmin 4 data + - /home/username/docker/DMB/pgAdmin4/data:/pgadmin/data ## Location for pgAdmin 4 data - /home/username/docker/DMB/Zilean/data:/zilean/app/data ## Location for Zilean data environment: - TZ= @@ -65,7 +65,7 @@ services: # network_mode: container:gluetun ## Example to attach to gluetun vpn container if realdebrid blocks IP address ports: - "3000:3000" ## Riven frontend - - "5050:5050" ## pgAdmin 4 + - "5050:5050" ## pgAdmin 4 devices: - /dev/fuse:/dev/fuse:rwm cap_add: @@ -243,10 +243,10 @@ The following table describes the ports used by the container. The mappings are |`8080`| TCP | Riven backend - The API is accessible at the assigned port| |`5432`| TCP | PostgreSQL - The SQL server is accessible at the assigned port| |`5050`| TCP | pgAdmin 4 - A web UI is accessible at the assigned port| +|`8182`| TCP | Zilean - The API and Web Ui (/swagger/index.html) is accessible at the assigned port| |`Random (9001-9999)`| TCP | Zurg - A web UI is accessible at the assigned port| - ## 📂 Data Volumes The following table describes the data volumes used by the container. The mappings @@ -317,7 +317,7 @@ secrets: seerr_address: file: ./path/to/seerr_address.txt zurg_user: - file: ./path/to/zurg_user.txt + file: ./path/to/zurg_user.txt zurg_pass: file: ./path/to/zurg_pass.txt pgadmin_setup_email: diff --git a/docker-compose.yml b/docker-compose.yml index f079829..4107d7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: - /home/username/docker/DMB/Riven/data:/riven/backend/data ## Location for Riven backend data - /home/username/docker/DMB/Riven/mnt:/mnt ## Location for Riven symlinks - /home/username/docker/DMB/PostgreSQL/data:/postgres_data ## Location for PostgreSQL database - - /home/username/docker/pgAdmin4/data:/pgadmin/data ## Location for pgAdmin 4 data + - /home/username/docker/DMB/pgAdmin4/data:/pgadmin/data ## Location for pgAdmin 4 data - /home/username/docker/DMB/Zilean/data:/zilean/app/data ## Location for Zilean data environment: - TZ= diff --git a/main.py b/main.py index 71f6893..e289c7c 100644 --- a/main.py +++ b/main.py @@ -31,7 +31,7 @@ def unmount_all(): else: logger.error(f"Failed to unmount {full_path}: {umount.stderr.strip()}") - processes = ['riven_frontend', 'riven_backend', 'Zilean', 'PostgreSQL', 'Zurg', 'rclone', 'pgAdmin'] + processes = ['riven_frontend', 'riven_backend', 'Zilean', 'PostgreSQL', 'Zurg', 'rclone', 'pgAdmin', 'pgAgent'] for process in processes: stop_process(process) @@ -43,7 +43,7 @@ def unmount_all(): def main(): - version = '5.3.0' + version = '5.3.1' ascii_art = f''' diff --git a/utils/processes.py b/utils/processes.py index 1e6e1de..5ba2d8e 100644 --- a/utils/processes.py +++ b/utils/processes.py @@ -10,6 +10,7 @@ def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super(ProcessHandler, cls).__new__(cls) cls._instance.init_attributes(*args, **kwargs) + signal.signal(signal.SIGCHLD, cls._instance.reap_zombies) return cls._instance def init_attributes(self, logger): @@ -23,6 +24,16 @@ def init_attributes(self, logger): def __init__(self, logger): pass + def reap_zombies(self, signum, frame): + while True: + try: + pid, _ = os.waitpid(-1, os.WNOHANG) + if pid == 0: + break + self.logger.info(f"Reaped zombie process with PID: {pid}") + except ChildProcessError: + break + def start_process(self, process_name, config_dir, command, key_type=None, suppress_logging=False, env=None): try: try: @@ -49,11 +60,11 @@ def preexec_fn(): process_description = f"{process_name}" if isinstance(command, str): - command = shlex.split(command) + command = shlex.split(command) - process_env = os.environ.copy() + process_env = os.environ.copy() if env: - process_env.update(env) + process_env.update(env) if process_name in ["rclone", "poetry_install", "install_poetry", "poetry_env_setup", "PostgreSQL_init", "npm_install", "node_build", "python_env_setup", "install_requirements", "setup_env_and_install", "dotnet_env_restore", "dotnet_publish_api", "dotnet_publish_scraper"]: process = subprocess.Popen( @@ -64,7 +75,7 @@ def preexec_fn(): cwd=config_dir, universal_newlines=True, bufsize=1, - env=process_env + env=process_env ) else: process = subprocess.Popen( @@ -76,14 +87,14 @@ def preexec_fn(): universal_newlines=True, bufsize=1, preexec_fn=preexec_fn, - env=process_env + env=process_env ) if not suppress_logging: self.subprocess_logger = SubprocessLogger(self.logger, f"{process_description}") self.subprocess_logger.start_logging_stdout(process) self.subprocess_logger.start_monitoring_stderr(process, key_type, process_name) - + self.logger.info(f"{process_name} process started with PID: {process.pid}") self.processes[process_name] = process return process @@ -92,17 +103,21 @@ def preexec_fn(): self.logger.error(f"Error running subprocess for {process_description}: {e}") return None - def wait(self, process_name): process = self.processes.get(process_name) if process: - self.stdout, self.stderr = process.communicate() - self.returncode = process.returncode - self.stdout = self.stdout.strip() if self.stdout else "" - self.stderr = self.stderr.strip() if self.stderr else "" - if self.subprocess_loggers.get(process_name): - self.subprocess_loggers[process_name].stop_logging_stdout() - self.subprocess_loggers[process_name].stop_monitoring_stderr() + try: + self.stdout, self.stderr = process.communicate() + self.returncode = process.returncode + self.stdout = self.stdout.strip() if self.stdout else "" + self.stderr = self.stderr.strip() if self.stderr else "" + except Exception as e: + self.logger.error(f"Error while waiting for process {process_name}: {e}") + finally: + if self.subprocess_loggers.get(process_name): + self.subprocess_loggers[process_name].stop_logging_stdout() + self.subprocess_loggers[process_name].stop_monitoring_stderr() + del self.processes[process_name] else: self.logger.error(f"No process found with the name {process_name}.")