Skip to content

My contribution. #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 28 additions & 30 deletions daemon_command.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from optparse import make_option
import signal
from django.core.management.base import NoArgsCommand
from django.core.management.base import BaseCommand
from initd import Initd

class DaemonCommand(NoArgsCommand):

class DaemonCommand(BaseCommand):
"""
Run a management command as a daemon.

Expand All @@ -19,45 +19,43 @@ class DaemonCommand(NoArgsCommand):
"""
requires_model_validation = True
WORKDIR = '.'
UMASK = 0
UMASK = 0o022
PID_FILE = 'daemon_command.pid'
LOGFILE = 'daemon_command.log'
STDOUT = '/dev/null'
STDERR = STDOUT

option_list = NoArgsCommand.option_list + (
make_option('--start', action='store_const', const='start', dest='action',
help='Start the daemon'),
make_option('--stop', action='store_const', const='stop', dest='action',
help='Stop the daemon'),
make_option('--restart', action='store_const', const='restart', dest='action',
help='Stop and restart the daemon'),
make_option('--status', action='store_const', const='status', dest='action',
help='Report whether the daemon is currently running or stopped'),
make_option('--workdir', action='store', dest='workdir', default=WORKDIR,
help='Full path of the working directory to which the process should '
'change on daemon start.'),
make_option('--umask', action='store', dest='umask', default=UMASK, type="int",
help='File access creation mask ("umask") to set for the process on '
'daemon start.'),
make_option('--pidfile', action='store', dest='pid_file',
default=PID_FILE, help='PID filename.'),
make_option('--logfile', action='store', dest='log_file',
default=LOGFILE, help='Path to log file'),
make_option('--stdout', action='store', dest='stdout', default=STDOUT,
help='Destination to redirect standard out'),
make_option('--stderr', action='store', dest='stderr', default=STDERR,
help='Destination to redirect standard error'),
)

def add_arguments(self, parser):
"""
Add options to daemon command, compatible for Django version >= 1.8
:param parser: current Command parser
:return: Nothing
"""
parser.add_argument('--start', action='store_const', const='start', dest='action', help='Start the daemon')
parser.add_argument('--stop', action='store_const', const='stop', dest='action', help='Stop the daemon')
parser.add_argument('--restart', action='store_const', const='restart', dest='action',
help='Stop and restart the daemon')
parser.add_argument('--status', action='store_const', const='status', dest='action',
help='Report whether the daemon is currently running or stopped')
parser.add_argument('--workdir', action='store', dest='workdir', default=self.WORKDIR,
help='Full path of the working directory to which the process should change '
'on daemon start.')
parser.add_argument('--umask', action='store', dest='umask', default=self.UMASK, type=int,
help='File access creation mask ("umask") to set for the process on daemon start.')
parser.add_argument('--pidfile', action='store', dest='pid_file', default=self.PID_FILE, help='PID filename.')
parser.add_argument('--logfile', action='store', dest='log_file', default=self.LOGFILE, help='Path to log file')
parser.add_argument('--stdout', action='store', dest='stdout', default=self.STDOUT,
help='Destination to redirect standard out')
parser.add_argument('--stderr', action='store', dest='stderr', default=self.STDERR,
help='Destination to redirect standard error')

def loop_callback(self):
raise NotImplementedError

def exit_callback(self):
pass

def handle_noargs(self, **options):
def handle(self, **options):
action = options.pop('action', None)

if action:
Expand Down
84 changes: 75 additions & 9 deletions initd.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,81 @@
"""
from __future__ import print_function


import logging, os, signal, sys, time, errno
import logging
import os
import signal
import sys
import time
import errno
import django

__all__ = ['start', 'stop', 'restart', 'status', 'execute']

"""
Django compatibility as become daemon is no more in Django framework
"""
if django.VERSION < (1, 9):
from django.utils.daemonize import become_daemon
else:
if os.name == 'posix':
def become_daemon(our_home_dir='.', out_log='/dev/null',
err_log='/dev/null', umask=0o022):
"""Robustly turn into a UNIX daemon, running in our_home_dir."""
# First fork
try:
if os.fork() > 0:
sys.exit(0) # kill off parent
except OSError as e:
sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1)
os.setsid()
os.chdir(our_home_dir)
os.umask(umask)

# Second fork
try:
if os.fork() > 0:
os._exit(0)
except OSError as e:
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
os._exit(1)

si = open('/dev/null', 'r')
so = open(out_log, 'ab+', 0)
se = open(err_log, 'ab+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# Set custom file descriptors so that they get proper buffering.
sys.stdout, sys.stderr = so, se
else:
def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=0o022):
"""
If we're not running under a POSIX system, just simulate the daemon
mode by doing redirections and directory changing.
"""
os.chdir(our_home_dir)
os.umask(umask)
sys.stdin.close()
sys.stdout.close()
sys.stderr.close()
if err_log:
sys.stderr = open(err_log, 'ab', 0)
else:
sys.stderr = NullDevice()
if out_log:
sys.stdout = open(out_log, 'ab', 0)
else:
sys.stdout = NullDevice()

class NullDevice:
"""A writeable object that writes to nowhere -- like /dev/null."""
def write(self, s):
pass


class Initd(object):
def __init__(self, log_file='', pid_file='', workdir='', umask='',
def __init__(self, log_file='', pid_file='', workdir='', umask='',
stdout='', stderr='', **kwargs):
self.log_file = log_file
self.pid_file = pid_file
Expand Down Expand Up @@ -43,14 +111,14 @@ def start(self, run, exit=None):
logging.warn('Daemon already running.')
return

from django.utils.daemonize import become_daemon
become_daemon(self.workdir, self.stdout, self.stderr, self.umask)

_initialize_logging(self.log_file)
_create_pid_file(self.pid_file)

# workaround for closure issue is putting running flag in array
running = [True]

def cb_term_handler(sig, frame):
"""
Invoked when the daemon is stopping. Tries to stop gracefully
Expand All @@ -64,6 +132,7 @@ def cb_term_handler(sig, frame):
logging.debug('Calling exit handler')
exit()
running[0] = False

def cb_alrm_handler(sig, frame):
"""
Invoked when the daemon could not stop gracefully. Forces
Expand All @@ -75,6 +144,7 @@ def cb_alrm_handler(sig, frame):
"""
logging.warn('Could not exit gracefully. Forcefully exiting.')
sys.exit(1)

signal.signal(signal.SIGALRM, cb_alrm_handler)
signal.alarm(5)

Expand All @@ -87,13 +157,12 @@ def cb_alrm_handler(sig, frame):
run()
# disabling warning for catching Exception, since it is the
# top level loop
except Exception as exc: # pylint: disable-msg=W0703
except Exception as exc: # pylint: disable-msg=W0703
logging.exception(exc)
finally:
os.remove(self.pid_file)
logging.info('Exiting.')


def stop(self, run=None, exit=None):
"""
Stops the daemon. This reads from the pid file, and sends the SIGTERM
Expand All @@ -117,7 +186,6 @@ def stop(self, run=None, exit=None):
time.sleep(0.5)
sys.stdout.write('\n')


def restart(self, run, exit=None):
"""
Restarts the daemon. This simply calls stop (if the process is running)
Expand All @@ -131,7 +199,6 @@ def restart(self, run, exit=None):
print('Starting.')
self.start(run, exit=exit)


def status(self, run=None, exit=None):
"""
Prints the daemon's status:
Expand All @@ -143,7 +210,6 @@ def status(self, run=None, exit=None):
sys.stdout.write('Stopped.\n')
sys.stdout.flush()


def execute(self, action, run=None, exit=None):
cmd = getattr(self, action)
cmd(run, exit)
Expand Down