X-Git-Url: https://reisub.nsupdate.info/git/?p=x93.git%2F.git;a=blobdiff_plain;f=xftp.git%2Fext%2Fpyftpdlib%2Fprefork.py;fp=xftp.git%2Fext%2Fpyftpdlib%2Fprefork.py;h=eb67365341ec4442fc4ae1414135c74a68503e35;hp=0000000000000000000000000000000000000000;hb=3b43a05e10ec76aa79c878d8bd130e943708c08d;hpb=5938fd9eafee8417d8e7d677d3e663e66f02e7d4 diff --git a/xftp.git/ext/pyftpdlib/prefork.py b/xftp.git/ext/pyftpdlib/prefork.py new file mode 100644 index 0000000..eb67365 --- /dev/null +++ b/xftp.git/ext/pyftpdlib/prefork.py @@ -0,0 +1,125 @@ +# Copyright (C) 2007 Giampaolo Rodola' . +# Use of this source code is governed by MIT license that can be +# found in the LICENSE file. + +"""Process utils.""" + +import errno +import os +import sys +import time +from binascii import hexlify +try: + import multiprocessing +except ImportError: + multiprocessing = None + +from ._compat import long +from .log import logger + + +_task_id = None + + +def cpu_count(): + """Returns the number of processors on this machine.""" + if multiprocessing is None: + return 1 + try: + return multiprocessing.cpu_count() + except NotImplementedError: + pass + try: + return os.sysconf("SC_NPROCESSORS_CONF") + except (AttributeError, ValueError): + pass + return 1 + + +def _reseed_random(): + if 'random' not in sys.modules: + return + import random + # If os.urandom is available, this method does the same thing as + # random.seed (at least as of python 2.6). If os.urandom is not + # available, we mix in the pid in addition to a timestamp. + try: + seed = long(hexlify(os.urandom(16)), 16) + except NotImplementedError: + seed = int(time.time() * 1000) ^ os.getpid() + random.seed(seed) + + +def fork_processes(number, max_restarts=100): + """Starts multiple worker processes. + + If *number* is None or <= 0, we detect the number of cores available + on this machine and fork that number of child processes. + If *number* is given and > 0, we fork that specific number of + sub-processes. + + Since we use processes and not threads, there is no shared memory + between any server code. + + In each child process, *fork_processes* returns its *task id*, a + number between 0 and *number*. Processes that exit abnormally + (due to a signal or non-zero exit status) are restarted with the + same id (up to *max_restarts* times). In the parent process, + *fork_processes* returns None if all child processes have exited + normally, but will otherwise only exit by throwing an exception. + """ + global _task_id + assert _task_id is None + if number is None or number <= 0: + number = cpu_count() + logger.info("starting %d pre-fork processes", number) + children = {} + + def start_child(i): + pid = os.fork() + if pid == 0: + # child process + _reseed_random() + global _task_id + _task_id = i + return i + else: + children[pid] = i + return None + + for i in range(number): + id = start_child(i) + if id is not None: + return id + num_restarts = 0 + while children: + try: + pid, status = os.wait() + except OSError as e: + if e.errno == errno.EINTR: + continue + raise + if pid not in children: + continue + id = children.pop(pid) + if os.WIFSIGNALED(status): + logger.warning("child %d (pid %d) killed by signal %d, restarting", + id, pid, os.WTERMSIG(status)) + elif os.WEXITSTATUS(status) != 0: + logger.warning( + "child %d (pid %d) exited with status %d, restarting", + id, pid, os.WEXITSTATUS(status)) + else: + logger.info("child %d (pid %d) exited normally", id, pid) + continue + num_restarts += 1 + if num_restarts > max_restarts: + raise RuntimeError("Too many child restarts, giving up") + new_id = start_child(id) + if new_id is not None: + return new_id + # All child processes exited cleanly, so exit the master process + # instead of just returning to right after the call to + # fork_processes (which will probably just start up another IOLoop + # unless the caller checks the return value). + sys.exit(0)