From d03924ae82c7b84de4d1e4ca18046228da911333 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 16 Aug 2017 11:11:07 -0700 Subject: [PATCH] launcher: add checks for stale PID files via psutil on Unix This hasn't been tested on other systems, so it is disabled there. Closes #512. --- README.md | 1 + launcher.py | 35 +++++++++++++++++++++++++++++++---- setup.py | 1 + 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 46adb23..4045377 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ If you are a developer and want to help make PyLink more portable, patches are h * PyYAML (`pip3 install pyyaml`) * ircmatch (`pip3 install ircmatch`) * *For password encryption*: Passlib (`pip3 install passlib`) + * *For enhanced cron support (by removing stale PID files): psutil (`pip3 install psutil`) * *For the servprotect plugin*: expiringdict (install this from [source](https://github.com/mailgun/expiringdict); installation is broken in pip due to [mailgun/expiringdict#13](https://github.com/mailgun/expiringdict/issues/13)) 2) Clone the repository: `git clone https://github.com/GLolol/PyLink && cd PyLink` diff --git a/launcher.py b/launcher.py index 46a7969..4507f8c 100644 --- a/launcher.py +++ b/launcher.py @@ -7,6 +7,11 @@ import os import sys from pylinkirc import world, conf, __version__, real_version +try: + import psutil +except ImportError: + psutil = None + def main(): import argparse @@ -40,10 +45,32 @@ def main(): if not args.no_pid: pidfile = '%s.pid' % conf.confname if os.path.exists(pidfile): - log.error("PID file exists %r; aborting! If PyLink didn't shut down cleanly last time it " - "ran, or you're upgrading from PyLink < 1.1-dev, delete %r and start the " - "server again." % (pidfile, pidfile)) - sys.exit(1) + + has_stale_pid = False + if psutil is not None and os.name == 'posix': + # FIXME: Haven't tested this on other platforms, so not turning it on by default. + with open(pidfile) as f: + try: + pid = int(f.read()) + proc = psutil.Process(pid) + except psutil.NoSuchProcess: # Process doesn't exist! + has_stale_pid = True + log.info("Ignoring stale PID %s from PID file %r: no such process exists.", pid, pidfile) + else: + # This PID got reused for something that isn't us? + if not any('pylink' in arg.lower() for arg in proc.cmdline()): + log.info("Ignoring stale PID %s from PID file %r: process command line %r is not us", pid, pidfile, proc.cmdline()) + has_stale_pid = True + + if not has_stale_pid: + log.error("PID file exists %r; aborting!", pidfile) + if psutil is None: + log.error("If PyLink didn't shut down cleanly last time it ran, or you're upgrading " + "from PyLink < 1.1-dev, delete %r and start the server again.", pidfile) + if os.name == 'posix': + log.error("Alternatively, you can install psutil for Python 3 (pip3 install psutil), " + "which will allow this launcher to detect stale PID files and ignore them.") + sys.exit(1) with open(pidfile, 'w') as f: f.write(str(os.getpid())) diff --git a/setup.py b/setup.py index 04978ec..251e298 100644 --- a/setup.py +++ b/setup.py @@ -79,6 +79,7 @@ setup( extras_require={ 'password-hashing': ['passlib'], + 'cron-support': ['psutil'], 'servprotect': ['expiringdict>=1.1.4'], },