Skip to content

Commit

Permalink
Add a pid file to avoid running honcho multiple times
Browse files Browse the repository at this point in the history
  • Loading branch information
klmp200 committed Feb 26, 2025
1 parent b253dd0 commit a4df3d4
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 12 deletions.
7 changes: 5 additions & 2 deletions manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
import atexit
import logging
import os
import sys

Expand All @@ -22,13 +24,14 @@
from sith.settings import PROCFILE_RUNSERVER

if __name__ == "__main__":
logging.basicConfig(encoding="utf-8", level=logging.INFO)

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sith.settings")

from django.core.management import execute_from_command_line

if os.environ.get(DJANGO_AUTORELOAD_ENV) is None and PROCFILE_RUNSERVER is not None:
start_composer(PROCFILE_RUNSERVER)
_ = atexit.register(stop_composer)

execute_from_command_line(sys.argv)

stop_composer()
54 changes: 48 additions & 6 deletions sith/composer.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,68 @@
import os
import logging
import signal
import subprocess
import sys

import psutil

COMPOSER_PID = "COMPOSER_PID"
from sith import settings


def get_pid() -> int | None:
"""Read the PID file to get the currently running composer if it exists"""
if not settings.COMPOSER_PID_PATH.exists():
return None
with open(settings.COMPOSER_PID_PATH, "r", encoding="utf8") as f:
return int(f.read())


def write_pid(pid: int):
"""Write currently running composer pid in PID file"""
if not settings.COMPOSER_PID_PATH.exists():
settings.COMPOSER_PID_PATH.parent.mkdir(parents=True, exist_ok=True)
with open(settings.COMPOSER_PID_PATH, "w", encoding="utf8") as f:
_ = f.write(str(pid))


def delete_pid():
"""Delete PID file for cleanup"""
settings.COMPOSER_PID_PATH.unlink(missing_ok=True)


def is_composer_running() -> bool:
"""Check if the process in the PID file is running"""
pid = get_pid()
if pid is None:
return False
try:
return psutil.Process(pid).is_running()
except psutil.NoSuchProcess:
return False


def start_composer(procfile: str):
"""Starts the composer and stores the PID as an environment variable
This allows for running smoothly with the django reloader
"""
if is_composer_running():
logging.info(f"Composer is already running with pid {get_pid()}")
logging.info(
f"If this is a mistake, please delete {settings.COMPOSER_PID_PATH} and restart the process"
)
return
process = subprocess.Popen(
[sys.executable, "-m", "honcho", "-f", procfile, "start"],
)
os.environ[COMPOSER_PID] = str(process.pid)
write_pid(process.pid)


def stop_composer():
"""Stops the composer if it was started before"""
if (pid := os.environ.get(COMPOSER_PID, None)) is not None:
process = psutil.Process(int(pid))
if is_composer_running():
process = psutil.Process(get_pid())
if process.parent() != psutil.Process():
logging.info("Currently running composer is controlled by another process")
return
process.send_signal(signal.SIGTERM)
process.wait()
_ = process.wait()
delete_pid()
7 changes: 3 additions & 4 deletions sith/pytest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import atexit

import pytest

from .composer import start_composer, stop_composer
Expand All @@ -15,7 +17,4 @@ def pytest_load_initial_conftests(early_config, parser, args):
"""Hook that loads the composer before the pytest-django plugin"""
if PROCFILE_PYTEST is not None:
start_composer(PROCFILE_PYTEST)


def pytest_unconfigure(config):
stop_composer()
_ = atexit.register(stop_composer)
3 changes: 3 additions & 0 deletions sith/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
PROCFILE_RUNSERVER = env.str("PROCFILE_RUNSERVER", None)
PROCFILE_PYTEST = env.str("PROCFILE_PYTEST", None)

## File path used to avoid running the composer multiple times at the same time
COMPOSER_PID_PATH = env.path("COMPOSER_PID_PATH", BASE_DIR / "composer.pid")

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/

Expand Down

0 comments on commit a4df3d4

Please sign in to comment.