SoFunction
Updated on 2024-11-10

Python daemon and script singleton running in detail

This article focuses on Python daemons and scripts run singly, I think it's pretty good, now share it with you, but also give you a reference. Together follow the editor over to see it

I. Introduction

The most important characteristic of a daemon is that it runs in the background; it must be isolated from its pre-run environment, which consists of unclosed file descriptors, control terminals, session and process groups, working directories, and file creation masks; it can be started from the startup script /etc/ at system startup, either by the inetd daemon, or by the job planning process crond. It can also be executed by a user terminal (usually a shell).

Python sometimes needs to ensure that only one instance of a script is run to avoid data conflicts.

II. Python daemons

1、Function realization

#!/usr/bin/env python 
#coding: utf-8 
import sys, os 
 
'''Fork the current process as a daemon
  Note: If your daemon was started by inetd, don't do this! inetd finishes!
  Inetd does everything you need to do, including redirecting standard file descriptors, and the only things you need to do are chdir() and umask().
''' 
 
def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 
   # Redirect standard file descriptors (default to /dev/null)
  try:  
    pid = ()  
     # The parent process (session group leader process) exits, which means that a non-session group leader process can never regain control of the terminal.
    if pid > 0: 
      (0)  # The parent process exits
  except OSError, e:  
     ("fork #1 failed: (%d) %s\n" % (, ) ) 
    (1) 
 
   # Removed from the maternal environment
  ("/") #chdir makes sure that the process does not keep any directories in use, otherwise it cannot mount a filesystem. It is also possible to change to a directory that is important for the daemon to run.
  (0)  # Call umask(0) in order to have full control over whatever is written, because sometimes it's not clear what kind of umask is inherited.
  ()  After a successful #setsid call, the process becomes the new session leader and the new process leader and is detached from the original login session and process group.
 
   # Execute the second fork
  try:  
    pid = ()  
    if pid > 0: 
      (0)  # The second parent process exits
  except OSError, e:  
     ("fork #2 failed: (%d) %s\n" % (, ) ) 
    (1) 
 
   #Process is already a daemon, redirecting standard file descriptors
 
  for f in , : () 
  si = open(stdin, 'r') 
  so = open(stdout, 'a+') 
  se = open(stderr, 'a+', 0) 
  os.dup2((), ())  The #dup2 function atomically closes and copies file descriptors
  os.dup2((), ()) 
  os.dup2((), ()) 
 
# Sample function: print a number and timestamp every second
def main(): 
  import time 
  ('Daemon started with pid %d\n' % ()) 
  ('Daemon stdout output\n') 
  ('Daemon stderr output\n') 
  c = 0 
  while True: 
    ('%d: %s\n' %(c, ())) 
    () 
    c = c+1 
    (1) 
 
if __name__ == "__main__": 
   daemonize('/dev/null','/tmp/daemon_stdout.log','/tmp/daemon_error.log') 
   main() 

You can use the command ps -ef | grep to view the inheritance running in the background, in /tmp/daemon_error.log will record the error running log, in /tmp/daemon_stdout.log will record the standard output log.

2、Class realization

#!/usr/bin/env python 
#coding: utf-8 
 
#python emulates linux daemons
 
import sys, os, time, atexit, string 
from signal import SIGTERM 
 
class Daemon: 
 def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 
   # Need to get debug information, change to stdin='/dev/stdin', stdout='/dev/stdout', stderr='/dev/stderr', run as root.
   = stdin 
   = stdout 
   = stderr 
   = pidfile 
  
 def _daemonize(self): 
  try: 
   pid = ()  # First fork, spawns child process, breaks away from parent process
   if pid > 0: 
    (0)   # Exit the main process
  except OSError, e: 
   ('fork #1 failed: %d (%s)\n' % (, )) 
   (1) 
  
  ("/")   # Modify the working directory
  ()    #Setup a new session connection
  (0)    # Reset file creation permissions
  
  try: 
   pid = () #Fork the process a second time to prevent it from opening a terminal.
   if pid > 0: 
    (0) 
  except OSError, e: 
   ('fork #2 failed: %d (%s)\n' % (, )) 
   (1) 
  
   # Redirect file descriptors
  () 
  () 
  si = file(, 'r') 
  so = file(, 'a+') 
  se = file(, 'a+', 0) 
  os.dup2((), ()) 
  os.dup2((), ()) 
  os.dup2((), ()) 
  
   #Register the exit function to determine if a process exists based on the file pid.
  () 
  pid = str(()) 
  file(,'w+').write('%s\n' % pid) 
  
 def delpid(self): 
  () 
 
 def start(self): 
   # Check for the existence of a pid file to detect the presence of a process
  try: 
   pf = file(,'r') 
   pid = int(().strip()) 
   () 
  except IOError: 
   pid = None 
  
  if pid: 
   message = 'pidfile %s already exist. Daemon already running!\n' 
   (message % ) 
   (1) 
   
  #Start monitoring
  self._daemonize() 
  self._run() 
 
 def stop(self): 
  # Get the pid from the pid file
  try: 
   pf = file(,'r') 
   pid = int(().strip()) 
   () 
  except IOError: 
   pid = None 
  
  if not pid:  #Reboot without error
   message = 'pidfile %s does not exist. Daemon not running!\n' 
   (message % ) 
   return 
 
   # Kill the process
  try: 
   while 1: 
    (pid, SIGTERM) 
    (0.1) 
    #(' stop datanode') 
    #(' stop tasktracker') 
    #() 
  except OSError, err: 
   err = str(err) 
   if ('No such process') > 0: 
    if (): 
     () 
   else: 
    print str(err) 
    (1) 
 
 def restart(self): 
  () 
  () 
 
 def _run(self): 
  """ run your fun""" 
  while True: 
   #fp=open('/tmp/result','a+') 
   #('Hello World\n') 
   ('%s:hello world\n' % ((),)) 
   ()  
   (2) 
   
 
if __name__ == '__main__': 
  daemon = Daemon('/tmp/watch_process.pid', stdout = '/tmp/watch_stdout.log') 
  if len() == 2: 
    if 'start' == [1]: 
      () 
    elif 'stop' == [1]: 
      () 
    elif 'restart' == [1]: 
      () 
    else: 
      print 'unknown command' 
      (2) 
    (0) 
  else: 
    print 'usage: %s start|stop|restart' % [0] 
    (2) 

Run results:

It is when Daemon is designed as a template, from daemon import Daemon in other files, and then define subclasses that override the run() method to implement their own functionality.

class MyDaemon(Daemon): 
  def run(self): 
    while True: 
      fp=open('/tmp/','a+') 
      ('Hello World\n') 
      (1) 

Weaknesses: signal handling (, cleanup_handler) is not installed at the moment, and the callback function delpid() when the registered program exits is not called.

Then, write a shell command to add a boot startup service that detects if the daemon is started every 2 seconds, and starts it if it is not, and automatically monitors the recovery process.

#/bin/sh 
while true 
do 
 count=`ps -ef | grep "" | grep -v "grep"` 
 if [ "$?" != "0" ]; then 
    start 
 fi 
 sleep 2 
done 

III. python guarantees that only one instance of the script can be run

1. Open the file itself with a lock

#!/usr/bin/env python 
#coding: utf-8 
import fcntl, sys, time, os 
pidfile = 0 
 
def ApplicationInstance(): 
  global pidfile 
  pidfile = open((__file__), "r") 
  try: 
    (pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB) # Create an exclusive lock, and other locked processes will not block.
  except: 
    print "another instance is running..." 
    (1) 
 
if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    (1) 

Note: the open() parameter cannot use w, otherwise it will overwrite its own file; pidfile must be declared as a global variable, otherwise the life cycle of the local variable is over and the file descriptor will be reclaimed by the system due to a reference count of 0. (If the whole function is written in the main function, it doesn't need to be defined as global).

              

2. Open the customized file and lock it

#!/usr/bin/env python 
#coding: utf-8 
import fcntl, sys, time 
pidfile = 0 
 
def ApplicationInstance(): 
  global pidfile 
  pidfile = open("", "w") 
  try: 
    (pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB) # Create an exclusive lock, and other locked processes will not block.
  except IOError: 
    print "another instance is running..." 
    (0) 
 
if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    (1) 

3. Detection of PID in documents

#!/usr/bin/env python 
#coding: utf-8 
import time, os, sys 
import signal 
 
pidfile = '/tmp/' 
 
def sig_handler(sig, frame): 
  if (pidfile): 
    (pidfile) 
  (0) 
 
def ApplicationInstance(): 
  (, sig_handler) 
  (, sig_handler) 
  (, sig_handler) 
 
  try: 
   pf = file(pidfile, 'r') 
   pid = int(().strip()) 
   () 
  except IOError: 
   pid = None 
  
  if pid: 
   ('instance is running...\n') 
   (0) 
 
  file(pidfile, 'w+').write('%s\n' % ()) 
 
if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    (1) 

4. Detect specific folders or files

#!/usr/bin/env python 
#coding: utf-8 
import time, commands, signal, sys 
 
def sig_handler(sig, frame): 
  if ("/tmp/test"): 
    ("/tmp/test") 
  (0) 
 
def ApplicationInstance(): 
  (, sig_handler) 
  (, sig_handler) 
  (, sig_handler) 
  if ("mkdir /tmp/test")[0]: 
    print "instance is running..." 
    (0) 
 
if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    (1) 

It is also possible to detect a specific file and determine if the file exists:

import os 
import  
import time 
  
  
#class used to handle one application instance mechanism 
class ApplicationInstance: 
  
  #specify the file used to save the application instance pid 
  def __init__( self, pid_file ): 
    self.pid_file = pid_file 
    () 
    () 
  
  #check if the current application is already running 
  def check( self ): 
    #check if the pidfile exists 
    if not ( self.pid_file ): 
      return 
    #read the pid from the file 
    pid = 0 
    try: 
      file = open( self.pid_file, 'rt' ) 
      data = () 
      () 
      pid = int( data ) 
    except: 
      pass 
    #check if the process with specified by pid exists 
    if 0 == pid: 
      return 
  
    try: 
      ( pid, 0 )  #this will raise an exception if the pid is not valid 
    except: 
      return 
  
    #exit the application 
    print "The application is already running..." 
    exit(0) #exit raise an exception so don't put it in a try/except block 
  
  #called when the single instance starts to save it's pid 
  def startApplication( self ): 
    file = open( self.pid_file, 'wt' ) 
    ( str( () ) ) 
    () 
  
  #called when the single instance exit ( remove pid file ) 
  def exitApplication( self ): 
    try: 
      ( self.pid_file ) 
    except: 
      pass 
  
  
if __name__ == '__main__': 
  #create application instance 
  appInstance = ApplicationInstance( '/tmp/' ) 
  
  #do something here 
  print "Start MyApp" 
  (5)  #sleep 5 seconds 
  print "End MyApp" 
  
  #remove pid file 
  () 

The above ( pid, 0 ) is used to check if a process with pid is still alive, and throws an exception if the process with pid has stopped, and does not send a kill signal if it is running.

5. socket listens on a specific port

#!/usr/bin/env python 
#coding: utf-8 
import socket, time, sys 
 
 
def ApplicationInstance(): 
  try:   
    global s 
    s = () 
    host = () 
    ((host, 60123)) 
  except: 
    print "instance is running..." 
    (0) 
 
if __name__ == "__main__": 
  ApplicationInstance() 
  while True: 
    print 'running...' 
    (1) 

The function can be implemented using a decorator for easy reuse (same effect as above):

#!/usr/bin/env python 
#coding: utf-8 
import socket, time, sys 
import functools 
 
# Implemented using decorators
def ApplicationInstance(func): 
  @(func) 
  def fun(*args,**kwargs): 
    import socket 
    try: 
      global s 
      s = () 
      host = () 
      ((host, 60123)) 
    except: 
      print('already has an instance...') 
      return None 
    return func(*args,**kwargs) 
  return fun 
 
@ApplicationInstance 
def main(): 
  while True: 
    print 'running...' 
    (1) 
 
if __name__ == "__main__": 
  main() 

IV. Summary

(1) daemon and single script run in the actual application is more important, the method is also more, you can choose the appropriate to modify, you can make them into a separate class or template, and then subclassing to achieve customization.

(2) daemon monitoring process automatic recovery to avoid the use of nohup and & and with shell scripts can save a lot of untimely startup hangs the server trouble.

This is the whole content of this article.