[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