Commit 6b651ec2 authored by Ilya A. Otyutskiy's avatar Ilya A. Otyutskiy

Let it be Object Oriented v2.0 version of daemonize.

parent e5017a74
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
Copyright (c) 2012 Ilya A. Otyutskiy <sharp@thesharp.ru>
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2. Subject to the terms and conditions of this License Agreement, PSF
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python
alone or in any derivative version, provided, however, that PSF's
License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
2001, 2002, 2003, 2004 Python Software Foundation; All Rights Reserved"
are retained in Python alone or in any derivative version prepared
by Licensee.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
# daemonize
## Description
**daemonize** is a library for writing system daemons in Python. It was forked from [daemonize.sourceforge.net](http://daemonize.sourceforge.net). It is distributed under PSF license.
**daemonize** is a library for writing system daemons in Python. It has some bits from [daemonize.sourceforge.net](http://daemonize.sourceforge.net). It is distributed under MIT license.
[![Build Status](https://secure.travis-ci.org/thesharp/daemonize.png)](http://travis-ci.org/thesharp/daemonize)
......@@ -11,19 +11,15 @@ You can install it from Python Package Index (PyPI):
$ pip install daemonize
## Usage
#!/usr/bin/env python
from time import sleep
import daemonize
from daemonize import Daemonize
pid = "/tmp/test.pid"
logfile = "/tmp/test.log"
def main():
while True:
daemonize.logging.debug("Doing some pointless job.")
sleep(5)
daemonize.start(main, pid, logfile, debug=True)
daemon = Daemonize(app="test_app", pid=pid, action=main)
daemon.start()
#!/usr/bin/python
# Copyright 2007 Jerry Seutter yello (*a*t*) thegeeks.net
# Copyright 2012 Ilya A. Otyutskiy <sharp@thesharp.ru>
from functools import partial
# #!/usr/bin/python
import fcntl
import os
......@@ -10,95 +6,116 @@ import sys
import signal
import resource
import logging
import atexit
from logging import handlers
# Initialize logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
logger.propagate = False
if sys.platform == "darwin":
syslog_address = "/var/run/syslog"
else:
syslog_address = "/dev/log"
syslog = handlers.SysLogHandler(syslog_address)
syslog.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s %(name)s: %(message)s",
"%b %e %H:%M:%S")
syslog.setFormatter(formatter)
logger.addHandler(syslog)
def sigterm(pid, signum, frame):
logger.warn("Caught signal %d. Stopping daemon." % signum)
os.remove(pid)
sys.exit(0)
def start(fun_to_start, pid, debug=False):
# Fork, creating a new process for the child.
process_id = os.fork()
if process_id < 0:
# Fork error. Exit badly.
sys.exit(1)
elif process_id != 0:
# This is the parent process. Exit.
class Daemonize(object):
""" Daemonize object
Object constructor expects three arguments:
- app: contains the application name which will be sent to syslog
- pid: path to the pidfile
- action: your custom function which will be executed after daemonization.
"""
def __init__(self, app, pid, action):
self.app = app
self.pid = pid
self.action = action
# Initialize logging.
self.logger = logging.getLogger(self.app)
self.logger.setLevel(logging.DEBUG)
# Display log messages only on defined handlers.
self.logger.propagate = False
# It will work on OS X and Linux. No FreeBSD support, guys, I don't
# want to import re here to parse your peculiar platform string.
if sys.platform == "darwin":
syslog_address = "/var/run/syslog"
else:
syslog_address = "/dev/log"
syslog = handlers.SysLogHandler(syslog_address)
syslog.setLevel(logging.INFO)
# Try to mimic to normal syslog messages.
formatter = logging.Formatter("%(asctime)s %(name)s: %(message)s",
"%b %e %H:%M:%S")
syslog.setFormatter(formatter)
self.logger.addHandler(syslog)
def sigterm(self, signum, frame):
""" sigterm method
These actions will be done after SIGTERM.
"""
self.logger.warn("Caught signal %s. Stopping daemon." % signum)
os.remove(self.pid)
sys.exit(0)
# This is the child process. Continue.
# Stop listening for signals that the parent process receives.
# This is done by getting a new process id.
# setpgrp() is an alternative to setsid().
# setsid puts the process in a new parent group and detaches its
# controlling terminal.
process_id = os.setsid()
if process_id == -1:
# Uh oh, there was a problem.
sys.exit(1)
# Close file descriptors
devnull = "/dev/null"
if hasattr(os, "devnull"):
# Python has set os.devnull on this system, use it instead
# as it might be different than /dev/null.
devnull = os.devnull
for fd in range(resource.getrlimit(resource.RLIMIT_NOFILE)[0]):
try:
os.close(fd)
except OSError:
pass
os.open(devnull, os.O_RDWR)
os.dup(0)
os.dup(0)
# Set umask to default to safe file permissions when running
# as a root daemon. 027 is an octal number.
os.umask(027)
# Change to a known directory. If this isn't done, starting
# a daemon in a subdirectory that needs to be deleted results
# in "directory busy" errors.
# On some systems, running with chdir("/") is not allowed,
# so this should be settable by the user of this library.
os.chdir("/")
# Create a lockfile so that only one instance of this daemon
# is running at any time.
lockfile = open(pid, "w")
# Try to get an exclusive lock on the file. This will fail
# if another process has the file locked.
fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
# Record the process id to the lockfile. This is standard
# practice for daemons.
lockfile.write("%s" % (os.getpid()))
lockfile.flush()
# Set custom action on SIGTERM.
signal.signal(signal.SIGTERM, partial(sigterm, pid))
logger.warn("Starting daemon.")
fun_to_start()
def start(self):
""" start method
Main daemonization process.
"""
# Fork, creating a new process for the child.
process_id = os.fork()
if process_id < 0:
# Fork error. Exit badly.
sys.exit(1)
elif process_id != 0:
# This is the parent process. Exit.
sys.exit(0)
# This is the child process. Continue.
# Stop listening for signals that the parent process receives.
# This is done by getting a new process id.
# setpgrp() is an alternative to setsid().
# setsid puts the process in a new parent group and detaches its
# controlling terminal.
process_id = os.setsid()
if process_id == -1:
# Uh oh, there was a problem.
sys.exit(1)
# Close file descriptors
devnull = "/dev/null"
if hasattr(os, "devnull"):
# Python has set os.devnull on this system, use it instead
# as it might be different than /dev/null.
devnull = os.devnull
for fd in range(resource.getrlimit(resource.RLIMIT_NOFILE)[0]):
try:
os.close(fd)
except OSError:
pass
os.open(devnull, os.O_RDWR)
os.dup(0)
os.dup(0)
# Set umask to default to safe file permissions when running
# as a root daemon. 027 is an octal number.
os.umask(027)
# Change to a known directory. If this isn't done, starting
# a daemon in a subdirectory that needs to be deleted results
# in "directory busy" errors.
# On some systems, running with chdir("/") is not allowed,
# so this should be settable by the user of this library.
os.chdir("/")
# Create a lockfile so that only one instance of this daemon
# is running at any time.
lockfile = open(self.pid, "w")
# Try to get an exclusive lock on the file. This will fail
# if another process has the file locked.
fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
# Record the process id to the lockfile. This is standard
# practice for daemons.
lockfile.write("%s" % (os.getpid()))
lockfile.flush()
# Set custom action on SIGTERM.
signal.signal(signal.SIGTERM, self.sigterm)
atexit.register(self.sigterm)
self.logger.warn("Starting daemon.")
self.action()
......@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
setup(
name="daemonize",
version="1.3",
version="2.0",
py_modules=["daemonize"],
author="Ilya A. Otyutskiy",
author_email="sharp@thesharp.ru",
......@@ -12,5 +12,5 @@ setup(
url="https://github.com/thesharp/daemonize",
description="Library to enable your code run as a daemon process "
"on Unix-like systems.",
license="PSF"
license="MIT"
)
#!/usr/bin/env python
from itertools import islice, count
import daemonize
pid = "/tmp/test.pid"
logfile = "/tmp/test.log"
def main():
daemonize.logging.debug("Counting from 0 to 4 and dying.")
for i in islice(count(), 5):
daemonize.logging.debug(i)
daemonize.start(main, pid, logfile, debug=True)
#!/usr/bin/env python
from time import sleep
import daemonize
from daemonize import Daemonize
pid = "/tmp/test.pid"
logfile = "/tmp/test.log"
def main():
while True:
daemonize.logging.debug("...")
sleep(5)
daemonize.start(main, pid, logfile, debug=True)
daemon = Daemonize(app="test_app", pid=pid, action=main)
daemon.start()
......@@ -4,51 +4,18 @@ import subprocess
from time import sleep
class CounterTest(unittest.TestCase):
def setUp(self):
self.logfile = "/tmp/test.log"
self.pidfile = "/tmp/test.pid"
if os.path.isfile(self.logfile):
os.remove(self.logfile)
os.system("python tests/daemon_counter.py")
sleep(.1)
def tearDown(self):
if os.path.isfile(self.logfile):
os.remove(self.logfile)
if os.path.isfile(self.pidfile):
os.remove(self.pidfile)
def test_counter_logfile(self):
self.assertTrue(os.path.isfile(self.logfile))
def test_counter_logfile_length(self):
log = open(self.logfile, "r")
self.assertEqual(len(log.readlines()), 7)
log.close()
def test_counter_logfile_last_line(self):
log = open(self.logfile, "r")
self.assertEqual(log.readlines()[6].split()[4], "4")
log.close()
class DaemonizeTest(unittest.TestCase):
def setUp(self):
self.logfile = "/tmp/test.log"
self.pidfile = "/tmp/test.pid"
if os.path.isfile(self.logfile):
os.remove(self.logfile)
os.system("python tests/daemon_sigterm.py")
sleep(.1)
def tearDown(self):
os.system("kill `cat %s`" % self.pidfile)
sleep(.1)
if os.path.isfile(self.logfile):
os.remove(self.logfile)
def test_is_working(self):
sleep(10)
proc = subprocess.Popen("ps ax | awk '{print $1}' | grep `cat %s`" \
% self.pidfile, shell=True,
stdout=subprocess.PIPE)
......@@ -59,28 +26,8 @@ class DaemonizeTest(unittest.TestCase):
self.assertEqual("%s\n" % pid, ps_pid)
def test_pidfile_presense(self):
sleep(10)
self.assertTrue(os.path.isfile(self.pidfile))
class SIGTERMTest(unittest.TestCase):
def setUp(self):
self.logfile = "/tmp/test.log"
self.pidfile = "/tmp/test.pid"
if os.path.isfile(self.logfile):
os.remove(self.logfile)
os.system("python tests/daemon_sigterm.py")
sleep(.1)
def test_sigterm(self):
os.system("kill `cat %s`" % self.pidfile)
sleep(.1)
self.assertFalse(os.path.isfile(self.pidfile))
def tearDown(self):
os.system("kill `cat %s`" % self.pidfile)
sleep(.1)
if os.path.isfile(self.logfile):
os.remove(self.logfile)
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment