|  | @@ -13,22 +13,21 @@ import atexit
 | 
	
		
			
				|  |  |  import errno
 | 
	
		
			
				|  |  |  import os
 | 
	
		
			
				|  |  |  import platform as _platform
 | 
	
		
			
				|  |  | -import shlex
 | 
	
		
			
				|  |  |  import signal as _signal
 | 
	
		
			
				|  |  |  import sys
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +from billiard import current_process
 | 
	
		
			
				|  |  |  from contextlib import contextmanager
 | 
	
		
			
				|  |  |  from itertools import imap
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  from .local import try_import
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -from kombu.utils.limits import TokenBucket
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  _setproctitle = try_import('setproctitle')
 | 
	
		
			
				|  |  |  resource = try_import('resource')
 | 
	
		
			
				|  |  |  pwd = try_import('pwd')
 | 
	
		
			
				|  |  |  grp = try_import('grp')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +# exitcodes
 | 
	
		
			
				|  |  |  EX_OK = getattr(os, 'EX_OK', 0)
 | 
	
		
			
				|  |  |  EX_FAILURE = 1
 | 
	
		
			
				|  |  |  EX_UNAVAILABLE = getattr(os, 'EX_UNAVAILABLE', 69)
 | 
	
	
		
			
				|  | @@ -44,13 +43,12 @@ DAEMON_WORKDIR = '/'
 | 
	
		
			
				|  |  |  PIDFILE_FLAGS = os.O_CREAT | os.O_EXCL | os.O_WRONLY
 | 
	
		
			
				|  |  |  PIDFILE_MODE = ((os.R_OK | os.W_OK) << 6) | ((os.R_OK) << 3) | ((os.R_OK))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -_setps_bucket = TokenBucket(0.5)  # 30/m, every 2 seconds
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  PIDLOCKED = """ERROR: Pidfile ({0}) already exists.
 | 
	
		
			
				|  |  | -Seems we're already running? (PID: {1})"""
 | 
	
		
			
				|  |  | +Seems we're already running? (pid: {1})"""
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def pyimplementation():
 | 
	
		
			
				|  |  | +    """Returns string identifying the current Python implementation."""
 | 
	
		
			
				|  |  |      if hasattr(_platform, 'python_implementation'):
 | 
	
		
			
				|  |  |          return _platform.python_implementation()
 | 
	
		
			
				|  |  |      elif sys.platform.startswith('java'):
 | 
	
	
		
			
				|  | @@ -65,6 +63,12 @@ def pyimplementation():
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def _find_option_with_arg(argv, short_opts=None, long_opts=None):
 | 
	
		
			
				|  |  | +    """Search argv for option specifying its short and longopt
 | 
	
		
			
				|  |  | +    alternatives.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    Returns the value of the option if found.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  |      for i, arg in enumerate(argv):
 | 
	
		
			
				|  |  |          if arg.startswith('-'):
 | 
	
		
			
				|  |  |              if long_opts and arg.startswith('--'):
 | 
	
	
		
			
				|  | @@ -77,6 +81,10 @@ def _find_option_with_arg(argv, short_opts=None, long_opts=None):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def maybe_patch_concurrency(argv, short_opts=None, long_opts=None):
 | 
	
		
			
				|  |  | +    """With short and long opt alternatives that specify the command-line
 | 
	
		
			
				|  |  | +    option to set the pool, this makes sure that anything that needs
 | 
	
		
			
				|  |  | +    to be patched is completed as early as possible.
 | 
	
		
			
				|  |  | +    (e.g. eventlet/gevent monkey patches)."""
 | 
	
		
			
				|  |  |      try:
 | 
	
		
			
				|  |  |          pool = _find_option_with_arg(argv, short_opts, long_opts)
 | 
	
		
			
				|  |  |      except KeyError:
 | 
	
	
		
			
				|  | @@ -89,7 +97,6 @@ def maybe_patch_concurrency(argv, short_opts=None, long_opts=None):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  class LockFailed(Exception):
 | 
	
		
			
				|  |  |      """Raised if a pidlock can't be acquired."""
 | 
	
		
			
				|  |  | -    pass
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def get_fdmax(default=None):
 | 
	
	
		
			
				|  | @@ -106,13 +113,14 @@ def get_fdmax(default=None):
 | 
	
		
			
				|  |  |      return fdmax
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -class PIDFile(object):
 | 
	
		
			
				|  |  | -    """PID lock file.
 | 
	
		
			
				|  |  | +class Pidfile(object):
 | 
	
		
			
				|  |  | +    """Pidfile
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      This is the type returned by :func:`create_pidlock`.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    **Should not be used directly, use the :func:`create_pidlock`
 | 
	
		
			
				|  |  | -    context instead**
 | 
	
		
			
				|  |  | +    TIP: Use the :func:`create_pidlock` function instead,
 | 
	
		
			
				|  |  | +    which is more convenient and also removes stale pidfiles (when
 | 
	
		
			
				|  |  | +    the process holding the lock is no longer running).
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      """
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -142,34 +150,23 @@ class PIDFile(object):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def read_pid(self):
 | 
	
		
			
				|  |  |          """Reads and returns the current pid."""
 | 
	
		
			
				|  |  | -        try:
 | 
	
		
			
				|  |  | -            fh = open(self.path, 'r')
 | 
	
		
			
				|  |  | -        except IOError as exc:
 | 
	
		
			
				|  |  | -            if exc.errno == errno.ENOENT:
 | 
	
		
			
				|  |  | -                return
 | 
	
		
			
				|  |  | -            raise
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        try:
 | 
	
		
			
				|  |  | -            line = fh.readline()
 | 
	
		
			
				|  |  | -            if line.strip() == line:  # must contain '\n'
 | 
	
		
			
				|  |  | -                raise ValueError(
 | 
	
		
			
				|  |  | -                    'Partial or invalid pidfile {0.path}'.format(self))
 | 
	
		
			
				|  |  | -        finally:
 | 
	
		
			
				|  |  | -            fh.close()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        try:
 | 
	
		
			
				|  |  | -            return int(line.strip())
 | 
	
		
			
				|  |  | -        except ValueError:
 | 
	
		
			
				|  |  | -            raise ValueError('PID file {0.path} invalid.'.format(self))
 | 
	
		
			
				|  |  | +        with ignore_errno('ENOENT'):
 | 
	
		
			
				|  |  | +            with open(self.path, 'r') as fh:
 | 
	
		
			
				|  |  | +                line = fh.readline()
 | 
	
		
			
				|  |  | +                if line.strip() == line:  # must contain '\n'
 | 
	
		
			
				|  |  | +                    raise ValueError(
 | 
	
		
			
				|  |  | +                        'Partial or invalid pidfile {0.path}'.format(self))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                try:
 | 
	
		
			
				|  |  | +                    return int(line.strip())
 | 
	
		
			
				|  |  | +                except ValueError:
 | 
	
		
			
				|  |  | +                    raise ValueError(
 | 
	
		
			
				|  |  | +                        'pidfile {0.path} contents invalid.'.format(self))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def remove(self):
 | 
	
		
			
				|  |  |          """Removes the lock."""
 | 
	
		
			
				|  |  | -        try:
 | 
	
		
			
				|  |  | +        with ignore_errno(errno.ENOENT, errno.EACCES):
 | 
	
		
			
				|  |  |              os.unlink(self.path)
 | 
	
		
			
				|  |  | -        except OSError as exc:
 | 
	
		
			
				|  |  | -            if exc.errno in (errno.ENOENT, errno.EACCES):
 | 
	
		
			
				|  |  | -                return
 | 
	
		
			
				|  |  | -            raise
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def remove_if_stale(self):
 | 
	
		
			
				|  |  |          """Removes the lock if the process is not running.
 | 
	
	
		
			
				|  | @@ -217,19 +214,21 @@ class PIDFile(object):
 | 
	
		
			
				|  |  |                      "Inconsistency: Pidfile content doesn't match at re-read")
 | 
	
		
			
				|  |  |          finally:
 | 
	
		
			
				|  |  |              rfh.close()
 | 
	
		
			
				|  |  | +PIDFile = Pidfile  # compat alias
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def create_pidlock(pidfile):
 | 
	
		
			
				|  |  | -    """Create and verify pid file.
 | 
	
		
			
				|  |  | +    """Create and verify pidfile.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    If the pid file already exists the program exits with an error message,
 | 
	
		
			
				|  |  | -    however if the process it refers to is not running anymore, the pid file
 | 
	
		
			
				|  |  | +    If the pidfile already exists the program exits with an error message,
 | 
	
		
			
				|  |  | +    however if the process it refers to is not running anymore, the pidfile
 | 
	
		
			
				|  |  |      is deleted and the program continues.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    The caller is responsible for releasing the lock before the program
 | 
	
		
			
				|  |  | -    exits.
 | 
	
		
			
				|  |  | +    This function will automatically install an :mod:`atexit` handler
 | 
	
		
			
				|  |  | +    to release the lock at exit, you can skip this by calling
 | 
	
		
			
				|  |  | +    :func:`_create_pidlock` instead.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    :returns: :class:`PIDFile`.
 | 
	
		
			
				|  |  | +    :returns: :class:`Pidfile`.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      **Example**:
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -244,7 +243,7 @@ def create_pidlock(pidfile):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def _create_pidlock(pidfile):
 | 
	
		
			
				|  |  | -    pidlock = PIDFile(pidfile)
 | 
	
		
			
				|  |  | +    pidlock = Pidfile(pidfile)
 | 
	
		
			
				|  |  |      if pidlock.is_locked() and not pidlock.remove_if_stale():
 | 
	
		
			
				|  |  |          raise SystemExit(PIDLOCKED.format(pidfile, pidlock.read_pid()))
 | 
	
		
			
				|  |  |      pidlock.acquire()
 | 
	
	
		
			
				|  | @@ -252,6 +251,7 @@ def _create_pidlock(pidfile):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def fileno(f):
 | 
	
		
			
				|  |  | +    """Get object fileno, or :const:`None` if not defined."""
 | 
	
		
			
				|  |  |      try:
 | 
	
		
			
				|  |  |          return f.fileno()
 | 
	
		
			
				|  |  |      except AttributeError:
 | 
	
	
		
			
				|  | @@ -260,13 +260,11 @@ def fileno(f):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  class DaemonContext(object):
 | 
	
		
			
				|  |  |      _is_open = False
 | 
	
		
			
				|  |  | -    workdir = DAEMON_WORKDIR
 | 
	
		
			
				|  |  | -    umask = DAEMON_UMASK
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def __init__(self, pidfile=None, workdir=None, umask=None,
 | 
	
		
			
				|  |  |              fake=False, **kwargs):
 | 
	
		
			
				|  |  | -        self.workdir = workdir or self.workdir
 | 
	
		
			
				|  |  | -        self.umask = self.umask if umask is None else umask
 | 
	
		
			
				|  |  | +        self.workdir = workdir or DAEMON_WORKDIR
 | 
	
		
			
				|  |  | +        self.umask = DAEMON_UMASK if umask is None else umask
 | 
	
		
			
				|  |  |          self.fake = fake
 | 
	
		
			
				|  |  |          self.stdfds = (sys.stdin, sys.stdout, sys.stderr)
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -286,7 +284,7 @@ class DaemonContext(object):
 | 
	
		
			
				|  |  |              preserve = [fileno(f) for f in self.stdfds if fileno(f)]
 | 
	
		
			
				|  |  |              for fd in reversed(range(get_fdmax(default=2048))):
 | 
	
		
			
				|  |  |                  if fd not in preserve:
 | 
	
		
			
				|  |  | -                    with ignore_EBADF():
 | 
	
		
			
				|  |  | +                    with ignore_errno(errno.EBADF):
 | 
	
		
			
				|  |  |                          os.close(fd)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              for fd in self.stdfds:
 | 
	
	
		
			
				|  | @@ -316,7 +314,7 @@ def detached(logfile=None, pidfile=None, uid=None, gid=None, umask=0,
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      :keyword logfile: Optional log file.  The ability to write to this file
 | 
	
		
			
				|  |  |         will be verified before the process is detached.
 | 
	
		
			
				|  |  | -    :keyword pidfile: Optional pid file.  The pid file will not be created,
 | 
	
		
			
				|  |  | +    :keyword pidfile: Optional pidfile.  The pidfile will not be created,
 | 
	
		
			
				|  |  |        as this is the responsibility of the child.  But the process will
 | 
	
		
			
				|  |  |        exit if the pid lock exists and the pid written is still running.
 | 
	
		
			
				|  |  |      :keyword uid: Optional user id or user name to change
 | 
	
	
		
			
				|  | @@ -332,7 +330,6 @@ def detached(logfile=None, pidfile=None, uid=None, gid=None, umask=0,
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      .. code-block:: python
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        import atexit
 | 
	
		
			
				|  |  |          from celery.platforms import detached, create_pidlock
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          with detached(logfile='/var/log/app.log', pidfile='/var/run/app.pid',
 | 
	
	
		
			
				|  | @@ -418,6 +415,7 @@ def _setgroups_hack(groups):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def setgroups(groups):
 | 
	
		
			
				|  |  | +    """Set active groups from a list of group ids."""
 | 
	
		
			
				|  |  |      max_groups = None
 | 
	
		
			
				|  |  |      try:
 | 
	
		
			
				|  |  |          max_groups = os.sysconf('SC_NGROUPS_MAX')
 | 
	
	
		
			
				|  | @@ -434,6 +432,8 @@ def setgroups(groups):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def initgroups(uid, gid):
 | 
	
		
			
				|  |  | +    """Compat version of :func:`os.initgroups` which was first
 | 
	
		
			
				|  |  | +    added to Python 2.7."""
 | 
	
		
			
				|  |  |      if not pwd:  # pragma: no cover
 | 
	
		
			
				|  |  |          return
 | 
	
		
			
				|  |  |      username = pwd.getpwuid(uid)[0]
 | 
	
	
		
			
				|  | @@ -444,25 +444,13 @@ def initgroups(uid, gid):
 | 
	
		
			
				|  |  |      setgroups(groups)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def setegid(gid):
 | 
	
		
			
				|  |  | -    """Set effective group id."""
 | 
	
		
			
				|  |  | -    gid = parse_gid(gid)
 | 
	
		
			
				|  |  | -    if gid != os.getegid():
 | 
	
		
			
				|  |  | -        os.setegid(gid)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -def seteuid(uid):
 | 
	
		
			
				|  |  | -    """Set effective user id."""
 | 
	
		
			
				|  |  | -    uid = parse_uid(uid)
 | 
	
		
			
				|  |  | -    if uid != os.geteuid():
 | 
	
		
			
				|  |  | -        os.seteuid(uid)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  def setgid(gid):
 | 
	
		
			
				|  |  | +    """Version of :func:`os.setgid` supporting group names."""
 | 
	
		
			
				|  |  |      os.setgid(parse_gid(gid))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def setuid(uid):
 | 
	
		
			
				|  |  | +    """Version of :func:`os.setuid` supporting usernames."""
 | 
	
		
			
				|  |  |      os.setuid(parse_uid(uid))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -625,29 +613,48 @@ if os.environ.get('NOSETPS'):  # pragma: no cover
 | 
	
		
			
				|  |  |          pass
 | 
	
		
			
				|  |  |  else:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    def set_mp_process_title(progname, info=None, hostname=None,  # noqa
 | 
	
		
			
				|  |  | -            rate_limit=False):
 | 
	
		
			
				|  |  | +    def set_mp_process_title(progname, info=None, hostname=None):  # noqa
 | 
	
		
			
				|  |  |          """Set the ps name using the multiprocessing process name.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          Only works if :mod:`setproctitle` is installed.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          """
 | 
	
		
			
				|  |  | -        if not rate_limit or _setps_bucket.can_consume(1):
 | 
	
		
			
				|  |  | -            from billiard import current_process
 | 
	
		
			
				|  |  | -            if hostname:
 | 
	
		
			
				|  |  | -                progname = '{0}@{1}'.format(progname, hostname.split('.')[0])
 | 
	
		
			
				|  |  | -            return set_process_title(
 | 
	
		
			
				|  |  | -                '{0}:{1}'.format(progname, current_process().name), info=info)
 | 
	
		
			
				|  |  | +        if hostname:
 | 
	
		
			
				|  |  | +            progname = '{0}@{1}'.format(progname, hostname.split('.')[0])
 | 
	
		
			
				|  |  | +        return set_process_title(
 | 
	
		
			
				|  |  | +            '{0}:{1}'.format(progname, current_process().name), info=info)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def shellsplit(s):
 | 
	
		
			
				|  |  | -    return shlex.split(s, posix=not IS_WINDOWS)
 | 
	
		
			
				|  |  | +def get_errno(n):
 | 
	
		
			
				|  |  | +    """Get errno for string, e.g. ``ENOENT``."""
 | 
	
		
			
				|  |  | +    if isinstance(n, basestring):
 | 
	
		
			
				|  |  | +        return getattr(errno, n)
 | 
	
		
			
				|  |  | +    return n
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @contextmanager
 | 
	
		
			
				|  |  | -def ignore_EBADF():
 | 
	
		
			
				|  |  | +def ignore_errno(*errnos, **kwargs):
 | 
	
		
			
				|  |  | +    """Context manager to ignore specific POSIX error codes.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    Takes a list of error codes to ignore, which can be either
 | 
	
		
			
				|  |  | +    the name of the code, or the code integer itself::
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        >>> with ignore_errno('ENOENT'):
 | 
	
		
			
				|  |  | +        ...     with open('foo', 'r'):
 | 
	
		
			
				|  |  | +        ...         return r.read()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        >>> with ignore_errno(errno.ENOENT, errno.EPERM):
 | 
	
		
			
				|  |  | +        ...    pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    :keyword types: A tuple of exceptions to ignore (when the errno matches),
 | 
	
		
			
				|  |  | +                    defaults to :exc:`Exception`.
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    types = kwargs.get('types') or (Exception, )
 | 
	
		
			
				|  |  | +    errnos = [get_errno(errno) for errno in errnos]
 | 
	
		
			
				|  |  |      try:
 | 
	
		
			
				|  |  |          yield
 | 
	
		
			
				|  |  | -    except OSError as exc:
 | 
	
		
			
				|  |  | -        if exc.errno != errno.EBADF:
 | 
	
		
			
				|  |  | +    except types, exc:
 | 
	
		
			
				|  |  | +        if not hasattr(exc, 'errno'):
 | 
	
		
			
				|  |  | +            raise
 | 
	
		
			
				|  |  | +        if exc.errno not in errnos:
 | 
	
		
			
				|  |  |              raise
 |