[Python-modules-commits] [python-hupper] 01/05: New upstream version 1.0

Takaki Taniguchi takaki at moszumanska.debian.org
Wed Jul 19 03:25:40 UTC 2017


This is an automated email from the git hooks/post-receive script.

takaki pushed a commit to branch master
in repository python-hupper.

commit 73340d1dab34d4827a7bc17045628cb0eaab296d
Author: TANIGUCHI Takaki <takaki at asis.media-as.org>
Date:   Wed Jul 19 12:19:54 2017 +0900

    New upstream version 1.0
---
 .coveragerc                     |   2 +-
 .gitignore                      |  63 ---------
 .travis.yml                     |   8 +-
 CHANGES.rst                     |  31 +++++
 PKG-INFO                        |  46 +++++-
 README.rst                      |   2 +-
 docs/conf.py                    |   2 +-
 setup.cfg                       |   5 +-
 setup.py                        |  12 +-
 src/hupper.egg-info/PKG-INFO    |  46 +++++-
 src/hupper.egg-info/SOURCES.txt |   1 -
 src/hupper/compat.py            |  11 +-
 src/hupper/ipc.py               | 301 +++++++++++++++++++++++++++++++++++++---
 src/hupper/reloader.py          |  32 ++---
 src/hupper/winapi.py            |  18 ++-
 src/hupper/worker.py            | 122 +++++++---------
 tests/conftest.py               |   2 +-
 tests/myapp/__init__.py         |   9 ++
 tests/test_it.py                |  10 +-
 tox.ini                         |  16 ++-
 20 files changed, 522 insertions(+), 217 deletions(-)

diff --git a/.coveragerc b/.coveragerc
index f9fb7ce..7d3f59d 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -4,7 +4,7 @@ source =
     hupper
     tests
 omit =
-    hupper/winapi.py
+    src/hupper/winapi.py
 
 [paths]
 source =
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index bf0bf4d..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,63 +0,0 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
-env*/
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# PyInstaller
-#  Usually these files are written by a python script from a template
-#  before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*,cover
-.hypothesis/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-.idea
-cover
diff --git a/.travis.yml b/.travis.yml
index a37a4ea..9716134 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,11 +18,11 @@ matrix:
       env: TOXENV=py27
     - python: 'pypy'
       env: TOXENV=pypy
-    - python: '3.5'
-      env: TOXENV=py27,py35,coverage
-    - python: '3.5'
+    - python: '3.6'
+      env: TOXENV=py2,py3,coverage
+    - python: '3.6'
       env: TOXENV=docs
-    - python: '3.5'
+    - python: '3.6'
       env: TOXENV=pep8
 
 install: pip install tox
diff --git a/CHANGES.rst b/CHANGES.rst
index d071d2c..e81a328 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,34 @@
+1.0 (2017-05-18)
+================
+
+- Copy ``sys.path`` to the worker process and ensure ``hupper`` is on the
+  ``PYTHONPATH`` so that the subprocess can import it to start the worker.
+  This fixes an issue with how ``zc.buildout`` injects dependencies into a
+  process which is done entirely by ``sys.path`` manipulation.
+  See https://github.com/Pylons/hupper/pull/27
+
+0.5 (2017-05-10)
+================
+
+- On non-windows systems ensure an exec occurs so that the worker does not
+  share the same process space as the reloader causing certain code that
+  is imported in both to not ever be reloaded. Under the hood this was a
+  significant rewrite to use subprocess instead of multiprocessing.
+  See https://github.com/Pylons/hupper/pull/23
+
+0.4.4 (2017-03-10)
+==================
+
+- Fix some versions of Windows which were failing to duplicate stdin to
+  the subprocess and crashing.
+  https://github.com/Pylons/hupper/pull/16
+
+0.4.3 (2017-03-07)
+==================
+
+- Fix pdb and other readline-based programs to operate properly.
+  See https://github.com/Pylons/hupper/pull/15
+
 0.4.2 (2017-01-24)
 ==================
 
diff --git a/PKG-INFO b/PKG-INFO
index 89cf2d8..78e68ef 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,11 +1,11 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
 Name: hupper
-Version: 0.4.2
-Summary: Integrated process monitor for developing servers.
+Version: 1.0
+Summary: Integrated process monitor for developing and reloading daemons.
 Home-page: https://github.com/Pylons/hupper
 Author: Michael Merickel
-Author-email: michael at merickel.org
-License: UNKNOWN
+Author-email: pylons-discuss at googlegroups.com
+License: MIT
 Description: ======
         hupper
         ======
@@ -13,7 +13,7 @@ Description: ======
         .. image:: https://img.shields.io/pypi/v/hupper.svg
             :target: https://pypi.python.org/pypi/hupper
         
-        .. image:: https://img.shields.io/travis/Pylons/hupper.svg
+        .. image:: https://img.shields.io/travis/Pylons/hupper/master.svg
             :target: https://travis-ci.org/Pylons/hupper
         
         .. image:: https://readthedocs.org/projects/hupper/badge/?version=latest
@@ -72,6 +72,37 @@ Description: ======
         ``pserve --reload``.
         
         
+        1.0 (2017-05-18)
+        ================
+        
+        - Copy ``sys.path`` to the worker process and ensure ``hupper`` is on the
+          ``PYTHONPATH`` so that the subprocess can import it to start the worker.
+          This fixes an issue with how ``zc.buildout`` injects dependencies into a
+          process which is done entirely by ``sys.path`` manipulation.
+          See https://github.com/Pylons/hupper/pull/27
+        
+        0.5 (2017-05-10)
+        ================
+        
+        - On non-windows systems ensure an exec occurs so that the worker does not
+          share the same process space as the reloader causing certain code that
+          is imported in both to not ever be reloaded. Under the hood this was a
+          significant rewrite to use subprocess instead of multiprocessing.
+          See https://github.com/Pylons/hupper/pull/23
+        
+        0.4.4 (2017-03-10)
+        ==================
+        
+        - Fix some versions of Windows which were failing to duplicate stdin to
+          the subprocess and crashing.
+          https://github.com/Pylons/hupper/pull/16
+        
+        0.4.3 (2017-03-07)
+        ==================
+        
+        - Fix pdb and other readline-based programs to operate properly.
+          See https://github.com/Pylons/hupper/pull/15
+        
         0.4.2 (2017-01-24)
         ==================
         
@@ -174,7 +205,7 @@ Description: ======
         
 Keywords: server daemon autoreload reloader hup file watch process
 Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
+Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Natural Language :: English
@@ -186,3 +217,4 @@ Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
diff --git a/README.rst b/README.rst
index e421547..648da48 100644
--- a/README.rst
+++ b/README.rst
@@ -5,7 +5,7 @@ hupper
 .. image:: https://img.shields.io/pypi/v/hupper.svg
     :target: https://pypi.python.org/pypi/hupper
 
-.. image:: https://img.shields.io/travis/Pylons/hupper.svg
+.. image:: https://img.shields.io/travis/Pylons/hupper/master.svg
     :target: https://travis-ci.org/Pylons/hupper
 
 .. image:: https://readthedocs.org/projects/hupper/badge/?version=latest
diff --git a/docs/conf.py b/docs/conf.py
index 2886e21..4b746fd 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -62,7 +62,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'hupper'
-copyright = u'2016, Michael Merickel'
+copyright = u'2017, Michael Merickel'
 
 # The version info for the project you're documenting, acts as replacement
 # for |version| and |release|, also used in various other places throughout
diff --git a/setup.cfg b/setup.cfg
index 5bbf2cc..ad1df59 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,9 +1,12 @@
 [wheel]
 universal = 1
 
+[metadata]
+license_file = LICENSE.txt
+
 [flake8]
 exclude = 
-	hupper/compat.py
+	hupper/compat.py,
 show-source = True
 max-line-length = 80
 
diff --git a/setup.py b/setup.py
index 951dd38..1407431 100644
--- a/setup.py
+++ b/setup.py
@@ -22,15 +22,19 @@ tests_require = [
 
 setup(
     name='hupper',
-    version='0.4.2',
-    description='Integrated process monitor for developing servers.',
+    version='1.0',
+    description=(
+        'Integrated process monitor for developing and reloading daemons.'
+    ),
     long_description=readme + '\n\n' + changes,
     author='Michael Merickel',
-    author_email='michael at merickel.org',
+    author_email='pylons-discuss at googlegroups.com',
     url='https://github.com/Pylons/hupper',
+    license='MIT',
     packages=find_packages('src', exclude=['tests']),
     package_dir={'': 'src'},
     include_package_data=True,
+    python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
     extras_require={
         'docs': docs_require,
         'testing': tests_require,
@@ -39,7 +43,7 @@ setup(
     zip_safe=False,
     keywords='server daemon autoreload reloader hup file watch process',
     classifiers=[
-        'Development Status :: 4 - Beta',
+        'Development Status :: 5 - Production/Stable',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: MIT License',
         'Natural Language :: English',
diff --git a/src/hupper.egg-info/PKG-INFO b/src/hupper.egg-info/PKG-INFO
index 89cf2d8..78e68ef 100644
--- a/src/hupper.egg-info/PKG-INFO
+++ b/src/hupper.egg-info/PKG-INFO
@@ -1,11 +1,11 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
 Name: hupper
-Version: 0.4.2
-Summary: Integrated process monitor for developing servers.
+Version: 1.0
+Summary: Integrated process monitor for developing and reloading daemons.
 Home-page: https://github.com/Pylons/hupper
 Author: Michael Merickel
-Author-email: michael at merickel.org
-License: UNKNOWN
+Author-email: pylons-discuss at googlegroups.com
+License: MIT
 Description: ======
         hupper
         ======
@@ -13,7 +13,7 @@ Description: ======
         .. image:: https://img.shields.io/pypi/v/hupper.svg
             :target: https://pypi.python.org/pypi/hupper
         
-        .. image:: https://img.shields.io/travis/Pylons/hupper.svg
+        .. image:: https://img.shields.io/travis/Pylons/hupper/master.svg
             :target: https://travis-ci.org/Pylons/hupper
         
         .. image:: https://readthedocs.org/projects/hupper/badge/?version=latest
@@ -72,6 +72,37 @@ Description: ======
         ``pserve --reload``.
         
         
+        1.0 (2017-05-18)
+        ================
+        
+        - Copy ``sys.path`` to the worker process and ensure ``hupper`` is on the
+          ``PYTHONPATH`` so that the subprocess can import it to start the worker.
+          This fixes an issue with how ``zc.buildout`` injects dependencies into a
+          process which is done entirely by ``sys.path`` manipulation.
+          See https://github.com/Pylons/hupper/pull/27
+        
+        0.5 (2017-05-10)
+        ================
+        
+        - On non-windows systems ensure an exec occurs so that the worker does not
+          share the same process space as the reloader causing certain code that
+          is imported in both to not ever be reloaded. Under the hood this was a
+          significant rewrite to use subprocess instead of multiprocessing.
+          See https://github.com/Pylons/hupper/pull/23
+        
+        0.4.4 (2017-03-10)
+        ==================
+        
+        - Fix some versions of Windows which were failing to duplicate stdin to
+          the subprocess and crashing.
+          https://github.com/Pylons/hupper/pull/16
+        
+        0.4.3 (2017-03-07)
+        ==================
+        
+        - Fix pdb and other readline-based programs to operate properly.
+          See https://github.com/Pylons/hupper/pull/15
+        
         0.4.2 (2017-01-24)
         ==================
         
@@ -174,7 +205,7 @@ Description: ======
         
 Keywords: server daemon autoreload reloader hup file watch process
 Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
+Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Natural Language :: English
@@ -186,3 +217,4 @@ Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
diff --git a/src/hupper.egg-info/SOURCES.txt b/src/hupper.egg-info/SOURCES.txt
index f8288b1..8fc7b88 100644
--- a/src/hupper.egg-info/SOURCES.txt
+++ b/src/hupper.egg-info/SOURCES.txt
@@ -1,5 +1,4 @@
 .coveragerc
-.gitignore
 .travis.yml
 CHANGES.rst
 CONTRIBUTING.rst
diff --git a/src/hupper/compat.py b/src/hupper/compat.py
index c1dc23f..a87021e 100644
--- a/src/hupper/compat.py
+++ b/src/hupper/compat.py
@@ -6,11 +6,6 @@ import sys
 PY2 = sys.version_info[0] == 2
 WIN = sys.platform == 'win32'
 
-if PY2:
-    long = long
-else:
-    long = int
-
 try:
     import queue
 except ImportError:
@@ -38,6 +33,12 @@ except ImportError:
         get_py_path = imp.source_from_cache
 
 
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+
 def is_watchdog_supported():
     """ Return ``True`` if watchdog is available."""
     try:
diff --git a/src/hupper/ipc.py b/src/hupper/ipc.py
index 6f3890f..dcd7e82 100644
--- a/src/hupper/ipc.py
+++ b/src/hupper/ipc.py
@@ -1,6 +1,22 @@
+import io
+import imp
+import importlib
 import os
+import struct
+import sys
+import subprocess
+import threading
 
 from .compat import WIN
+from .compat import pickle
+from .compat import queue
+
+
+def resolve_spec(spec):
+    modname, funcname = spec.rsplit('.', 1)
+    module = importlib.import_module(modname)
+    func = getattr(module, funcname)
+    return func
 
 
 if WIN:  # pragma: no cover
@@ -25,19 +41,26 @@ if WIN:  # pragma: no cover
 
         def add_child(self, pid):
             hp = winapi.OpenProcess(winapi.PROCESS_ALL_ACCESS, False, pid)
-            return winapi.AssignProcessToJobObject(self.h_job, hp)
-
-    def send_fd(pipe, fd, pid):
-        hf = msvcrt.get_osfhandle(fd)
-        hp = winapi.OpenProcess(winapi.PROCESS_ALL_ACCESS, False, pid)
-        tp = winapi.DuplicateHandle(
-            winapi.GetCurrentProcess(), hf, hp,
-            0, False, winapi.DUPLICATE_SAME_ACCESS,
-        ).Detach()  # do not close the handle
-        pipe.send(tp)
-
-    def recv_fd(pipe, mode):
-        handle = pipe.recv()
+            try:
+                return winapi.AssignProcessToJobObject(self.h_job, hp)
+            except OSError as ex:
+                if getattr(ex, 'winerror') == 5:
+                    # skip ACCESS_DENIED_ERROR on windows < 8 which occurs when
+                    # the process is already attached to another job
+                    pass
+                else:
+                    raise
+
+    def snapshot_termios(fd):
+        pass
+
+    def restore_termios(fd, state):
+        pass
+
+    def get_handle(fd):
+        return msvcrt.get_osfhandle(fd)
+
+    def open_handle(handle, mode):
         flags = 0
         if 'w' not in mode and '+' not in mode:
             flags |= os.O_RDONLY
@@ -45,18 +68,256 @@ if WIN:  # pragma: no cover
             flags |= os.O_TEXT
         if 'a' in mode:
             flags |= os.O_APPEND
-        fd = msvcrt.open_osfhandle(handle, flags)
-        return os.fdopen(fd, mode)
+        return msvcrt.open_osfhandle(handle, flags)
 
 else:
+    import fcntl
+    import termios
+
     class ProcessGroup(object):
         def add_child(self, pid):
             # nothing to do on *nix
             pass
 
-    def send_fd(pipe, fd, pid):
-        pipe.send(fd)
+    def snapshot_termios(fd):
+        if os.isatty(fd):
+            state = termios.tcgetattr(fd)
+            return state
+
+    def restore_termios(fd, state):
+        if os.isatty(fd) and state:
+            termios.tcflush(fd, termios.TCIOFLUSH)
+            termios.tcsetattr(fd, termios.TCSANOW, state)
+
+    def get_handle(fd):
+        return fd
+
+    def open_handle(handle, mode):
+        return handle
+
+
+def _pipe():
+    r, w = os.pipe()
+    set_inheritable(r, False)
+    set_inheritable(w, False)
+    return r, w
+
+
+def Pipe():
+    c2pr_fd, c2pw_fd = _pipe()
+    p2cr_fd, p2cw_fd = _pipe()
+
+    c1 = Connection(c2pr_fd, p2cw_fd)
+    c2 = Connection(p2cr_fd, c2pw_fd)
+    return c1, c2
+
+
+class Connection(object):
+    """
+    A connection to a bi-directional pipe.
+
+    """
+    _packet_len = struct.Struct('Q')
+
+    def __init__(self, r_fd, w_fd):
+        self.r_fd = r_fd
+        self.w_fd = w_fd
+
+    def __getstate__(self):
+        return {
+            'r_handle': get_handle(self.r_fd),
+            'w_handle': get_handle(self.w_fd),
+        }
+
+    def __setstate__(self, state):
+        self.r_fd = open_handle(state['r_handle'], 'rb')
+        self.w_fd = open_handle(state['w_handle'], 'wb')
+
+    def activate(self):
+        self.send_lock = threading.Lock()
+        self.reader_queue = queue.Queue()
+
+        self.reader_thread = threading.Thread(target=self._read_loop)
+        self.reader_thread.daemon = True
+        self.reader_thread.start()
+
+    def close(self):
+        close_fd(self.r_fd)
+        close_fd(self.w_fd)
+
+    def _recv_packet(self):
+        buf = io.BytesIO()
+        chunk = os.read(self.r_fd, self._packet_len.size)
+        if not chunk:
+            return
+        size = remaining = self._packet_len.unpack(chunk)[0]
+        while remaining > 0:
+            chunk = os.read(self.r_fd, remaining)
+            n = len(chunk)
+            if n == 0:
+                if remaining == size:
+                    raise EOFError
+                else:
+                    raise IOError('got end of file during message')
+            buf.write(chunk)
+            remaining -= n
+        return pickle.loads(buf.getvalue())
+
+    def _read_loop(self):
+        try:
+            while True:
+                packet = self._recv_packet()
+                if packet is None:
+                    break
+                self.reader_queue.put(packet)
+        except EOFError:
+            pass
+        self.reader_queue.put(None)
+
+    def _write_packet(self, data):
+        while data:
+            n = os.write(self.w_fd, data)
+            data = data[n:]
+
+    def send(self, value):
+        data = pickle.dumps(value)
+        with self.send_lock:
+            self._write_packet(self._packet_len.pack(len(data)))
+            self._write_packet(data)
+        return len(data) + self._packet_len.size
+
+    def recv(self, timeout=None):
+        packet = self.reader_queue.get(block=True, timeout=timeout)
+        return packet
+
+
+def set_inheritable(fd, inheritable):
+    # On py34+ we can use os.set_inheritable but < py34 we must polyfill
+    # with fcntl and SetHandleInformation
+    if hasattr(os, 'get_inheritable'):
+        if os.get_inheritable(fd) != inheritable:
+            os.set_inheritable(fd, inheritable)
+
+    elif WIN:
+        h = get_handle(fd)
+        flags = winapi.HANDLE_FLAG_INHERIT if inheritable else 0
+        winapi.SetHandleInformation(h, winapi.HANDLE_FLAG_INHERIT, flags)
+
+    else:
+        flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+        if inheritable:
+            new_flags = flags & ~fcntl.FD_CLOEXEC
+        else:
+            new_flags = flags | fcntl.FD_CLOEXEC
+        if new_flags != flags:
+            fcntl.fcntl(fd, fcntl.F_SETFD, new_flags)
+
+
+def close_fd(fd, raises=True):
+    if fd is not None:
+        try:
+            os.close(fd)
+        except Exception:  # pragma: nocover
+            if raises:
+                raise
+
+
+def args_from_interpreter_flags():
+    """
+    Return a list of command-line arguments reproducing the current
+    settings in sys.flags and sys.warnoptions.
+
+    """
+    flag_opt_map = {
+        'debug': 'd',
+        'dont_write_bytecode': 'B',
+        'no_user_site': 's',
+        'no_site': 'S',
+        'ignore_environment': 'E',
+        'verbose': 'v',
+        'bytes_warning': 'b',
+        'quiet': 'q',
+        'optimize': 'O',
+    }
+    args = []
+    for flag, opt in flag_opt_map.items():
+        v = getattr(sys.flags, flag, 0)
+        if v > 0:
+            args.append('-' + opt * v)
+    for opt in sys.warnoptions:
+        args.append('-W' + opt)
+    return args
+
+
+def get_command_line(**kwds):
+    prog = 'from hupper.ipc import spawn_main; spawn_main(%s)'
+    prog %= ', '.join('%s=%r' % item for item in kwds.items())
+    opts = args_from_interpreter_flags()
+    args = [sys.executable] + opts + ['-c', prog]
+
+    # ensure hupper is on the PYTHONPATH in the worker process
+    self_path = os.path.abspath(imp.find_module('hupper')[1])
+    extra_py_paths = [os.path.dirname(self_path)]
+
+    env = os.environ.copy()
+    env['PYTHONPATH'] = (
+        os.pathsep.join(extra_py_paths) +
+        os.pathsep +
+        env.get('PYTHONPATH', '')
+    )
+    return args, env
+
+
+def get_preparation_data():
+    data = {}
+    data['sys.argv'] = sys.argv
+
+    # multiprocessing does some work here to replace '' in sys.path with
+    # os.getcwd() but it is not valid to assume that os.getcwd() at the time
+    # hupper is imported is the starting folder of the process so for now
+    # we'll just assume that the user has not changed the CWD
+    data['sys.path'] = list(sys.path)
+    return data
+
+
+def prepare(data):
+    if 'sys.argv' in data:
+        sys.argv = data['sys.argv']
+
+    if 'sys.path' in data:
+        sys.path = data['sys.path']
+
+
+def spawn(spec, kwargs, pass_fds=()):
+    """
+    Invoke a python function in a subprocess.
+
+    """
+    r, w = os.pipe()
+    for fd in [r] + list(pass_fds):
+        set_inheritable(fd, True)
+
+    preparation_data = get_preparation_data()
+
+    r_handle = get_handle(r)
+    args, env = get_command_line(pipe_handle=r_handle)
+    process = subprocess.Popen(args, env=env, close_fds=False)
+
+    to_child = os.fdopen(w, 'wb')
+    to_child.write(pickle.dumps([preparation_data, spec, kwargs]))
+    to_child.close()
+
+    return process
+
+
+def spawn_main(pipe_handle):
+    fd = open_handle(pipe_handle, 'rb')
+    from_parent = os.fdopen(fd, 'rb')
+    preparation_data, spec, kwargs = pickle.load(from_parent)
+    from_parent.close()
+
+    prepare(preparation_data)
 
-    def recv_fd(pipe, mode):
-        fd = pipe.recv()
-        return os.fdopen(fd, mode)
+    func = resolve_spec(spec)
+    func(**kwargs)
+    sys.exit(0)
diff --git a/src/hupper/reloader.py b/src/hupper/reloader.py
index 8e3625e..b9c1551 100644
--- a/src/hupper/reloader.py
+++ b/src/hupper/reloader.py
@@ -6,10 +6,8 @@ import sys
 import threading
 import time
 
-from .compat import (
-    is_watchdog_supported,
-    queue,
-)
+from .compat import is_watchdog_supported
+from .compat import queue
 from .ipc import ProcessGroup
 from .worker import (
     Worker,
@@ -152,26 +150,26 @@ class Reloader(object):
 
             while not self.monitor.is_changed() and self.worker.is_alive():
                 try:
-                    # if the child has sent any data then restart
-                    if self.worker.pipe.poll(0):
-                        # do not read, the pipe is closed after the break
-                        break
-                except EOFError:  # pragma: nocover
-                    pass
-
-                try:
-                    path = self.worker.files_queue.get(
-                        timeout=self.reload_interval,
-                    )
+                    cmd = self.worker.pipe.recv(timeout=self.reload_interval)
                 except queue.Empty:
-                    pass
+                    continue
+
+                if not cmd or cmd[0] == 'reload':
+                    break
+
+                if cmd[0] == 'watch':
+                    for path in cmd[1]:
+                        self.monitor.add_path(path)
+
                 else:
-                    self.monitor.add_path(path)
+                    raise RuntimeError('received unknown command')
+
         except KeyboardInterrupt:
             if self.worker.is_alive():
                 self.out('Waiting for server to exit ...')
                 time.sleep(self.reload_interval)
             raise
+
         finally:
             if self.worker.is_alive():
                 self.out('Killing server with PID %s.' % self.worker.pid)
diff --git a/src/hupper/winapi.py b/src/hupper/winapi.py
index 54b9c0d..19de572 100644
--- a/src/hupper/winapi.py
+++ b/src/hupper/winapi.py
@@ -7,8 +7,10 @@ if ctypes.sizeof(ctypes.c_void_p) == 8:
     ULONG_PTR = ctypes.c_int64
 else:
     ULONG_PTR = ctypes.c_ulong
+BOOL = wintypes.BOOL
 DWORD = wintypes.DWORD
-LARGE_INTEGER = ctypes.c_int64
+HANDLE = wintypes.HANDLE
+LARGE_INTEGER = wintypes.LARGE_INTEGER
 SIZE_T = ULONG_PTR
 ULONGLONG = ctypes.c_uint64
 
@@ -45,6 +47,9 @@ PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF
 
 DUPLICATE_SAME_ACCESS = 0x0002
 
+HANDLE_FLAG_INHERIT = 0x0001
+HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x0002
+
 
 class IO_COUNTERS(ctypes.Structure):
     _fields_ = [
@@ -82,7 +87,7 @@ class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(ctypes.Structure):
     ]
 
 
-class Handle(wintypes.HANDLE):
+class Handle(HANDLE):
     closed = False
 
     def Close(self):
@@ -119,7 +124,9 @@ def DuplicateHandle(
     targetHandle = wintypes.HANDLE()
     ret = kernel32.DuplicateHandle(
         hSourceProcess, hSourceHandle, hTargetProcess,
-        ctypes.byref(targetHandle), desiredAccess, inheritHandle, options)
+        ctypes.byref(targetHandle),
+        desiredAccess, inheritHandle, options,
+    )
     CheckError(ret, 'failed to duplicate handle')
     return Handle(targetHandle.value)
 
@@ -152,3 +159,8 @@ def SetInformationJobObject(hJob, infoType, jobObjectInfo):
 def AssignProcessToJobObject(hJob, hProcess):
     ret = kernel32.AssignProcessToJobObject(hJob, hProcess)
     CheckError(ret, 'failed to assign process to job object')
+
+
+def SetHandleInformation(h, dwMask, dwFlags):
+    ret = kernel32.SetHandleInformation(h, dwMask, dwFlags)
+    CheckError(ret, 'failed to set handle information')
\ No newline at end of file
diff --git a/src/hupper/worker.py b/src/hupper/worker.py
index 0792888..bc47878 100644
--- a/src/hupper/worker.py
+++ b/src/hupper/worker.py
@@ -1,21 +1,15 @@
-import importlib
-import multiprocessing
 import os
+import signal
 import sys
 import threading
 import time
 import traceback
 
-from .compat import (
-    interrupt_main,
-    get_pyc_path,
-    get_py_path,
-)
+from .compat import get_pyc_path
+from .compat import get_py_path
+from .compat import interrupt_main
 from .interfaces import IReloaderProxy
-from .ipc import (
-    recv_fd,
-    send_fd,
-)
+from . import ipc
 
 
 class WatchSysModules(threading.Thread):
@@ -38,23 +32,30 @@ class WatchSysModules(threading.Thread):
         self.stopped = True
 
     def update_paths(self):
-        """Check sys.modules for paths to add to our path set."""
+        """ Check sys.modules for paths to add to our path set."""
+        new_paths = []
         with self.lock:
-            for path in iter_source_paths(iter_module_paths()):
+            for path in expand_source_paths(iter_module_paths()):
                 if path not in self.paths:
                     self.paths.add(path)
-                    self.callback(path)
+                    new_paths.append(path)
+        if new_paths:
+            self.callback(new_paths)
 
     def search_traceback(self, tb):
+        """ Inspect a traceback for new paths to add to our path set."""
+        new_paths = []
         with self.lock:
             for filename, line, funcname, txt in traceback.extract_tb(tb):
                 path = os.path.abspath(filename)
                 if path not in self.paths:
                     self.paths.add(path)
-                    self.callback(path)
+                    new_paths.append(path)
+        if new_paths:
+            self.callback(new_paths)
 
 
-def iter_source_paths(paths):
+def expand_source_paths(paths):
     """ Convert pyc files into their source equivalents."""
     for src_path in paths:
         yield src_path
@@ -95,7 +96,7 @@ class WatchForParentShutdown(threading.Thread):
     def run(self):
         try:
             # wait until the pipe breaks
-            while self.pipe.recv_bytes():  # pragma: nocover
+            while self.pipe.recv():  # pragma: nocover
                 pass
         except EOFError:
             pass
@@ -109,48 +110,39 @@ class Worker(object):
         self.worker_spec = spec
         self.worker_args = args
         self.worker_kwargs = kwargs
-        self.files_queue = multiprocessing.Queue()
-        self.pipe, self._c2p = multiprocessing.Pipe()
+        self.pipe, self._child_pipe = ipc.Pipe()
         self.terminated = False
         self.pid = None
+        self.process = None
         self.exitcode = None
+        self.stdin_termios = None
 
     def start(self):
-        # prepare to close our stdin by making a new copy that is
-        # not attached to sys.stdin - we will pass this to the worker while
-        # it's running and then restore it when the worker is done
-        # we dup it early such that it's inherited by the child
-        self.stdin_fd = os.dup(sys.stdin.fileno())
-
-        # py34 and above sets CLOEXEC automatically on file descriptors
-        # NOTE: this isn't usually an issue because multiprocessing doesn't
-        # actually exec on linux/macos, but we're depending on the behavior
-        if hasattr(os, 'set_inheritable'):  # pragma: nocover
-            os.set_inheritable(self.stdin_fd, True)
+        self.stdin_termios = ipc.snapshot_termios(sys.stdin.fileno())
 
         kw = dict(
... 241 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-hupper.git



More information about the Python-modules-commits mailing list