Skip to content

Commit

Permalink
Merge 63b26b6 into 26f511b
Browse files Browse the repository at this point in the history
  • Loading branch information
Xorboo committed Dec 24, 2018
2 parents 26f511b + 63b26b6 commit 7be22ef
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 60 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
pid.egg-info/
build/
dist/
.idea
.eggs
venv
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
language: python

python:
- 2.6
- 2.7
- 3.3
- 3.4
- 3.5
- pypy
Expand All @@ -13,12 +11,12 @@ before_install:
- sudo chmod 0777 /var/run

install:
- pip install "coverage>=3.6" "nose>=1.3.0" "flake8>=2.1" "coveralls>=0.3" "mock>=2.0.0"
- pip install "coverage>=3.6" "nose>=1.3.0" "flake8>=2.1" "coveralls>=0.3" "mock>=2.0.0" "psutil>=5.4.8"
- pip install -e .

script:
- echo $TRAVIS_PYTHON_VERSION
- if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]]; then flake8 -v --show-source --ignore=E501,W391 .; fi
- flake8 -v --show-source --ignore=E501,W391 .
- nosetests -v --with-coverage --cover-package=pid

after_success:
Expand Down
51 changes: 36 additions & 15 deletions pid/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import os
import sys
import errno
import fcntl
import atexit
import signal
import logging
import tempfile
import psutil


__version__ = "2.2.0"
Expand Down Expand Up @@ -33,6 +33,10 @@ class PidFileAlreadyLockedError(PidFileError):
pass


class SamePidFileNotSupported(PidFileError):
pass


class PidFile(object):
__slots__ = ("pid", "pidname", "piddir", "enforce_dotpid_postfix", "register_term_signal_handler",
"filename", "fh", "lock_pidfile", "chmod", "uid", "gid", "force_tmpdir",
Expand All @@ -50,7 +54,10 @@ def __init__(self, pidname=None, piddir=None, enforce_dotpid_postfix=True,
self.uid = uid
self.gid = gid
self.force_tmpdir = force_tmpdir

self.allow_samepid = allow_samepid
if os.name != "posix" and self.allow_samepid:
raise SamePidFileNotSupported("Flag allow_samepid is not supported on non-POSIX systems")

self.fh = None
self.filename = None
Expand Down Expand Up @@ -132,16 +139,11 @@ def inner_check(fh):
if self.allow_samepid and self.pid == pid:
return PID_CHECK_SAMEPID

try:
os.kill(pid, 0)
except OSError as exc:
if exc.errno == errno.ESRCH:
# this pid is not running
return PID_CHECK_NOTRUNNING
if psutil.pid_exists(pid):
self.close(fh=fh, cleanup=False)
raise PidFileAlreadyRunningError(exc)
self.close(fh=fh, cleanup=False)
raise PidFileAlreadyRunningError("Program already running with pid: %d" % pid)
raise PidFileAlreadyRunningError("Program already running with pid: %d" % pid)
else:
return PID_CHECK_NOTRUNNING

self.setup()

Expand All @@ -150,6 +152,16 @@ def inner_check(fh):
if self.fh is None:
if self.filename and os.path.isfile(self.filename):
with open(self.filename, "r") as fh:
# Try to read from file to check if it is locked by the same process
if os.name != "posix":
try:
fh.seek(0)
fh.read(1)
except IOError as exc:
if exc.errno == 13:
raise PidFileAlreadyRunningError(exc)
else:
raise
return inner_check(fh)
return PID_CHECK_NOFILE
else:
Expand All @@ -162,7 +174,15 @@ def create(self):
self.fh = open(self.filename, 'a+')
if self.lock_pidfile:
try:
fcntl.flock(self.fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
if os.name == "posix":
import fcntl
fcntl.flock(self.fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
else:
import msvcrt
msvcrt.locking(self.fh.fileno(), msvcrt.LK_NBLCK, 1)
# Try to read from file to check if it is actually locked
self.fh.seek(0)
self.fh.read(1)
except IOError as exc:
if not self.allow_samepid:
self.close(cleanup=False)
Expand All @@ -172,10 +192,11 @@ def create(self):
if check_result == PID_CHECK_SAMEPID:
return

if self.chmod:
os.fchmod(self.fh.fileno(), self.chmod)
if self.uid >= 0 or self.gid >= 0:
os.fchown(self.fh.fileno(), self.uid, self.gid)
if os.name == "posix":
if self.chmod:
os.fchmod(self.fh.fileno(), self.chmod)
if self.uid >= 0 or self.gid >= 0:
os.fchown(self.fh.fileno(), self.uid, self.gid)
self.fh.seek(0)
self.fh.truncate()
# pidfile must consist of the pid and a newline character
Expand Down
4 changes: 1 addition & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,8 @@ def find_version(*file_paths):
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: Implementation :: PyPy',
Expand All @@ -54,5 +52,5 @@ def find_version(*file_paths):
packages=["pid"],
install_requires=[],
test_suite='nose.collector',
setup_requires=['nose>=1.0'],
setup_requires=['nose>=1.0', 'psutil>=5.4.8'],
)
141 changes: 103 additions & 38 deletions tests/test_pid.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ def test_that_raises_at_the_end():
raise AssertionError("Failed to throw exception of type(s) %s." % (", ".join(exc_type.__name__ for exc_type in exc_types),))


@contextmanager
def raising_windows_io_error():
try:
yield
except IOError as exc:
if exc.errno != 13:
raise
except Exception:
raise
else:
raise AssertionError("Failed to throw exception")


def test_pid_class():
pidfile = pid.PidFile()
pidfile.create()
Expand All @@ -54,9 +67,16 @@ def test_pid_context_manager():


def test_pid_pid():
with pid.PidFile() as pidfile:
pidnr = int(open(pidfile.filename, "r").readline().strip())
assert pidnr == os.getpid(), "%s != %s" % (pidnr, os.getpid())
def check_pid_pid():
with pid.PidFile() as pidfile:
pidnr = int(open(pidfile.filename, "r").readline().strip())
assert pidnr == os.getpid(), "%s != %s" % (pidnr, os.getpid())

if os.name == "posix":
check_pid_pid()
else:
with raising_windows_io_error():
check_pid_pid()


def test_pid_custom_name():
Expand Down Expand Up @@ -205,9 +225,11 @@ def test_pid_multiplecreate():


def test_pid_gid():
gid = os.getgid()
with pid.PidFile(gid=gid):
pass
# os.getgid() does not exist on windows
if os.name == "posix":
gid = os.getgid()
with pid.PidFile(gid=gid):
pass


def test_pid_check_const_empty():
Expand All @@ -227,16 +249,31 @@ def test_pid_check_const_nofile():


def test_pid_check_const_samepid():
with pid.PidFile(allow_samepid=True) as pidfile:
assert pidfile.check() == pid.PID_CHECK_SAMEPID
def check_const_samepid():
with pid.PidFile(allow_samepid=True) as pidfile:
assert pidfile.check() == pid.PID_CHECK_SAMEPID

if os.name == "posix":
check_const_samepid()
else:
with raising(pid.SamePidFileNotSupported):
check_const_samepid()


def test_pid_check_const_notrunning():
with pid.PidFile() as pidfile:
with open(pidfile.filename, "w") as f:
# hope this does not clash
f.write("999999999\n")
assert pidfile.check() == pid.PID_CHECK_NOTRUNNING
def check_const_notrunning():
with pid.PidFile() as pidfile:
with open(pidfile.filename, "w") as f:
# hope this does not clash
f.write("999999999\n")
f.flush()
assert pidfile.check() == pid.PID_CHECK_NOTRUNNING

if os.name == "posix":
check_const_notrunning()
else:
with raising_windows_io_error():
check_const_notrunning()


def test_pid_check_already_running():
Expand All @@ -247,40 +284,68 @@ def test_pid_check_already_running():


def test_pid_check_samepid_with_blocks():
with pid.PidFile(allow_samepid=True):
def check_samepid_with_blocks_separate_objects():
with pid.PidFile(allow_samepid=True):
pass
with pid.PidFile(allow_samepid=True):
pass

pidfile = pid.PidFile(allow_samepid=True)
with pidfile:
def check_samepid_with_blocks_same_objects():
pidfile = pid.PidFile(allow_samepid=True)
with pidfile:
pass
with pidfile:
pass

if os.name == "posix":
check_samepid_with_blocks_separate_objects()
else:
with raising(pid.SamePidFileNotSupported):
check_samepid_with_blocks_separate_objects()

def test_pid_check_samepid():
pidfile = pid.PidFile(allow_samepid=True)
try:
pidfile.create()
pidfile.create()
finally:
pidfile.close()
if os.name == "posix":
check_samepid_with_blocks_same_objects()
else:
with raising(pid.SamePidFileNotSupported):
check_samepid_with_blocks_same_objects()


def test_pid_check_samepid_two_processes():
pidfile_proc1 = pid.PidFile()
pidfile_proc2 = pid.PidFile(allow_samepid=True)
def test_pid_check_samepid():
def check_samepid():
pidfile = pid.PidFile(allow_samepid=True)
try:
pidfile.create()
pidfile.create()
finally:
pidfile.close()

try:
with patch('pid.os.getpid') as mgetpid:
mgetpid.return_value = 1
pidfile_proc1.create()
if os.name == "posix":
check_samepid()
else:
with raising(pid.SamePidFileNotSupported):
check_samepid()

mgetpid.return_value = 2
with raising(pid.PidFileAlreadyRunningError, pid.PidFileAlreadyLockedError):
pidfile_proc2.create()
finally:
pidfile_proc1.close()
pidfile_proc2.close()

def test_pid_check_samepid_two_processes():
def check_samepid_two_processes():
pidfile_proc1 = pid.PidFile()
pidfile_proc2 = pid.PidFile(allow_samepid=True)

try:
with patch('pid.os.getpid') as mgetpid:
mgetpid.return_value = 1
pidfile_proc1.create()

mgetpid.return_value = 2
with raising(pid.PidFileAlreadyRunningError, pid.PidFileAlreadyLockedError):
pidfile_proc2.create()
finally:
pidfile_proc1.close()
pidfile_proc2.close()

if os.name == "posix":
check_samepid_two_processes()
else:
with raising(pid.SamePidFileNotSupported):
check_samepid_two_processes()


def test_pid_default_term_signal():
Expand Down

0 comments on commit 7be22ef

Please sign in to comment.