Commits

Can Xue committed dc4b82a

remove daemonkit

  • Participants
  • Parent commits 30d3d72

Comments (0)

Files changed (1)

File kahgean/daemonkit.py

-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 Xue Can <xuecan@gmail.com> and contributors.
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license
-
-"""守护程序工具包
-
-这个模块提供了编写守护程序所需的工具。包括:
-
-  - daemonize: 检查并创建 pid 文件,将当前进程转变成在后台运行的守护进程
-  - change_rlimit_nofile: 修改一个进程可同时打开的文件描述符数量
-  - change_group: 修改用户组
-  - change_user: 修改用户
-
-这个模块仅适用于类 Unix 的操作系统,在 Windows 下不发生作用,但并不抛出错误。
-"""
-
-import os
-import sys
-import signal
-import errno
-import logging
-
-try:
-    import resource, grp, pwd
-except ImportError:
-    resource = grp = pwd = None
-
-
-__all__ = ['MAXFD', 'DEVNULL', 'daemonize',
-           'change_group', 'change_user', 'change_rlimit_nofile']
-
-
-# 默认情况下一个进程最大允许打开的文件描述符是 1024 个
-MAXFD = 1024
-
-# 默认的将会把标准输入输出重定向到 /dev/null
-DEVNULL = os.devnull if hasattr(os, "devnull") else "/dev/null"
-
-
-def _daemonize(umask, stdout, stderr):
-    # 将当前进程从控制台中剥离出来,作为守护进程在后台运行。
-    #
-    # 根据 Chad J. Schroeder 发布于 http://code.activestate.com/recipes/278731/
-    # 的脚本改写。
-    #
-    # Copyright (C) 2012 Xue Can <xuecan@gmail.com>
-    # Copyright (C) 2005 Chad J. Schroeder
-    try:
-        # Fork 第一个子进程,这样父进程就可以退出了。这个操作用于交出对
-        # 终端的控制权。这个操作还保证了子进程不会成为进程组的 leader,
-        # 因为这个子进程拥有新的进程 ID 并继承了父进程的进程组 ID。这个
-        # 步骤对于保证后续的 os.setsid() 得以成功调用来说是必需的。
-        pid = os.fork()
-    except OSError, e:
-        raise Exception, "%s [%d]" % (e.strerror, e.errno)
-
-    if pid > 0:
-        # 结束最初的进程
-        #
-        # exit() 还是 _exit()?
-        # _exit() 和 exit() 类似,但是并不调用 atexit 以及 on_exit 注册的函数
-        # 以及信号处理器。它还关闭所有开启的文件描述符。使用 exit() 将会造成
-        # 所有 stdio 流 flush 两次并可能意外删除临时文件。因此建议 fork() 子
-        # 进程成为守护程序时父进程使用 _exit() 退出。
-        os._exit(0)
-        
-    # 现在我们在第一个子进程中 :-)
-    #
-    # 调用 os.setsid() 用于成为新的 session 和进程组的 leader。这个步骤
-    # 再次确保没有控制终端。
-    os.setsid()
-
-    # 忽略 SIGHUP 信号是必需的吗?
-    #
-    # It's often suggested that the SIGHUP signal should be ignored before
-    # the second fork to avoid premature termination of the process.  The
-    # reason is that when the first child terminates, all processes, e.g.
-    # the second child, in the orphaned group will be sent a SIGHUP.
-    #
-    # "However, as part of the session management system, there are exactly
-    # two cases where SIGHUP is sent on the death of a process:
-    #
-    #   1) When the process that dies is the session leader of a session that
-    #      is attached to a terminal device, SIGHUP is sent to all processes
-    #      in the foreground process group of that terminal device.
-    #   2) When the death of a process causes a process group to become
-    #      orphaned, and one or more processes in the orphaned group are
-    #      stopped, then SIGHUP and SIGCONT are sent to all members of the
-    #      orphaned group." [2]
-    #
-    # The first case can be ignored since the child is guaranteed not to have
-    # a controlling terminal.  The second case isn't so easy to dismiss.
-    # The process group is orphaned when the first child terminates and
-    # POSIX.1 requires that every STOPPED process in an orphaned process
-    # group be sent a SIGHUP signal followed by a SIGCONT signal.  Since the
-    # second child is not STOPPED though, we can safely forego ignoring the
-    # SIGHUP signal.  In any case, there are no ill-effects if it is ignored.
-    #
-    # 总之结论是:忽略 SIGHUP 信号并没有任何副作用。
-    signal.signal(signal.SIGHUP, signal.SIG_IGN)
-
-    try:
-        # Fork 第二个子进程并立即退出第一个子进程以避免僵尸进程。这让第二个
-        # 子进程成为孤儿,让 init 进程负责对该进程进行清理工作。此外,由于
-        # 第一个子进程是一个没有可控制终端的 session leader,后续还是可能
-        # 从打开的终端中获得该进程的(基于 SysV 的系统)。第二次 fork 确保了
-        # 这个进程不再是 session leader,从而杜绝了从终端获得该进程的可能。
-        pid = os.fork()
-    except OSError, e:
-        raise Exception, "%s [%d]" % (e.strerror, e.errno)
-
-    if pid > 0:
-        # 结束第一个子进程
-        os._exit(0)	     
-
-    # 终于到了第二个子进程,也就是守护进程中了 :-)
-
-    # 我们可能不希望继承父进程的 umask,因此可以在这里设置
-    os.umask(umask)
-
-    # 关闭所有的文件描述符。用于避免子进程继续保有从父进程继承而来的文件描述符。
-    # 有几种不同的方法实现这一目的。
-    #
-    # 尝试获得系统配置变量 SC_OPEN_MAX 来获得开启的文件描述符的最大值。如果不
-    # 存在,则使用可配置的默认值。
-    #
-    # try:
-    #     maxfd = os.sysconf("SC_OPEN_MAX")
-    # except (AttributeError, ValueError):
-    #     maxfd = MAXFD
-    #
-    # 或者
-    #
-    # if (os.sysconf_names.has_key("SC_OPEN_MAX")):
-    #     maxfd = os.sysconf("SC_OPEN_MAX")
-    # else:
-    #     maxfd = MAXFD
-    #
-    # 或者
-    #
-    # 使用 getrlimit() 来获取一个进程可打开的最大的文件描述符。如果没有限制
-    # 则使用默认值。
-    #
-    import resource
-    maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
-    if (maxfd == resource.RLIM_INFINITY):
-       maxfd = MAXFD
-   
-    # 遍历所有文件描述符并关闭,如果失败(因为并未打开)简单忽略即可。
-    for fd in range(0, maxfd):
-       try:
-          os.close(fd)
-       except OSError:
-          pass
- 
-    # 重定向标准输入/输出文件描述符到特殊的文件。由于守护程序不再拥有可控制
-    # 的终端,许多守护程序重定向 stdin、stdout 和 stderr 到 /dev/null。这样
-    # 可以避免读写标准输入/输出时产生的副作用。
- 
-    # 这个 open() 调用可以确保获得最小的文件描述符(0),这是因为之前已经关闭
-    # 了所有的文件描述符。
-    os.open(DEVNULL, os.O_RDWR)	        # stdin  (0)
- 
-    # 将 stdin 复制到 stdout 和 stderr
-    if stdout == DEVNULL:
-        os.dup2(0, 1)                   # stdout (1)
-    else:
-        os.open(stdout, os.O_CREAT|os.O_APPEND|os.O_RDWR)
-    if stderr == DEVNULL:
-        os.dup2(0, 2)                   # stderr (2)
-    else:
-        os.open(stderr, os.O_CREAT|os.O_APPEND|os.O_RDWR)
-    
-    # 上面的代码有些“魔术”。为什么这么设置一下 stdin,stdout 和 stderr 就自动
-    # 指向 /dev/null 或者指定的文件了呢?首先,之前我们清空了从 0 到 maxfd
-    # 之间所有文件描述符。这并非必须,但是被认为是最佳实践。随后执行的 open()
-    # 操作确保返回的是最小的文件描述符 0。而内部的 stdin、stdout 和 stderr 对应
-    # 的文件描述符恰恰是 0, 1, 2,这就可以理解上述如此简洁的代码为什么是有效
-    # 的了。
-    #
-    # 详情请参考 http://code.activestate.com/recipes/278731/ 下原作者的答疑。
-    #
-    # 当然,如果之前没有进行关闭所有文件描述符的操作,这就不能工作了。一个
-    # 可行的替代方案如下:
-    #
-    # class NullDevice:
-    #     def write(self, s):
-    #         pass
-    # sys.stdin.close()
-    # sys.stdout = NullDevice()
-    # sys.stderr = NullDevice()
-
-
-def _check_pidfile(pidfile):
-    """检查 pid 文件"""
-    if not pidfile: return
-    if not os.path.exists(pidfile): return
-    try:
-        pid = int(open(pidfile).read())
-    except ValueError:
-        message = u'PID 文件 %s 中包含的信息不是进程号。' % pidfile
-        logging.getLogger().error(message)
-        sys.exit(message)
-    try:
-        # kill -0 用于检查进程是否存在
-        os.kill(pid, 0)
-    except OSError, e:
-        if e.args[0] == errno.ESRCH:
-            # PID 不存在
-            logging.getLogger().info(u'删除过期的 PID 文件。')
-            os.remove(pidfile)
-        else:
-            message = (u'无法根据 PID 文件 %s 中 PID %d 检查进程状态,'
-                       u'失败原因是:%s' % (pidfile, pid, e.args[1]))
-            logging.getLogger().error(message)
-            sys.exit(message)
-    else:
-        message = u"另一个进程正在运行中,PID 为:%d\n" % pid
-        logging.getLogger().error(message)
-        sys.exit(message)
-
-
-def _write_pidfile(pidfile):
-    """将当前进程号写入指定的 PID 文件"""
-    if not pidfile: return
-    with open(pidfile, 'wb') as f:
-        f.write(str(os.getpid()))
-    if not os.path.exists(pidfile):
-        raise Exception(u"PID 文件 %s 丢失。" % pidfile)
-
-
-def _remove_pidfile(pidfile):
-    """删除 PID 文件并退出程序"""
-    if pidfile and os.path.exists(pidfile):
-        os.remove(pidfile)
-    sys.exit(0)
-
-
-def daemonize(pidfile, umask=0o000, stdout=DEVNULL, stderr=DEVNULL):
-    """让程序成为守护进程
-    
-    首先将会检查 PID 文件判断是否有服务进程正在运行。若没有,则将当前进程
-    从控制台中剥离出来,作为守护进程在后台运行。最后,将守护进程的进程号
-    写入 PID 文件。
-    
-    这个函数还会注册 SIGTERM 信号处理器,一旦收到 kill TERM 信号,将会
-    删除 PID 文件并退出系统。
-    
-    若参数 pidfile 为 None,则不会进行和 PID 文件有关的操作。
-    """
-    if not resource: return
-    _check_pidfile(pidfile)
-    _daemonize(umask, stdout, stderr)
-    _write_pidfile(pidfile)
-    signal.signal(signal.SIGTERM, lambda s, f: _remove_pidfile(pidfile))
-
-
-def change_group(groupname):
-    """修改用户组"""
-    if not grp: return
-    os.setgid(grp.getgrnam(groupname).gr_gid)
-
-
-def change_user(username):
-    """修改用户"""
-    if not pwd: return
-    os.setuid(pwd.getpwnam(username).pw_uid)
-
-
-def change_rlimit_nofile(limit):
-    """修改进程同时能打开的最大文件描述符数量"""
-    if not resource: return
-    resource.setrlimit(resource.RLIMIT_NOFILE, (limit, limit))
-
-
-def add_options(options):
-    """配合 options 模块使用,方便添加和本模块有关的配置参数"""
-    options.add_option('--daemon', action='store_true', help='should %(prog)s '
-                       'run as a daemon')
-    options.add_option('--pidfile', help='filename of the pid-file')
-    options.add_option('--user', help='name of the user to run %(prog)s as')
-    options.add_option('--group', help='name of the group to run %(prog)s as')
-    options.add_option('--rlimit-nofile', type=int, help='maximum number of '
-                       'open file descriptors')
-
-
-def deal_with_options(options):
-    """配合 add_options() 使用,分析参数并进行相应操作"""
-    if options.get('rlimit_nofile'):
-        change_rlimit_nofile(options.get('rlimit_nofile'))
-    if options.get('group'):
-        change_group(options.get('group'))
-    if options.get('user'):
-        change_user(options.get('user'))
-    if options.get('daemon'):
-        pidfile = options.get(pidfile)
-        daemonize(pidfile)