From commits-noreply at bitbucket.org Thu Oct 1 12:08:05 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 1 Oct 2009 10:08:05 +0000 (UTC)
Subject: [execnet-commit] execnet commit bf9200d244db: remove superflous old
tags
Message-ID: <20091001100805.D5DF0710A9@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1254391159 -7200
# Node ID bf9200d244dbf8a12de6a499cae2b65413f7915d
# Parent 2e79990d3390d1e19769cd1749fa474b17d9e71c
remove superflous old tags
--- a/.hgtags
+++ b/.hgtags
@@ -1,7 +0,0 @@
-6b41b610bcde267f4e8bbcaebbfc5f5dd48eb6bb 1.0.0
-6b41b610bcde267f4e8bbcaebbfc5f5dd48eb6bb 1.0.0b8
-6b41b610bcde267f4e8bbcaebbfc5f5dd48eb6bb 1.0.0b9
-8a4d9daf73aab2b1ea8f77d06bcf11b939eca70f 1.0.0b3
-9d205ffed94fa0318548a7e727fd67d7dda6f7fa 1.0.0b6
-a598d3e3e0196c73665377921ff22f0c4f74b2a5 1.0.1
-a598d3e3e0196c73665377921ff22f0c4f74b2a5 1.0.2
From commits-noreply at bitbucket.org Thu Oct 1 18:27:14 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 1 Oct 2009 16:27:14 +0000 (UTC)
Subject: [execnet-commit] execnet commit 50df8d9326a2: [mq]: doc
Message-ID: <20091001162714.8B01D71099@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1254392265 -7200
# Node ID 50df8d9326a2a6d9514fac0a22535c6e579d48d7
# Parent bf9200d244dbf8a12de6a499cae2b65413f7915d
[mq]: doc
From commits-noreply at bitbucket.org Thu Oct 1 18:27:16 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 1 Oct 2009 16:27:16 +0000 (UTC)
Subject: [execnet-commit] execnet commit 9c03758bf580: * improved
documentation with graphics, new site layout
Message-ID: <20091001162716.99E5C710AD@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1254414087 -7200
# Node ID 9c03758bf58043d68850d41075d8b0d321e93a58
# Parent 50df8d9326a2a6d9514fac0a22535c6e579d48d7
* improved documentation with graphics, new site layout
* remove remaining usages of py lib
* create setup.py and other missing bits
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -236,24 +236,24 @@ class SocketGateway(Gateway):
super(SocketGateway, self).__init__(io=io)
def new_remote(cls, gateway, hostport=None):
- """ return a new (connected) socket gateway, instatiated
- indirectly through the given 'gateway'.
+ """ return a new (connected) socket gateway,
+ instantiated through the given 'gateway'.
"""
if hostport is None:
host, port = ('', 0) # XXX works on all platforms?
else:
host, port = hostport
- import py
- mydir = py.path.local(__file__).dirpath()
- socketserverbootstrap = py.code.Source(
- mydir.join('script', 'socketserver.py').read('r'), """
+
+ mydir = os.path.dirname(__file__)
+ socketserver = os.path.join(mydir, 'script', 'socketserver.py')
+ socketserverbootstrap = "\n".join([
+ open(socketserver, 'r').read(), """if 1:
import socket
sock = bind_and_listen((%r, %r))
port = sock.getsockname()
channel.send(port)
startserver(sock)
- """ % (host, port)
- )
+ """ % (host, port)])
# execute the above socketserverbootstrap on the other side
channel = gateway.remote_exec(socketserverbootstrap)
(realhost, realport) = channel.receive()
Binary file doc/_static/execnet.png has changed
Binary file doc/_static/jython.png has changed
--- /dev/null
+++ b/doc/_templates/layout.html
@@ -0,0 +1,14 @@
+{% extends "!layout.html" %}
+
+{% block rootrellink %}
+ home |
+ issues |
+ pypi |
+ Documentation »
+{% endblock %}
+
+{% block header %}
+
+
+
+{% endblock %}
--- a/testing/test_rsync.py
+++ b/testing/test_rsync.py
@@ -1,5 +1,5 @@
import py
-from execnet.rsync import RSync
+from execnet import RSync
import execnet
def pytest_funcarg__gw1(request):
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -12,3 +12,4 @@ from execnet.gateway import PopenGateway
from execnet.gateway import HostNotFound
from execnet.xspec import makegateway, XSpec
from execnet.multi import MultiGateway,MultiChannel
+from execnet.rsync import RSync
Binary file doc/_static/basic1.png has changed
Binary file doc/_static/python-powered-h-70x91.png has changed
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -1,49 +1,57 @@
-==============================================================================
-execnet: Elastic Python Deployment.
-==============================================================================
+.. image:: _static/pythonring.png
+ :align: right
-the execnet package allows to:
+Welcome to execnet!
+=========================
-* ad-hoc instantiate local or remote Python Interpreters
+The execnet package allows to:
+
+* instantiate local/remote Python Interpreters
* send code for execution to one or many Interpreters
-* send and receive data between Interpreters through channels
+* send and receive data between codeInterpreters through channels
-execnet uses a **zero-install self-bootstrapping** technique such that
-no manual installation steps are required on remote places. A
-basic connection method and a Python executable suffices. The following
-interpreters can be connected with each other:
+execnet performs **zero-install bootstrapping** into other interpreters;
+package installation is only required at the initiating side. execnet enables
+interoperation between CPython 2.4-3.1, Jython 2.5 and PyPy 1.1 and works
+well on Windows, Linux and OSX systems.
-* |cpython| CPython versions, 2.4, 2.5, 2.6, 2.7, 3.0 and 3.1
-* |jython| Jython 2.5.1
-* |pypy| PyPy 1.1 and trunk versions
+execnet was written and is maintained by Holger Krekel with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
+Getting Started
+====================
-.. |jython| image:: _static/jython.png
- :target: http://jython.org
- :align: middle
+To install a public `pypi release`_ via `easy_install`_::
-.. |cpython| image:: _static/python-powered-h-70x91.png
- :target: http://python.org
- :align: middle
+ easy_install -U execnet
-.. |pypy| image:: _static/py-web1.png
- :width: 75
- :height: 55
- :target: http://pypy.org
- :align: middle
+To work from the development tree checkout the `execnet mercurial repository`_.
-Contents:
+Next checkout the basic api and examples:
.. toctree::
- :maxdepth: 2
+ :maxdepth: 1
basics
examples
+ changelog
-Indices and tables
-==================
+Contact channels
+===========================
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+You are welcome to:
+* join and post to the `execnet-dev`_ mailing list or `open an issue`_
+* join the #pylib IRC channel on Freenode
+* subscribe to the `execnet-commit`_ mailing list
+
+.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
+.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
+.. _`open an issue`: http://bitbucket.org/hpk42/execnet/issues/
+
+.. _`easy_install`: http://peak.telecommunity.com/DevCenter/EasyInstall
+.. _`execnet mercurial repository`: http://bitbucket.org/hpk42/execnet/
+.. _`pypi release`: http://pypi.python.org/pypi/execnet
+
+
+
+
--- a/execnet/rsync.py
+++ b/execnet/rsync.py
@@ -3,10 +3,20 @@ 1:N rsync implemenation on top of execne
(c) 2006-2009, Armin Rigo, Holger Krekel, Maciej Fijalkowski
"""
-import py, os, stat
+import os, stat
-md5 = py.builtin._tryimport('hashlib', 'md5').md5
-Queue = py.builtin._tryimport('queue', 'Queue').Queue
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
+
+try:
+ from queue import Queue
+except ImportError:
+ from Queue import Queue
+
+remote_file = os.path.join(os.path.dirname(__file__), 'rsync_remote.py')
+REMOTE_SOURCE = open(remote_file, 'r').read() + "\nf()"
class RSync(object):
""" This class allows to send a directory structure (recursively)
@@ -20,7 +30,7 @@ class RSync(object):
def __init__(self, sourcedir, callback=None, verbose=True):
self._sourcedir = str(sourcedir)
self._verbose = verbose
- assert callback is None or py.builtin.callable(callback)
+ assert callback is None or hasattr(callback, '__call__')
self._callback = callback
self._channels = {}
self._receivequeue = Queue()
@@ -134,10 +144,9 @@ class RSync(object):
def add_target(self, gateway, destdir,
finishedcallback=None, **options):
- """ Adds a remote target specified via a 'gateway'
+ """ Adds a remote target specified via a gateway
and a remote destination directory.
"""
- assert finishedcallback is None or py.builtin.callable(finishedcallback)
for name in options:
assert name in ('delete',)
def itemcallback(req):
@@ -196,6 +205,3 @@ class RSync(object):
else:
raise ValueError("cannot sync %r" % (path,))
-REMOTE_SOURCE = py.path.local(__file__).dirpath().\
- join('rsync_remote.py').open().read() + "\nf()"
-
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,48 @@
+"""
+the execnet package allows to:
+
+* instantiate local/remote Python Interpreters
+* send code for execution to one or many Interpreters
+* send and receive data between codeInterpreters through channels
+
+execnet performs **zero-install bootstrapping** into other interpreters;
+package installation is only required at the initiating side. execnet enables
+interoperation between CPython 2.4-3.1, Jython 2.5 and PyPy 1.1 and works
+well on Windows, Linux and OSX systems.
+
+execnet was written and is maintained by Holger Krekel with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
+"""
+
+import os, sys
+from distutils.core import setup
+
+from execnet import __version__
+
+def main():
+ setup(
+ name='execnet',
+ description='execnet: elastic Python deployment',
+ long_description = __doc__,
+ version= __version__,
+ url='http://codespeak.net/execnet',
+ license='GPL V2 or later',
+ platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
+ author='holger krekel and others',
+ author_email='holger at merlinux.eu',
+ classifiers=[
+ 'Development Status :: 3 - Alpha',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU General Public License (GPL)',
+ 'Operating System :: POSIX',
+ 'Operating System :: Microsoft :: Windows',
+ 'Operating System :: MacOS :: MacOS X',
+ 'Topic :: Software Development :: Testing',
+ 'Topic :: Software Development :: Libraries',
+ 'Topic :: System :: Distributed Computing',
+ 'Topic :: System :: Networking',
+ 'Programming Language :: Python'],
+ packages=['execnet', 'execnet.script'],
+ )
+
+if __name__ == '__main__':
+ main()
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -4,7 +4,11 @@ execnet API in a nutshell
execnet allows to create **gateways** which establish
a self-bootstrapping connection to another
-local or remote Python interpreter.
+local or remote Python interpreter. Through this
+connection you can execute code on the other side
+and maintain a data exchange via channels.
+
+.. image:: _static/basic1.png
Gateways: connecting to another Python Interpreter
===================================================
@@ -49,7 +53,7 @@ Here is an self-contained example for re
.. _`channel-api`:
.. _`exchange data`:
-Exchanging data with Channels
+Channels: exchanging data with remote code
=======================================================
.. currentmodule:: execnet.gateway_base
@@ -106,3 +110,20 @@ Here are some examples for valid specifi
process that listens on 192.168.1.4:8888; current dir will be the
'pyexecnet-cache' sub directory which is used a default for all remote
processes.
+
+rsync: synchronise filesystem with remote
+===============================================================
+
+``execnet`` implements a simple efficient rsyncing protocol.
+Here is a basic example::
+
+ rsync = execnet.RSync('/tmp/source')
+ gw = execnet.PopenGateway()
+ rsync.add_target(gw, '/tmp/dest')
+ rsync.send()
+
+More info on the RSync class:
+
+.. currentmodule:: execnet
+.. autoclass:: RSync
+ :members: add_target,send
--- /dev/null
+++ b/doc/_templates/indexsidebar.html
@@ -0,0 +1,21 @@
+Download
+{% if version.endswith('(hg)') %}
+This documentation is for version {{ version }} , which is
+ not released yet.
+You can use it from the
+ Mercurial repo or look for
+ released versions in the Python
+ Package Index .
+{% else %}
+Current version: {{ version }}
+Get execnet from the Python Package
+Index , or install it with:
+easy_install -U execnet
+{% endif %}
+
+Questions? Suggestions?
+
+Join
+ execnet-dev mailing list
+open an issue .
+come to #pylib on FreeNode
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,11 @@
-* use sphinx for documentation, improve docs and docstrings
+1.0.0alpha1
+----------------------------
+
+* improve documentation, new website
+
+* use sphinx for documentation, added boilerplate files and setup.py
* fixes for standalone usage, adding boilerplate files
-* imported py/execnet and related bits
+* imported py/execnet and made it work standalone
Binary file doc/_static/pythonring.png has changed
--- /dev/null
+++ b/doc/changelog.txt
@@ -0,0 +1,8 @@
+:tocdepth: 2
+
+.. _changes:
+
+execnet CHANGELOG
+********************
+
+.. include:: ../CHANGELOG
--- a/execnet/script/socketserver.py
+++ b/execnet/script/socketserver.py
@@ -20,8 +20,6 @@ if debug: # and not os.isatty(sys.stdin
f = open('/tmp/execnet-socket-pyout.log', 'w')
old = sys.stdout, sys.stderr
sys.stdout = sys.stderr = f
- #import py
- #compile = py.code.compile
def print_(*args):
print(" ".join(str(arg) for arg in args))
Binary file doc/_static/py-web1.png has changed
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -74,7 +74,7 @@ exclude_trees = ['_build']
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
-#add_module_names = True
+add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
@@ -93,6 +93,12 @@ pygments_style = 'sphinx'
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'sphinxdoc'
+#html_index = 'index.html'
+#html_sidebars = {'index': 'indexsidebar.html',
+# 'basics': 'indexsidebar.html',
+#}
+#html_additional_pages = {'index': 'index.html'}
+
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
@@ -130,15 +136,8 @@ html_static_path = ['_static']
# typographically correct entities.
#html_use_smartypants = True
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
# If false, no module index is generated.
-html_use_modindex = True
+html_use_modindex = False
# If false, no index is generated.
#html_use_index = True
@@ -147,7 +146,7 @@ html_use_modindex = True
#html_split_index = False
# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+html_show_sourcelink = False
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
--- a/.hgignore
+++ b/.hgignore
@@ -1,1 +1,4 @@
doc/_build
+build/
+execnet.egg-info/
+dist/
From commits-noreply at bitbucket.org Fri Oct 2 12:59:53 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Fri, 2 Oct 2009 10:59:53 +0000 (UTC)
Subject: [execnet-commit] execnet commit 70834ed6a5b0: * typos and fixes
Message-ID: <20091002105953.F1A7A71099@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1254415920 -7200
# Node ID 70834ed6a5b02d8a2b947a4b3142002dc3093650
# Parent 9c03758bf58043d68850d41075d8b0d321e93a58
* typos and fixes
* add google analytics key
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -8,14 +8,14 @@ The execnet package allows to:
* instantiate local/remote Python Interpreters
* send code for execution to one or many Interpreters
-* send and receive data between codeInterpreters through channels
+* send and receive data through channels
execnet performs **zero-install bootstrapping** into other interpreters;
package installation is only required at the initiating side. execnet enables
interoperation between CPython 2.4-3.1, Jython 2.5 and PyPy 1.1 and works
well on Windows, Linux and OSX systems.
-execnet was written and is maintained by Holger Krekel with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
+execnet was written and is maintained by `Holger Krekel`_ with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
Getting Started
====================
@@ -44,6 +44,7 @@ You are welcome to:
* join the #pylib IRC channel on Freenode
* subscribe to the `execnet-commit`_ mailing list
+.. _`Holger Krekel`: http://tetamap.wordpress.com
.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
.. _`open an issue`: http://bitbucket.org/hpk42/execnet/issues/
--- /dev/null
+++ b/doc/docindex.txt
@@ -0,0 +1,10 @@
+
+Documentation Index
+============================
+
+.. toctree::
+ :maxdepth: 2
+
+ basics
+ examples
+ changelog
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -2,6 +2,33 @@
execnet examples
==============================================================================
+Work with Java objects from CPython
+----------------------------------------
+
+Use your CPython interpreter to connect to a Jython_ interpreter
+and work with Java types::
+
+ import execnet
+ gw = execnet.PopenGateway("jython")
+ channel = gw.remote_exec("""
+ from java.util import Vector
+ v = Vector()
+ v.add('aaa')
+ v.add('bbb')
+ for val in v:
+ channel.send(val)
+ """)
+
+ for item in channel:
+ print (item)
+
+will print on the CPython side::
+
+ aaa
+ bbb
+
+.. _Jython: http://www.jython.org
+
Compare cwd() of Popen Gateways
----------------------------------------
--- a/doc/_templates/layout.html
+++ b/doc/_templates/layout.html
@@ -2,9 +2,11 @@
{% block rootrellink %}
home |
+ api |
+ examples | issues | pypi |
- Documentation »
+ docindex »
{% endblock %}
{% block header %}
@@ -12,3 +14,16 @@
{% endblock %}
+
+{% block footer %}
+{{ super() }}
+
+
+{% endblock %}
From commits-noreply at bitbucket.org Fri Oct 2 17:00:18 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Fri, 2 Oct 2009 15:00:18 +0000 (UTC)
Subject: [execnet-commit] execnet commit fa4978439179: alpha2: some fixes
triggered by making pylib and py.test work
Message-ID: <20091002150018.7521E710A3@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1254494678 -7200
# Node ID fa4978439179100c8a65b2e59cd21d9dcceecd78
# Parent 70834ed6a5b02d8a2b947a4b3142002dc3093650
alpha2: some fixes triggered by making pylib and py.test work
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,5 @@
-1.0.0alpha1
+1.0.0alpha2
----------------------------
* improve documentation, new website
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -3,10 +3,9 @@ Support for working with multiple channe
(c) 2008-2009, Holger Krekel and others
"""
-try:
- import queue
-except ImportError:
- import Queue as queue
+
+import sys
+from execnet.gateway_base import queue, reraise
NO_ENDMARKER_WANTED = object()
@@ -65,4 +64,4 @@ class MultiChannel:
if first is None:
first = sys.exc_info()
if first:
- py.builtin._reraise(first[0], first[1], first[2])
+ reraise(*first)
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -41,15 +41,14 @@ except ImportError:
import Queue as queue
if sys.version_info > (3, 0):
- exec("""def do_exec(co, loc):
- exec(co, loc)""")
+ exec("def do_exec(co, loc): exec(co, loc)\n"
+ "def reraise(cls, val, tb): raise val\n")
unicode = str
else:
- exec("""def do_exec(co, loc):
- exec co in loc""")
+ exec("def do_exec(co, loc): exec co in loc\n"
+ "def reraise(cls, val, tb): raise cls, val, tb\n")
bytes = str
-
def str(*args):
raise EnvironmentError(
"use unicode or bytes, not cross-python ambigous 'str'")
--- a/execnet/threadpool.py
+++ b/execnet/threadpool.py
@@ -17,7 +17,6 @@ if sys.version_info >= (3,0):
else:
exec ("def reraise(cls, val, tb): raise cls, val, tb")
-
ERRORMARKER = object()
class Reply(object):
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -5,7 +5,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.0alpha"
+__version__ = "1.0.0alpha2"
__author__ = "holger krekel and others"
from execnet.gateway import PopenGateway, SocketGateway, SshGateway
--- a/.hgignore
+++ b/.hgignore
@@ -2,3 +2,6 @@ doc/_build
build/
execnet.egg-info/
dist/
+
+syntax:glob
+.py
From commits-noreply at bitbucket.org Fri Oct 16 22:58:40 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Fri, 16 Oct 2009 20:58:40 +0000 (UTC)
Subject: [execnet-commit] execnet commit ba43d965f3e4: ignore pyc files
Message-ID: <20091016205840.52EA17EF10@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1254566104 -7200
# Node ID ba43d965f3e4b6b88223295d1e63bbc7e203c8eb
# Parent fa4978439179100c8a65b2e59cd21d9dcceecd78
ignore pyc files
--- a/.hgignore
+++ b/.hgignore
@@ -4,4 +4,4 @@ execnet.egg-info/
dist/
syntax:glob
-.py
+*.pyc
From commits-noreply at bitbucket.org Fri Oct 16 22:58:41 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Fri, 16 Oct 2009 20:58:41 +0000 (UTC)
Subject: [execnet-commit] execnet commit 97dcb89f2ea2: fix workerpool usage
Message-ID: <20091016205841.E662A7EF4B@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1255357563 -7200
# Node ID 97dcb89f2ea2421ded744b1e78247bef53b99245
# Parent ba43d965f3e4b6b88223295d1e63bbc7e203c8eb
fix workerpool usage
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -5,6 +5,7 @@ import os, sys, time
import py
import execnet
from execnet import gateway_base, gateway
+from execnet.threadpool import WorkerPool
queue = py.builtin._tryimport('queue', 'Queue')
TESTTIMEOUT = 10.0 # seconds
@@ -400,7 +401,7 @@ def test_join_blocked_execution_gateway(
gateway.join(joinexec=True)
return 17
- pool = py._thread.WorkerPool()
+ pool = WorkerPool()
reply = pool.dispatch(doit)
x = reply.get(timeout=1.0)
assert x == 17
From commits-noreply at bitbucket.org Fri Oct 16 22:58:41 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Fri, 16 Oct 2009 20:58:41 +0000 (UTC)
Subject: [execnet-commit] execnet commit ab11b55dc82b: adding ironpython
example
Message-ID: <20091016205841.F0EF27EF4D@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1255724987 -7200
# Node ID ab11b55dc82b47e420c663dfa361af754c753ce8
# Parent 97dcb89f2ea2421ded744b1e78247bef53b99245
adding ironpython example
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -12,11 +12,16 @@ The execnet package allows to:
execnet performs **zero-install bootstrapping** into other interpreters;
package installation is only required at the initiating side. execnet enables
-interoperation between CPython 2.4-3.1, Jython 2.5 and PyPy 1.1 and works
-well on Windows, Linux and OSX systems.
+interoperation between CPython 2.4-3.1, Jython 2.5.1, PyPy 1.1 and IronPython
+and works well on Windows, Linux and OSX systems.
execnet was written and is maintained by `Holger Krekel`_ with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
+.. note::
+ Support of Jython and IronPython are experimental at this point.
+ Feedback and help with testing welcome.
+
+
Getting Started
====================
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -29,6 +29,35 @@ will print on the CPython side::
.. _Jython: http://www.jython.org
+Work with C# objects from CPython
+----------------------------------------
+
+(Experimental) use your CPython interpreter to connect to a IronPython_ interpreter
+which can work with C# classes. Here is an example for instantiating
+a CLR Array instance and sending back its representation::
+
+ import execnet
+ gw = execnet.PopenGateway("ipy")
+
+ channel = gw.remote_exec("""
+ import clr
+ clr.AddReference("System")
+ from System import Array
+ array = Array[float]([1,2])
+ channel.send(str(array))
+ """)
+ print (channel.receive())
+
+using Mono 2.0 and IronPython-1.1 this will print on the CPython side::
+
+ System.Double[](1.0, 2.0)
+
+.. note::
+ Using IronPython needs more testing, likely newer versions
+ will work better. please feedback if you have information.
+
+.. _IronPython: http://www.IronPython.org
+
Compare cwd() of Popen Gateways
----------------------------------------
From commits-noreply at bitbucket.org Thu Oct 29 20:58:32 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 29 Oct 2009 19:58:32 +0000 (UTC)
Subject: [execnet-commit] execnet commit 69505782d0fd: heading towards a
1.0b1
Message-ID: <20091029195832.BDD117EFFC@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1256846251 -3600
# Node ID 69505782d0fdcdd674deaf0a035df5055e75caf2
# Parent ab11b55dc82b47e420c663dfa361af754c753ce8
heading towards a 1.0b1
- fixing python2.5 compat
- fixing windows / rsyncing config
- adding some examples
--- /dev/null
+++ b/doc/example/svn-sync-repo.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python
+
+"""
+
+small utility for hot-syncing a svn repository through ssh.
+uses execnet.
+
+"""
+
+import py
+import sys, os
+
+def usage():
+ arg0 = sys.argv[0]
+ print """%s [user@]remote-host:/repo/location localrepo [identity keyfile]""" % (arg0,)
+
+
+def main(args):
+ remote = args[0]
+ localrepo = py.path.local(args[1])
+ if not localrepo.check(dir=1):
+ raise SystemExit("localrepo %s does not exist" %(localrepo,))
+ if len(args) == 3:
+ keyfile = py.path.local(args[2])
+ else:
+ keyfile = None
+ remote_host, path = remote.split(':', 1)
+ print "ssh-connecting to", remote_host
+ gw = getgateway(remote_host, keyfile)
+
+ local_rev = get_svn_youngest(localrepo)
+
+ # local protocol
+ # 1. client sends rev/repo -> server
+ # 2. server checks for newer revisions and sends dumps
+ # 3. client receives dumps, updates local repo
+ # 4. client goes back to step 1
+ c = gw.remote_exec("""
+ import py
+ import os
+ remote_rev, repopath = channel.receive()
+ while 1:
+ rev = py.process.cmdexec('svnlook youngest "%s"' % repopath)
+ rev = int(rev)
+ if rev > remote_rev:
+ revrange = (remote_rev+1, rev)
+ dumpchannel = channel.gateway.newchannel()
+ channel.send(revrange)
+ channel.send(dumpchannel)
+
+ f = os.popen(
+ "svnadmin dump -q --incremental -r %s:%s %s"
+ % (revrange[0], revrange[1], repopath), 'r')
+ try:
+ maxcount = dumpchannel.receive()
+ count = maxcount
+ while 1:
+ s = f.read(8192)
+ if not s:
+ raise EOFError
+ dumpchannel.send(s)
+ count = count - 1
+ if count <= 0:
+ ack = dumpchannel.receive()
+ count = maxcount
+
+ except EOFError:
+ dumpchannel.close()
+ remote_rev = rev
+ else:
+ # using svn-hook instead would be nice here
+ py.std.time.sleep(30)
+ """)
+
+ c.send((local_rev, path))
+ print "checking revisions from %d in %s" %(local_rev, remote)
+ while 1:
+ revstart, revend = c.receive()
+ dumpchannel = c.receive()
+ print "receiving revisions", revstart, "-", revend, "replaying..."
+ svn_load(localrepo, dumpchannel)
+ print "current revision", revend
+
+def svn_load(repo, dumpchannel, maxcount=100):
+ # every maxcount we will send an ACK to the other
+ # side in order to synchronise and avoid our side
+ # growing buffers (py.execnet does not control
+ # RAM usage or receive queue sizes)
+ dumpchannel.send(maxcount)
+ f = os.popen("svnadmin load -q %s" %(repo, ), "w")
+ count = maxcount
+ for x in dumpchannel:
+ sys.stdout.write(".")
+ sys.stdout.flush()
+ f.write(x)
+ count = count - 1
+ if count <= 0:
+ dumpchannel.send(maxcount)
+ count = maxcount
+ print >>sys.stdout
+ f.close()
+
+def get_svn_youngest(repo):
+ rev = py.process.cmdexec('svnlook youngest "%s"' % repo)
+ return int(rev)
+
+def getgateway(host, keyfile=None):
+ return execnet.SshGateway(host, identity=keyfile)
+
+if __name__ == '__main__':
+ if len(sys.argv) < 3:
+ usage()
+ raise SystemExit(1)
+
+ main(sys.argv[1:])
+
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -510,7 +510,7 @@ class TestSshPopenGateway:
def test_sshaddress(self, gw, specssh):
assert gw.remoteaddress == specssh.ssh
- def test_host_not_found(self):
+ def test_host_not_found(self, gw):
py.test.raises(execnet.HostNotFound,
"execnet.SshGateway('nowhere.codespeak.net')")
--- /dev/null
+++ b/doc/example/popen_read_multiple.py
@@ -0,0 +1,37 @@
+"""
+example
+
+reading results from possibly blocking code running in sub processes.
+"""
+import py
+
+NUM_PROCESSES = 5
+
+channels = []
+for i in range(NUM_PROCESSES):
+ gw = execnet.PopenGateway() # or use SSH or socket gateways
+ channel = gw.remote_exec("""
+ import time
+ secs = channel.receive()
+ time.sleep(secs)
+ channel.send("waited %d secs" % secs)
+ """)
+ channels.append(channel)
+ print "*** instantiated subprocess", gw
+
+mc = execnet.MultiChannel(channels)
+queue = mc.make_receive_queue()
+
+print "***", "verifying that timeout on receiving results from blocked subprocesses works"
+try:
+ queue.get(timeout=1.0)
+except Exception:
+ pass
+
+print "*** sending subprocesses some data to have them unblock"
+mc.send_each(1)
+
+print "*** receiving results asynchronously"
+for i in range(NUM_PROCESSES):
+ channel, result = queue.get(timeout=2.0)
+ print "result", channel.gateway, result
--- /dev/null
+++ b/doc/example/sysinfo.py
@@ -0,0 +1,139 @@
+"""
+sysinfo.py [host1] [host2] [options]
+
+obtain system info from remote machine.
+"""
+
+import py
+import sys
+
+
+parser = py.std.optparse.OptionParser(usage=__doc__)
+parser.add_option("-f", "--sshconfig", action="store", dest="ssh_config", default=None,
+ help="use given ssh config file, and add info all contained hosts for getting info")
+parser.add_option("-i", "--ignore", action="store", dest="ignores", default=None,
+ help="ignore hosts (useful if the list of hostnames come from a file list)")
+
+def parsehosts(path):
+ path = py.path.local(path)
+ l = []
+ rex = py.std.re.compile(r'Host\s*(\S+)')
+ for line in path.readlines():
+ m = rex.match(line)
+ if m is not None:
+ sshname, = m.groups()
+ l.append(sshname)
+ return l
+
+class RemoteInfo:
+ def __init__(self, gateway):
+ self.gw = gateway
+ self._cache = {}
+
+ def exreceive(self, execstring):
+ if execstring not in self._cache:
+ channel = self.gw.remote_exec(execstring)
+ self._cache[execstring] = channel.receive()
+ return self._cache[execstring]
+
+ def getmodattr(self, modpath):
+ module = modpath.split(".")[0]
+ return self.exreceive("""
+ import %s
+ channel.send(%s)
+ """ %(module, modpath))
+
+ def islinux(self):
+ return self.getmodattr('sys.platform').find("linux") != -1
+
+ def getfqdn(self):
+ return self.exreceive("""
+ import socket
+ channel.send(socket.getfqdn())
+ """)
+
+ def getmemswap(self):
+ if self.islinux():
+ return self.exreceive("""
+ import commands, re
+ out = commands.getoutput("free")
+ mem = re.search(r"Mem:\s+(\S*)", out).group(1)
+ swap = re.search(r"Swap:\s+(\S*)", out).group(1)
+ channel.send((mem, swap))
+ """)
+
+ def getcpuinfo(self):
+ if self.islinux():
+ return self.exreceive("""
+ # a hyperthreaded cpu core only counts as 1, although it
+ # is present as 2 in /proc/cpuinfo. Counting it as 2 is
+ # misleading because it is *by far* not as efficient as
+ # two independent cores.
+ cpus = {}
+ cpuinfo = {}
+ f = open("/proc/cpuinfo")
+ lines = f.readlines()
+ f.close()
+ for line in lines + ['']:
+ if line.strip():
+ key, value = line.split(":", 1)
+ cpuinfo[key.strip()] = value.strip()
+ else:
+ corekey = (cpuinfo.get("physical id"),
+ cpuinfo.get("core id"))
+ cpus[corekey] = 1
+ numcpus = len(cpus)
+ model = cpuinfo.get("model name")
+ channel.send((numcpus, model))
+ """)
+
+def debug(*args):
+ print >>sys.stderr, " ".join(map(str, args))
+def error(*args):
+ debug("ERROR", args[0] + ":", *args[1:])
+
+def getinfo(sshname, ssh_config=None, loginfo=sys.stdout):
+ debug("connecting to", sshname)
+ try:
+ gw = execnet.SshGateway(sshname, ssh_config=ssh_config)
+ except IOError:
+ error("could not get sshagteway", sshname)
+ else:
+ ri = RemoteInfo(gw)
+ #print "%s info:" % sshname
+ prefix = sshname.upper() + " "
+ print >>loginfo, prefix, "fqdn:", ri.getfqdn()
+ for attr in (
+ "sys.platform",
+ "sys.version_info",
+ ):
+ loginfo.write("%s %s: " %(prefix, attr,))
+ loginfo.flush()
+ value = ri.getmodattr(attr)
+ loginfo.write(str(value))
+ loginfo.write("\n")
+ loginfo.flush()
+ memswap = ri.getmemswap()
+ if memswap:
+ mem,swap = memswap
+ print >>loginfo, prefix, "Memory:", mem, "Swap:", swap
+ cpuinfo = ri.getcpuinfo()
+ if cpuinfo:
+ numcpu, model = cpuinfo
+ print >>loginfo, prefix, "number of cpus:", numcpu
+ print >>loginfo, prefix, "cpu model", model
+ return ri
+
+if __name__ == '__main__':
+ options, args = parser.parse_args()
+ hosts = list(args)
+ ssh_config = options.ssh_config
+ if ssh_config:
+ hosts.extend(parsehosts(ssh_config))
+ ignores = options.ignores or ()
+ if ignores:
+ ignores = ignores.split(",")
+ for host in hosts:
+ if host not in ignores:
+ getinfo(host, ssh_config=ssh_config)
+
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -5,7 +5,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.0alpha2"
+__version__ = "1.0.0b1"
__author__ = "holger krekel and others"
from execnet.gateway import PopenGateway, SocketGateway, SshGateway
--- /dev/null
+++ b/doc/example/redirect_remote_output.py
@@ -0,0 +1,31 @@
+"""
+redirect output from remote to a local function
+showcasing features of the channel object:
+
+- sending a channel over a channel
+- adapting a channel to a file object
+- setting a callback for receiving channel data
+
+"""
+
+import py
+
+gw = execnet.PopenGateway()
+
+outchan = gw.remote_exec("""
+ import sys
+ outchan = channel.gateway.newchannel()
+ sys.stdout = outchan.makefile("w")
+ channel.send(outchan)
+""").receive()
+
+# note: callbacks execute in receiver thread!
+def write(data):
+ print "received:", repr(data)
+outchan.setcallback(write)
+
+gw.remote_exec("""
+ print 'hello world'
+ print 'remote execution ends'
+""").waitclose()
+
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -1,10 +1,12 @@
import execnet
import py
+rsyncdirs = ['../execnet', '.']
+
pytest_plugins = ['pytester']
# configuration information for tests
def pytest_addoption(parser):
- group = parser.addgroup("pylib", "py lib testing options")
+ group = parser.getgroup("pylib", "py lib testing options")
group.addoption('--gx',
action="append", dest="gspecs", default=None,
help=("add a global test environment, XSpec-syntax. "))
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -12,6 +12,11 @@ def _find_version(suffix=""):
name = "python" + suffix
executable = py.path.local.sysfind(name)
if executable is None:
+ if sys.platform == "win32" and suffix == "3":
+ for name in ('python31', 'python30'):
+ executable = py.path.local(r"c:\\%s\python.exe" % (name,))
+ if executable.check():
+ return executable
py.test.skip("can't find a %r executable" % (name,))
return executable
--- a/execnet/serializer.py
+++ b/execnet/serializer.py
@@ -79,7 +79,7 @@ class Serializer(object):
def save_bytes(self, bytes_):
self.stream.write(BYTES)
self._write_byte_sequence(bytes_)
- dispatch[bytes] = save_bytes
+ dispatch[type("".encode('ascii'))] = save_bytes
if _INPY3:
def save_string(self, s):
From commits-noreply at bitbucket.org Mon Nov 2 15:59:17 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 2 Nov 2009 14:59:17 +0000 (UTC)
Subject: [execnet-commit] execnet commit 3573cc5b82a7: small
fixes/adjustments for running tests on jython-2.5.1
Message-ID: <20091102145917.5A08C7EFAB@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257173606 -3600
# Node ID 3573cc5b82a7f502b6f42c028334c0f2d47bcfad
# Parent 69505782d0fdcdd674deaf0a035df5055e75caf2
small fixes/adjustments for running tests on jython-2.5.1
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -119,7 +119,7 @@ simple_tests = {
'float':'3.25',
'list': '[1, 2, 3]',
'tuple': '(1, 2, 3)',
- 'dict': '{6: 2, (1, 2, 3): 32}',
+ 'dict': '{(1, 2, 3): 32}',
}
def test_simple(tp_name, repr, dump, load):
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -71,9 +71,9 @@ class SocketIO:
def __init__(self, sock):
self.sock = sock
try:
+ sock.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10)# IPTOS_LOWDELAY
sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
- sock.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10)# IPTOS_LOWDELAY
- except socket.error:
+ except (AttributeError, socket.error):
e = sys.exc_info()[1]
sys.stderr.write("WARNING: cannot set socketoption")
self.readable = self.writeable = True
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -9,6 +9,7 @@ from execnet.threadpool import WorkerPoo
queue = py.builtin._tryimport('queue', 'Queue')
TESTTIMEOUT = 10.0 # seconds
+skiponjython = py.test.mark.skipif("sys.platform.startswith('java')")
class TestBasicRemoteExecution:
def test_correct_setup(self, gw):
@@ -216,6 +217,7 @@ class TestChannelBasicBehaviour:
assert l == [0, 100, 200, 300, 400]
return subchannel
+ @skiponjython
def test_channel_callback_remote_freed(self, gw):
channel = self.check_channel_callback_stays_active(gw, earlyfree=False)
# freed automatically at the end of producer()
@@ -341,6 +343,7 @@ class TestChannelFile:
channel = gw.newchannel()
py.test.raises(ValueError, 'channel.makefile("rw")')
+ @skiponjython
def test_confusion_from_os_write_stdout(self, gw):
channel = gw.remote_exec("""
import os
@@ -355,6 +358,7 @@ class TestChannelFile:
res = channel.receive()
assert res == 42
+ @skiponjython
def test_confusion_from_os_write_stderr(self, gw):
channel = gw.remote_exec("""
import os
@@ -394,16 +398,16 @@ def test_join_blocked_execution_gateway(
gateway = execnet.PopenGateway()
channel = gateway.remote_exec("""
import time
- time.sleep(5.0)
+ time.sleep(10.0)
""")
def doit():
gateway.exit()
- gateway.join(joinexec=True)
+ gateway.join()
return 17
pool = WorkerPool()
reply = pool.dispatch(doit)
- x = reply.get(timeout=1.0)
+ x = reply.get(timeout=5.0)
assert x == 17
class TestPopenGateway:
From commits-noreply at bitbucket.org Mon Nov 2 15:59:19 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 2 Nov 2009 14:59:19 +0000 (UTC)
Subject: [execnet-commit] execnet commit 309d91b1db59: fix python2.4
serializer compatibility (does not have struct.Struct)
Message-ID: <20091102145919.24BE67EFFF@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257173924 -3600
# Node ID 309d91b1db599824a012ebf42118326eee442c41
# Parent 3573cc5b82a7f502b6f42c028334c0f2d47bcfad
fix python2.4 serializer compatibility (does not have struct.Struct)
--- a/execnet/serializer.py
+++ b/execnet/serializer.py
@@ -35,8 +35,8 @@ else:
FOUR_BYTE_INT_MAX = 2147483647
-_int4_format = struct.Struct("!i")
-_float_format = struct.Struct("!d")
+FLOAT_FORMAT = "!d"
+FLOAT_FORMAT_SIZE = struct.calcsize(FLOAT_FORMAT)
# Protocol constants
VERSION_NUMBER = 1
@@ -114,14 +114,14 @@ class Serializer(object):
def save_float(self, flt):
self.stream.write(FLOAT)
- self.stream.write(_float_format.pack(flt))
+ self.stream.write(struct.pack(FLOAT_FORMAT, flt))
dispatch[float] = save_float
def _write_int4(self, i, error="int must be less than %i" %
(FOUR_BYTE_INT_MAX,)):
if i > FOUR_BYTE_INT_MAX:
raise SerializationError(error)
- self.stream.write(_int4_format.pack(i))
+ self.stream.write(struct.pack("!i", i))
def save_list(self, L):
self.stream.write(NEWLIST)
@@ -206,12 +206,12 @@ class Unserializer(object):
opcodes[INT] = load_int
def load_float(self):
- binary = self.stream.read(_float_format.size)
- self.stack.append(_float_format.unpack(binary)[0])
+ binary = self.stream.read(FLOAT_FORMAT_SIZE)
+ self.stack.append(struct.unpack(FLOAT_FORMAT, binary)[0])
opcodes[FLOAT] = load_float
def _read_int4(self):
- return _int4_format.unpack(self.stream.read(4))[0]
+ return struct.unpack("!i", self.stream.read(4))[0]
def _read_byte_string(self):
length = self._read_int4()
From commits-noreply at bitbucket.org Tue Nov 3 12:48:09 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 11:48:09 +0000 (UTC)
Subject: [execnet-commit] execnet commit d8ed57971198: adding neccessary
serialization support for long, None, bools
Message-ID: <20091103114809.1A5BE7EF46@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257248495 -3600
# Node ID d8ed5797119873303ca129658d7fc5c0d03e529c
# Parent 6ea0aa1903dbe6f1a520b690d36ca2795deadad4
adding neccessary serialization support for long, None, bools
--- a/testing/test_rsync.py
+++ b/testing/test_rsync.py
@@ -34,7 +34,9 @@ class TestRSync:
rsync = RSync(dirs.source)
rsync.add_target(gw1, dest)
rsync.add_target(gw2, dest2)
+ print "now sending", rsync
rsync.send()
+ print "did send", rsync
assert dest.join('subdir').check(dir=1)
assert dest.join('subdir', 'file1').check(file=1)
assert dest.join('subdir', 'file1').read() == s
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -1,7 +1,7 @@
"""
-base execnet gateway bootstrapping code.
+base execnet gateway code send to the other side for bootstrapping.
-NOTE: below code is to be compatible to Python 2.3-3.1, Jython and IronPython
+NOTE: aims to be compatible to Python 2.3-3.1, Jython and IronPython
(C) 2004-2009 Holger Krekel, Armin Rigo, Benjamin Peterson, and others
"""
@@ -125,11 +125,6 @@ sys.stdin = tempfile.TemporaryFile('r')
pass
self.writeable = None
-# ___________________________________________________________________________
-#
-# Messages
-# ___________________________________________________________________________
-
class Message:
""" encapsulates Messages and their wire protocol. """
_types = {}
@@ -712,6 +707,8 @@ FLOAT_FORMAT_SIZE = struct.calcsize(FLOA
# Protocol constants
VERSION_NUMBER = 1
VERSION = b(chr(VERSION_NUMBER))
+NONE = b('n')
+NONE = b('n')
PY2STRING = b('s')
PY3STRING = b('t')
UNICODE = b('u')
@@ -722,6 +719,8 @@ SETITEM = b('m')
NEWDICT = b('d')
INT = b('i')
FLOAT = b('f')
+TRUE = b('1')
+FALSE = b('0')
STOP = b('S')
class Serializer(object):
@@ -744,6 +743,17 @@ class Serializer(object):
dispatch = {}
+ def save_none(self, non):
+ self.stream.write(NONE)
+ dispatch[type(None)] = save_none
+
+ def save_bool(self, boolean):
+ if boolean:
+ self.stream.write(TRUE)
+ else:
+ self.stream.write(FALSE)
+ dispatch[bool] = save_bool
+
def save_bytes(self, bytes_):
self.stream.write(BYTES)
self._write_byte_sequence(bytes_)
@@ -779,6 +789,8 @@ class Serializer(object):
self.stream.write(INT)
self._write_int4(i)
dispatch[int] = save_int
+ if not ISPY3:
+ dispatch[long] = save_int
def save_float(self, flt):
self.stream.write(FLOAT)
@@ -868,6 +880,17 @@ class Unserializer(object):
opcodes = {}
+ def load_none(self):
+ self.stack.append(None)
+ opcodes[NONE] = load_none
+
+ def load_true(self):
+ self.stack.append(True)
+ opcodes[TRUE] = load_true
+ def load_false(self):
+ self.stack.append(False)
+ opcodes[FALSE] = load_false
+
def load_int(self):
i = self._read_int4()
self.stack.append(i)
From commits-noreply at bitbucket.org Tue Nov 3 12:48:10 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 11:48:10 +0000 (UTC)
Subject: [execnet-commit] execnet commit b0289aa788c4: refine docs,
avoid redundancy, move some bits to a new implnotes.txt file
Message-ID: <20091103114810.A54A37EF4F@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257177655 -3600
# Node ID b0289aa788c451cda903489e26da5030aa708167
# Parent 309d91b1db599824a012ebf42118326eee442c41
refine docs, avoid redundancy, move some bits to a new implnotes.txt file
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -1,35 +1,7 @@
"""
-base execnet gateway code, a quick overview.
+base execnet gateway code send to the other side for bootstrapping.
-the code of this module is sent to the "other side"
-as a means of bootstrapping a Gateway object
-capable of receiving and executing code,
-and routing data through channels.
-
-Gateways operate on InputOutput objects offering
-a write and a read(n) method.
-
-Once bootstrapped a higher level protocol
-based on Messages is used. Messages are serialized
-to and from InputOutput objects. The details of this protocol
-are locally defined in this module. There is no need
-for standardizing or versioning the protocol.
-
-After bootstrapping the BaseGateway opens a receiver thread which
-accepts encoded messages and triggers actions to interpret them.
-Sending of channel data items happens directly through
-write operations to InputOutput objects so there is no
-separate thread.
-
-Code execution messages are put into an execqueue from
-which they will be taken for execution. gateway.serve()
-will take and execute such items, one by one. This means
-that by incoming default execution is single-threaded.
-
-The receiver thread terminates if the remote side sends
-a gateway termination message or if the IO-connection drops.
-It puts an end symbol into the execqueue so
-that serve() can cleanly finish as well.
+NOTE: compatible to Python2.3-3.1, Jython and IronPython!
(C) 2004-2009 Holger Krekel, Armin Rigo and others
"""
@@ -611,8 +583,6 @@ class BaseGateway(object):
pass
def __init__(self, io, _startcount=2):
- """ initialize core gateway, using the given inputoutput object.
- """
self._io = io
self._channelfactory = ChannelFactory(self, _startcount)
self._receivelock = threading.RLock()
@@ -634,7 +604,6 @@ class BaseGateway(object):
sys.stderr.write("exception during tracing\n")
def _thread_receiver(self):
- """ thread to read and handle Messages half-sync-half-async. """
self._trace("starting to receive")
try:
while 1:
@@ -694,7 +663,6 @@ class BaseGateway(object):
# _____________________________________________________________________
#
def newchannel(self):
- """ return new channel object. """
return self._channelfactory.new()
def join(self, joinexec=True):
@@ -732,11 +700,9 @@ class SlaveGateway(BaseGateway):
self.join()
def executetask(self, item):
- """ execute channel/source items. """
channel, source = item
try:
- loc = { 'channel' : channel, '__name__': '__channelexec__'}
- #open("task.py", 'w').write(source)
+ loc = {'channel' : channel, '__name__': '__channelexec__'}
self._trace("execution starts: %s" % repr(source)[:50])
try:
co = compile(source+'\n', '', 'exec')
--- /dev/null
+++ b/doc/implnotes.txt
@@ -0,0 +1,33 @@
+
+gateway_base.py::
+
+the code of this module is sent to the "other side"
+as a means of bootstrapping a Gateway object
+capable of receiving and executing code,
+and routing data through channels.
+
+Gateways operate on InputOutput objects offering
+a write and a read(n) method.
+
+Once bootstrapped a higher level protocol
+based on Messages is used. Messages are serialized
+to and from InputOutput objects. The details of this protocol
+are locally defined in this module. There is no need
+for standardizing or versioning the protocol.
+
+After bootstrapping the BaseGateway opens a receiver thread which
+accepts encoded messages and triggers actions to interpret them.
+Sending of channel data items happens directly through
+write operations to InputOutput objects so there is no
+separate thread.
+
+Code execution messages are put into an execqueue from
+which they will be taken for execution. gateway.serve()
+will take and execute such items, one by one. This means
+that by incoming default execution is single-threaded.
+
+The receiver thread terminates if the remote side sends
+a gateway termination message or if the IO-connection drops.
+It puts an end symbol into the execqueue so
+that serve() can cleanly finish as well.
+
From commits-noreply at bitbucket.org Tue Nov 3 12:48:12 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 11:48:12 +0000 (UTC)
Subject: [execnet-commit] execnet commit 6ea0aa1903db: integrate serializer
into gateway bootstrap, simplify exception hierarchies
Message-ID: <20091103114812.5E5D17EF50@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257248494 -3600
# Node ID 6ea0aa1903dbe6f1a520b690d36ca2795deadad4
# Parent b0289aa788c451cda903489e26da5030aa708167
integrate serializer into gateway bootstrap, simplify exception hierarchies
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -5,7 +5,7 @@ import tempfile
import subprocess
import py
import execnet
-from execnet import serializer
+from execnet import gateway_base as serializer
def _find_version(suffix=""):
@@ -46,7 +46,7 @@ class PythonWrapper(object):
def dump(self, obj_rep):
script_file = TEMPDIR.join("dump.py")
script_file.write("""
-from execnet import serializer
+from execnet import gateway_base as serializer
import sys
if sys.version_info > (3, 0): # Need binary output
sys.stdout = sys.stdout.detach()
@@ -57,7 +57,7 @@ saver.save(%s)""" % (obj_rep,))
def load(self, data, option_args=""):
script_file = TEMPDIR.join("load.py")
script_file.write(r"""
-from execnet import serializer
+from execnet import gateway_base as serializer
import sys
if sys.version_info > (3, 0):
sys.stdin = sys.stdin.detach()
@@ -158,7 +158,7 @@ def test_string(py2, py3):
assert tp == "str"
assert s == "'xyz'"
tp, s = py3.load(p)
- assert tp == "bytes"
+ assert tp == "bytes" # depends on unserialization defaults
assert s == "b'xyz'"
tp, s = py3.load(p, "True")
assert tp == "str"
@@ -181,5 +181,5 @@ def test_unicode(py2, py3):
assert tp == "str"
assert s == "'hi'"
tp, s = py2.load(p)
- assert tp == "unicode"
+ assert tp == "unicode" # depends on unserialization defaults
assert s == "u'hi'"
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -1,9 +1,9 @@
"""
-base execnet gateway code send to the other side for bootstrapping.
+base execnet gateway bootstrapping code.
-NOTE: compatible to Python2.3-3.1, Jython and IronPython!
+NOTE: below code is to be compatible to Python 2.3-3.1, Jython and IronPython
-(C) 2004-2009 Holger Krekel, Armin Rigo and others
+(C) 2004-2009 Holger Krekel, Armin Rigo, Benjamin Peterson, and others
"""
import sys, os, weakref
import threading, traceback, socket, struct
@@ -12,7 +12,8 @@ try:
except ImportError:
import Queue as queue
-if sys.version_info > (3, 0):
+ISPY3 = sys.version_info > (3, 0)
+if ISPY3:
exec("def do_exec(co, loc): exec(co, loc)\n"
"def reraise(cls, val, tb): raise val\n")
unicode = str
@@ -21,10 +22,6 @@ else:
"def reraise(cls, val, tb): raise cls, val, tb\n")
bytes = str
-def str(*args):
- raise EnvironmentError(
- "use unicode or bytes, not cross-python ambigous 'str'")
-
default_encoding = "UTF-8"
sysex = (KeyboardInterrupt, SystemExit)
@@ -132,56 +129,22 @@ sys.stdin = tempfile.TemporaryFile('r')
#
# Messages
# ___________________________________________________________________________
-# the header format
-HDR_FORMAT = "!hhii"
-HDR_SIZE = struct.calcsize(HDR_FORMAT)
-
-is3k = sys.version_info >= (3,0)
class Message:
""" encapsulates Messages and their wire protocol. """
_types = {}
+
def __init__(self, channelid=0, data=''):
self.channelid = channelid
self.data = data
def writeto(self, io):
- # XXX marshal.dumps doesn't work for exchanging data across Python
- # version :-((( XXX check this statement wrt python2.4 through 3.1
- data = self.data
- if isinstance(data, bytes):
- dataformat = 1 + int(is3k)
- else:
- if isinstance(data, unicode):
- dataformat = 3
- else:
- data = repr(self.data) # argh
- dataformat = 4
- data = data.encode(default_encoding)
- header = struct.pack(HDR_FORMAT, self.msgtype, dataformat,
- self.channelid, len(data))
- io.write(header + data)
+ ser = Serializer(io)
+ ser.save((self.msgtype, self.channelid, self.data))
def readfrom(cls, io):
- header = io.read(HDR_SIZE)
- (msgtype, dataformat,
- senderid, stringlen) = struct.unpack(HDR_FORMAT, header)
- data = io.read(stringlen)
- if dataformat == 1:
- if is3k:
- # remote was python2-str, we are 3k-text
- data = data.decode(default_encoding)
- elif dataformat == 2:
- # remote was python3-bytes
- pass
- else:
- data = data.decode(default_encoding)
- if dataformat == 3:
- pass
- elif dataformat == 4:
- data = eval(data, {}) # reversed argh
- else:
- raise ValueError("bad data format")
+ unser = Unserializer(io)
+ msgtype, senderid, data = unser.load()
return cls._types[msgtype](senderid, data)
readfrom = classmethod(readfrom)
@@ -722,3 +685,258 @@ class SlaveGateway(BaseGateway):
else:
channel.close()
+#
+# Cross-Python pickling code, tested from test_serializer.py
+#
+
+class SerializeError(Exception):
+ pass
+
+class SerializationError(SerializeError):
+ """Error while serializing an object."""
+
+class UnserializationError(SerializeError):
+ """Error while unserializing an object."""
+
+if ISPY3:
+ def b(s):
+ return s.encode("ascii")
+else:
+ b = str
+
+FOUR_BYTE_INT_MAX = 2147483647
+
+FLOAT_FORMAT = "!d"
+FLOAT_FORMAT_SIZE = struct.calcsize(FLOAT_FORMAT)
+
+# Protocol constants
+VERSION_NUMBER = 1
+VERSION = b(chr(VERSION_NUMBER))
+PY2STRING = b('s')
+PY3STRING = b('t')
+UNICODE = b('u')
+BYTES = b('b')
+NEWLIST = b('l')
+BUILDTUPLE = b('T')
+SETITEM = b('m')
+NEWDICT = b('d')
+INT = b('i')
+FLOAT = b('f')
+STOP = b('S')
+
+class Serializer(object):
+
+ def __init__(self, stream):
+ self.stream = stream
+
+ def save(self, obj):
+ self.stream.write(VERSION)
+ self._save(obj)
+ self.stream.write(STOP)
+
+ def _save(self, obj):
+ tp = type(obj)
+ try:
+ dispatch = self.dispatch[tp]
+ except KeyError:
+ raise SerializationError("can't serialize %s" % (tp,))
+ dispatch(self, obj)
+
+ dispatch = {}
+
+ def save_bytes(self, bytes_):
+ self.stream.write(BYTES)
+ self._write_byte_sequence(bytes_)
+ dispatch[type("".encode('ascii'))] = save_bytes
+
+ if ISPY3:
+ def save_string(self, s):
+ self.stream.write(PY3STRING)
+ self._write_unicode_string(s)
+ else:
+ def save_string(self, s):
+ self.stream.write(PY2STRING)
+ self._write_byte_sequence(s)
+
+ def save_unicode(self, s):
+ self.stream.write(UNICODE)
+ self._write_unicode_string(s)
+ dispatch[unicode] = save_unicode
+ dispatch[str] = save_string
+
+ def _write_unicode_string(self, s):
+ try:
+ as_bytes = s.encode("utf-8")
+ except UnicodeEncodeError:
+ raise SerializationError("strings must be utf-8 encodable")
+ self._write_byte_sequence(as_bytes)
+
+ def _write_byte_sequence(self, bytes_):
+ self._write_int4(len(bytes_), "string is too long")
+ self.stream.write(bytes_)
+
+ def save_int(self, i):
+ self.stream.write(INT)
+ self._write_int4(i)
+ dispatch[int] = save_int
+
+ def save_float(self, flt):
+ self.stream.write(FLOAT)
+ self.stream.write(struct.pack(FLOAT_FORMAT, flt))
+ dispatch[float] = save_float
+
+ def _write_int4(self, i, error="int must be less than %i" %
+ (FOUR_BYTE_INT_MAX,)):
+ if i > FOUR_BYTE_INT_MAX:
+ raise SerializationError(error)
+ self.stream.write(struct.pack("!i", i))
+
+ def save_list(self, L):
+ self.stream.write(NEWLIST)
+ self._write_int4(len(L), "list is too long")
+ for i, item in enumerate(L):
+ self._write_setitem(i, item)
+ dispatch[list] = save_list
+
+ def _write_setitem(self, key, value):
+ self._save(key)
+ self._save(value)
+ self.stream.write(SETITEM)
+
+ def save_dict(self, d):
+ self.stream.write(NEWDICT)
+ for key, value in d.items():
+ self._write_setitem(key, value)
+ dispatch[dict] = save_dict
+
+ def save_tuple(self, tup):
+ for item in tup:
+ self._save(item)
+ self.stream.write(BUILDTUPLE)
+ self._write_int4(len(tup), "tuple is too long")
+ dispatch[tuple] = save_tuple
+
+class _UnserializationOptions(object):
+ pass
+
+class _Py2UnserializationOptions(_UnserializationOptions):
+
+ def __init__(self, py3_strings_as_str=False):
+ self.py3_strings_as_str = py3_strings_as_str
+
+class _Py3UnserializationOptions(_UnserializationOptions):
+
+ def __init__(self, py2_strings_as_str=False):
+ self.py2_strings_as_str = py2_strings_as_str
+
+if ISPY3:
+ UnserializationOptions = _Py3UnserializationOptions
+else:
+ UnserializationOptions = _Py2UnserializationOptions
+
+class _Stop(Exception):
+ pass
+
+class Unserializer(object):
+
+ def __init__(self, stream, options=UnserializationOptions()):
+ self.stream = stream
+ self.options = options
+
+ def load(self):
+ self.stack = []
+ version = ord(self.stream.read(1))
+ if version != VERSION_NUMBER:
+ raise UnserializationError(
+ "version mismatch: %i != %i" % (version, VERSION_NUMBER))
+ try:
+ while True:
+ opcode = self.stream.read(1)
+ if not opcode:
+ raise EOFError
+ try:
+ loader = self.opcodes[opcode]
+ except KeyError:
+ raise UnserializationError("unkown opcode %s" % (opcode,))
+ loader(self)
+ except _Stop:
+ if len(self.stack) != 1:
+ raise UnserializationError("internal unserialization error")
+ return self.stack[0]
+ else:
+ raise UnserializationError("didn't get STOP")
+
+ opcodes = {}
+
+ def load_int(self):
+ i = self._read_int4()
+ self.stack.append(i)
+ opcodes[INT] = load_int
+
+ def load_float(self):
+ binary = self.stream.read(FLOAT_FORMAT_SIZE)
+ self.stack.append(struct.unpack(FLOAT_FORMAT, binary)[0])
+ opcodes[FLOAT] = load_float
+
+ def _read_int4(self):
+ return struct.unpack("!i", self.stream.read(4))[0]
+
+ def _read_byte_string(self):
+ length = self._read_int4()
+ as_bytes = self.stream.read(length)
+ return as_bytes
+
+ def load_py3string(self):
+ as_bytes = self._read_byte_string()
+ if not ISPY3 and self.options.py3_strings_as_str:
+ # XXX Should we try to decode into latin-1?
+ self.stack.append(as_bytes)
+ else:
+ self.stack.append(as_bytes.decode("utf-8"))
+ opcodes[PY3STRING] = load_py3string
+
+ def load_py2string(self):
+ as_bytes = self._read_byte_string()
+ if ISPY3 and self.options.py2_strings_as_str:
+ s = as_bytes.decode("latin-1")
+ else:
+ s = as_bytes
+ self.stack.append(s)
+ opcodes[PY2STRING] = load_py2string
+
+ def load_bytes(self):
+ s = self._read_byte_string()
+ self.stack.append(s)
+ opcodes[BYTES] = load_bytes
+
+ def load_unicode(self):
+ self.stack.append(self._read_byte_string().decode("utf-8"))
+ opcodes[UNICODE] = load_unicode
+
+ def load_newlist(self):
+ length = self._read_int4()
+ self.stack.append([None] * length)
+ opcodes[NEWLIST] = load_newlist
+
+ def load_setitem(self):
+ if len(self.stack) < 3:
+ raise UnserializationError("not enough items for setitem")
+ value = self.stack.pop()
+ key = self.stack.pop()
+ self.stack[-1][key] = value
+ opcodes[SETITEM] = load_setitem
+
+ def load_newdict(self):
+ self.stack.append({})
+ opcodes[NEWDICT] = load_newdict
+
+ def load_buildtuple(self):
+ length = self._read_int4()
+ tup = tuple(self.stack[-length:])
+ del self.stack[-length:]
+ self.stack.append(tup)
+ opcodes[BUILDTUPLE] = load_buildtuple
+
+ def load_stop(self):
+ raise _Stop
+ opcodes[STOP] = load_stop
--- a/execnet/serializer.py
+++ /dev/null
@@ -1,274 +0,0 @@
-"""
-Simple marshal format (based on pickle) designed to work across Python versions.
-
-(c) 2006-2009 Benjamin Peterson, Ronny Pfannschmidt and others
-"""
-
-import sys
-import struct
-
-_INPY3 = _REALLY_PY3 = sys.version_info > (3, 0)
-
-class SerializeError(Exception):
- pass
-
-class SerializationError(SerializeError):
- """Error while serializing an object."""
-
-class UnserializableType(SerializationError):
- """Can't serialize a type."""
-
-class UnserializationError(SerializeError):
- """Error while unserializing an object."""
-
-class VersionMismatch(UnserializationError):
- """Data from a previous or later format."""
-
-class Corruption(UnserializationError):
- """The pickle format appears to have been corrupted."""
-
-if _INPY3:
- def b(s):
- return s.encode("ascii")
-else:
- b = str
-
-FOUR_BYTE_INT_MAX = 2147483647
-
-FLOAT_FORMAT = "!d"
-FLOAT_FORMAT_SIZE = struct.calcsize(FLOAT_FORMAT)
-
-# Protocol constants
-VERSION_NUMBER = 1
-VERSION = b(chr(VERSION_NUMBER))
-PY2STRING = b('s')
-PY3STRING = b('t')
-UNICODE = b('u')
-BYTES = b('b')
-NEWLIST = b('l')
-BUILDTUPLE = b('T')
-SETITEM = b('m')
-NEWDICT = b('d')
-INT = b('i')
-FLOAT = b('f')
-STOP = b('S')
-
-class CrossVersionOptions(object):
- pass
-
-class Serializer(object):
-
- def __init__(self, stream):
- self.stream = stream
-
- def save(self, obj):
- self.stream.write(VERSION)
- self._save(obj)
- self.stream.write(STOP)
-
- def _save(self, obj):
- tp = type(obj)
- try:
- dispatch = self.dispatch[tp]
- except KeyError:
- raise UnserializableType("can't serialize %s" % (tp,))
- dispatch(self, obj)
-
- dispatch = {}
-
- def save_bytes(self, bytes_):
- self.stream.write(BYTES)
- self._write_byte_sequence(bytes_)
- dispatch[type("".encode('ascii'))] = save_bytes
-
- if _INPY3:
- def save_string(self, s):
- self.stream.write(PY3STRING)
- self._write_unicode_string(s)
- else:
- def save_string(self, s):
- self.stream.write(PY2STRING)
- self._write_byte_sequence(s)
-
- def save_unicode(self, s):
- self.stream.write(UNICODE)
- self._write_unicode_string(s)
- dispatch[unicode] = save_unicode
- dispatch[str] = save_string
-
- def _write_unicode_string(self, s):
- try:
- as_bytes = s.encode("utf-8")
- except UnicodeEncodeError:
- raise SerializationError("strings must be utf-8 encodable")
- self._write_byte_sequence(as_bytes)
-
- def _write_byte_sequence(self, bytes_):
- self._write_int4(len(bytes_), "string is too long")
- self.stream.write(bytes_)
-
- def save_int(self, i):
- self.stream.write(INT)
- self._write_int4(i)
- dispatch[int] = save_int
-
- def save_float(self, flt):
- self.stream.write(FLOAT)
- self.stream.write(struct.pack(FLOAT_FORMAT, flt))
- dispatch[float] = save_float
-
- def _write_int4(self, i, error="int must be less than %i" %
- (FOUR_BYTE_INT_MAX,)):
- if i > FOUR_BYTE_INT_MAX:
- raise SerializationError(error)
- self.stream.write(struct.pack("!i", i))
-
- def save_list(self, L):
- self.stream.write(NEWLIST)
- self._write_int4(len(L), "list is too long")
- for i, item in enumerate(L):
- self._write_setitem(i, item)
- dispatch[list] = save_list
-
- def _write_setitem(self, key, value):
- self._save(key)
- self._save(value)
- self.stream.write(SETITEM)
-
- def save_dict(self, d):
- self.stream.write(NEWDICT)
- for key, value in d.items():
- self._write_setitem(key, value)
- dispatch[dict] = save_dict
-
- def save_tuple(self, tup):
- for item in tup:
- self._save(item)
- self.stream.write(BUILDTUPLE)
- self._write_int4(len(tup), "tuple is too long")
- dispatch[tuple] = save_tuple
-
-
-class _UnserializationOptions(object):
- pass
-
-class _Py2UnserializationOptions(_UnserializationOptions):
-
- def __init__(self, py3_strings_as_str=False):
- self.py3_strings_as_str = py3_strings_as_str
-
-class _Py3UnserializationOptions(_UnserializationOptions):
-
- def __init__(self, py2_strings_as_str=False):
- self.py2_strings_as_str = py2_strings_as_str
-
-if _INPY3:
- UnserializationOptions = _Py3UnserializationOptions
-else:
- UnserializationOptions = _Py2UnserializationOptions
-
-class _Stop(Exception):
- pass
-
-class Unserializer(object):
-
- def __init__(self, stream, options=UnserializationOptions()):
- self.stream = stream
- self.options = options
-
- def load(self):
- self.stack = []
- version = ord(self.stream.read(1))
- if version != VERSION_NUMBER:
- raise VersionMismatch("%i != %i" % (version, VERSION_NUMBER))
- try:
- while True:
- opcode = self.stream.read(1)
- if not opcode:
- raise EOFError
- try:
- loader = self.opcodes[opcode]
- except KeyError:
- raise Corruption("unkown opcode %s" % (opcode,))
- loader(self)
- except _Stop:
- if len(self.stack) != 1:
- raise UnserializationError("internal unserialization error")
- return self.stack[0]
- else:
- raise Corruption("didn't get STOP")
-
- opcodes = {}
-
- def load_int(self):
- i = self._read_int4()
- self.stack.append(i)
- opcodes[INT] = load_int
-
- def load_float(self):
- binary = self.stream.read(FLOAT_FORMAT_SIZE)
- self.stack.append(struct.unpack(FLOAT_FORMAT, binary)[0])
- opcodes[FLOAT] = load_float
-
- def _read_int4(self):
- return struct.unpack("!i", self.stream.read(4))[0]
-
- def _read_byte_string(self):
- length = self._read_int4()
- as_bytes = self.stream.read(length)
- return as_bytes
-
- def load_py3string(self):
- as_bytes = self._read_byte_string()
- if not _INPY3 and self.options.py3_strings_as_str:
- # XXX Should we try to decode into latin-1?
- self.stack.append(as_bytes)
- else:
- self.stack.append(as_bytes.decode("utf-8"))
- opcodes[PY3STRING] = load_py3string
-
- def load_py2string(self):
- as_bytes = self._read_byte_string()
- if _INPY3 and self.options.py2_strings_as_str:
- s = as_bytes.decode("latin-1")
- else:
- s = as_bytes
- self.stack.append(s)
- opcodes[PY2STRING] = load_py2string
-
- def load_bytes(self):
- s = self._read_byte_string()
- self.stack.append(s)
- opcodes[BYTES] = load_bytes
-
- def load_unicode(self):
- self.stack.append(self._read_byte_string().decode("utf-8"))
- opcodes[UNICODE] = load_unicode
-
- def load_newlist(self):
- length = self._read_int4()
- self.stack.append([None] * length)
- opcodes[NEWLIST] = load_newlist
-
- def load_setitem(self):
- if len(self.stack) < 3:
- raise Corruption("not enough items for setitem")
- value = self.stack.pop()
- key = self.stack.pop()
- self.stack[-1][key] = value
- opcodes[SETITEM] = load_setitem
-
- def load_newdict(self):
- self.stack.append({})
- opcodes[NEWDICT] = load_newdict
-
- def load_buildtuple(self):
- length = self._read_int4()
- tup = tuple(self.stack[-length:])
- del self.stack[-length:]
- self.stack.append(tup)
- opcodes[BUILDTUPLE] = load_buildtuple
-
- def load_stop(self):
- raise _Stop
- opcodes[STOP] = load_stop
From commits-noreply at bitbucket.org Tue Nov 3 12:59:51 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 11:59:51 +0000 (UTC)
Subject: [execnet-commit] execnet commit 9ca8d04cb915: adding tests,
fixing typos and setting option to automatically turn py2
strings into a py3 'str'
Message-ID: <20091103115951.D6F207EF46@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257249262 -3600
# Node ID 9ca8d04cb915afc9e42278b2fe28284cc44d05f1
# Parent d8ed5797119873303ca129658d7fc5c0d03e529c
adding tests, fixing typos and setting option to automatically turn py2 strings into a py3 'str'
--- a/testing/test_rsync.py
+++ b/testing/test_rsync.py
@@ -34,9 +34,7 @@ class TestRSync:
rsync = RSync(dirs.source)
rsync.add_target(gw1, dest)
rsync.add_target(gw2, dest2)
- print "now sending", rsync
rsync.send()
- print "did send", rsync
assert dest.join('subdir').check(dir=1)
assert dest.join('subdir', 'file1').check(file=1)
assert dest.join('subdir', 'file1').read() == s
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -838,7 +838,7 @@ class _Py2UnserializationOptions(_Unseri
class _Py3UnserializationOptions(_UnserializationOptions):
- def __init__(self, py2_strings_as_str=False):
+ def __init__(self, py2_strings_as_str=True):
self.py2_strings_as_str = py2_strings_as_str
if ISPY3:
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -158,14 +158,7 @@ def test_string(py2, py3):
assert tp == "str"
assert s == "'xyz'"
tp, s = py3.load(p)
- assert tp == "bytes" # depends on unserialization defaults
- assert s == "b'xyz'"
- tp, s = py3.load(p, "True")
- assert tp == "str"
- assert s == "'xyz'"
- p = py3.dump("'xyz'")
- tp, s = py2.load(p, True)
- assert tp == "str"
+ assert tp == "str" # depends on unserialization defaults
assert s == "'xyz'"
def test_unicode(py2, py3):
@@ -183,3 +176,29 @@ def test_unicode(py2, py3):
tp, s = py2.load(p)
assert tp == "unicode" # depends on unserialization defaults
assert s == "u'hi'"
+
+def test_long(py2, py3):
+ p = py2.dump("123L")
+ tp, s = py2.load(p)
+ assert s == "123"
+ tp, s = py3.load(p)
+ assert s == "123"
+
+def test_bool(py2, py3):
+ p = py2.dump("True")
+ tp, s = py2.load(p)
+ assert tp == "bool"
+ assert s == "True"
+ tp, s = py3.load(p)
+ assert s == "True"
+ assert tp == "bool"
+ p = py2.dump("False")
+ tp, s = py2.load(p)
+ assert s == "False"
+
+def test_none(py2, py3):
+ p = py2.dump("None")
+ tp, s = py2.load(p)
+ assert s == "None"
+ tp, s = py3.load(p)
+ assert s == "None"
From commits-noreply at bitbucket.org Tue Nov 3 18:04:56 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 17:04:56 +0000 (UTC)
Subject: [execnet-commit] execnet commit 6371d6423d5d: small refinements:
simplifying send-code, fixing RInfo for python3, extend .hgignore
Message-ID: <20091103170456.E4F3D7EF06@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257264604 -3600
# Node ID 6371d6423d5d28faba17462c154b3282db0c3d99
# Parent 9ca8d04cb915afc9e42278b2fe28284cc44d05f1
small refinements: simplifying send-code, fixing RInfo for python3, extend .hgignore
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -95,7 +95,7 @@ class Gateway(gateway_base.BaseGateway):
""" return some sys/env information from remote. """
if update or not hasattr(self, '_cache_rinfo'):
ch = self.remote_exec(rinfo_source)
- self._cache_rinfo = RInfo(**ch.receive())
+ self._cache_rinfo = RInfo(ch.receive())
return self._cache_rinfo
def remote_exec(self, source):
@@ -156,7 +156,7 @@ class Gateway(gateway_base.BaseGateway):
class RInfo:
- def __init__(self, **kwargs):
+ def __init__(self, kwargs):
self.__dict__.update(kwargs)
def __repr__(self):
info = ", ".join(["%s=%s" % item
--- a/.hgignore
+++ b/.hgignore
@@ -5,3 +5,4 @@ dist/
syntax:glob
*.pyc
+*$py.class
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -595,19 +595,13 @@ class BaseGateway(object):
self._trace('leaving %r' % threading.currentThread())
def _send(self, msg):
- if msg is None:
- self._io.close_write()
- else:
- try:
- msg.writeto(self._io)
- except:
- excinfo = self.exc_info()
- self._trace(geterrortext(excinfo))
- else:
- self._trace('sent -> %r' % msg)
+ assert isinstance(msg, Message)
+ msg.writeto(self._io)
+ self._trace('sent -> %r' % msg)
def _stopsend(self):
- self._send(None)
+ self._io.close_write()
+ self._trace('closing IO')
def _stopexec(self):
pass
From commits-noreply at bitbucket.org Tue Nov 3 18:05:00 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 17:05:00 +0000 (UTC)
Subject: [execnet-commit] execnet commit 5302d8c04c02: simplify serialization
Message-ID: <20091103170500.B6D3E7EF49@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257267874 -3600
# Node ID 5302d8c04c02a193d1e2463aa0cfd615ebfe32de
# Parent 6371d6423d5d28faba17462c154b3282db0c3d99
simplify serialization
- automatically generate serialization opcodes encoding
- remove VERSION opcode because it's not needed for execnet
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -698,130 +698,6 @@ FOUR_BYTE_INT_MAX = 2147483647
FLOAT_FORMAT = "!d"
FLOAT_FORMAT_SIZE = struct.calcsize(FLOAT_FORMAT)
-# Protocol constants
-VERSION_NUMBER = 1
-VERSION = b(chr(VERSION_NUMBER))
-NONE = b('n')
-NONE = b('n')
-PY2STRING = b('s')
-PY3STRING = b('t')
-UNICODE = b('u')
-BYTES = b('b')
-NEWLIST = b('l')
-BUILDTUPLE = b('T')
-SETITEM = b('m')
-NEWDICT = b('d')
-INT = b('i')
-FLOAT = b('f')
-TRUE = b('1')
-FALSE = b('0')
-STOP = b('S')
-
-class Serializer(object):
-
- def __init__(self, stream):
- self.stream = stream
-
- def save(self, obj):
- self.stream.write(VERSION)
- self._save(obj)
- self.stream.write(STOP)
-
- def _save(self, obj):
- tp = type(obj)
- try:
- dispatch = self.dispatch[tp]
- except KeyError:
- raise SerializationError("can't serialize %s" % (tp,))
- dispatch(self, obj)
-
- dispatch = {}
-
- def save_none(self, non):
- self.stream.write(NONE)
- dispatch[type(None)] = save_none
-
- def save_bool(self, boolean):
- if boolean:
- self.stream.write(TRUE)
- else:
- self.stream.write(FALSE)
- dispatch[bool] = save_bool
-
- def save_bytes(self, bytes_):
- self.stream.write(BYTES)
- self._write_byte_sequence(bytes_)
- dispatch[type("".encode('ascii'))] = save_bytes
-
- if ISPY3:
- def save_string(self, s):
- self.stream.write(PY3STRING)
- self._write_unicode_string(s)
- else:
- def save_string(self, s):
- self.stream.write(PY2STRING)
- self._write_byte_sequence(s)
-
- def save_unicode(self, s):
- self.stream.write(UNICODE)
- self._write_unicode_string(s)
- dispatch[unicode] = save_unicode
- dispatch[str] = save_string
-
- def _write_unicode_string(self, s):
- try:
- as_bytes = s.encode("utf-8")
- except UnicodeEncodeError:
- raise SerializationError("strings must be utf-8 encodable")
- self._write_byte_sequence(as_bytes)
-
- def _write_byte_sequence(self, bytes_):
- self._write_int4(len(bytes_), "string is too long")
- self.stream.write(bytes_)
-
- def save_int(self, i):
- self.stream.write(INT)
- self._write_int4(i)
- dispatch[int] = save_int
- if not ISPY3:
- dispatch[long] = save_int
-
- def save_float(self, flt):
- self.stream.write(FLOAT)
- self.stream.write(struct.pack(FLOAT_FORMAT, flt))
- dispatch[float] = save_float
-
- def _write_int4(self, i, error="int must be less than %i" %
- (FOUR_BYTE_INT_MAX,)):
- if i > FOUR_BYTE_INT_MAX:
- raise SerializationError(error)
- self.stream.write(struct.pack("!i", i))
-
- def save_list(self, L):
- self.stream.write(NEWLIST)
- self._write_int4(len(L), "list is too long")
- for i, item in enumerate(L):
- self._write_setitem(i, item)
- dispatch[list] = save_list
-
- def _write_setitem(self, key, value):
- self._save(key)
- self._save(value)
- self.stream.write(SETITEM)
-
- def save_dict(self, d):
- self.stream.write(NEWDICT)
- for key, value in d.items():
- self._write_setitem(key, value)
- dispatch[dict] = save_dict
-
- def save_tuple(self, tup):
- for item in tup:
- self._save(item)
- self.stream.write(BUILDTUPLE)
- self._write_int4(len(tup), "tuple is too long")
- dispatch[tuple] = save_tuple
-
class _UnserializationOptions(object):
pass
@@ -844,6 +720,7 @@ class _Stop(Exception):
pass
class Unserializer(object):
+ num2func = {} # is filled after this class definition
def __init__(self, stream, options=UnserializationOptions()):
self.stream = stream
@@ -851,17 +728,13 @@ class Unserializer(object):
def load(self):
self.stack = []
- version = ord(self.stream.read(1))
- if version != VERSION_NUMBER:
- raise UnserializationError(
- "version mismatch: %i != %i" % (version, VERSION_NUMBER))
try:
while True:
opcode = self.stream.read(1)
if not opcode:
raise EOFError
try:
- loader = self.opcodes[opcode]
+ loader = self.num2func[opcode]
except KeyError:
raise UnserializationError("unkown opcode %s" % (opcode,))
loader(self)
@@ -872,28 +745,22 @@ class Unserializer(object):
else:
raise UnserializationError("didn't get STOP")
- opcodes = {}
-
def load_none(self):
self.stack.append(None)
- opcodes[NONE] = load_none
def load_true(self):
self.stack.append(True)
- opcodes[TRUE] = load_true
+
def load_false(self):
self.stack.append(False)
- opcodes[FALSE] = load_false
def load_int(self):
i = self._read_int4()
self.stack.append(i)
- opcodes[INT] = load_int
def load_float(self):
binary = self.stream.read(FLOAT_FORMAT_SIZE)
self.stack.append(struct.unpack(FLOAT_FORMAT, binary)[0])
- opcodes[FLOAT] = load_float
def _read_int4(self):
return struct.unpack("!i", self.stream.read(4))[0]
@@ -910,7 +777,6 @@ class Unserializer(object):
self.stack.append(as_bytes)
else:
self.stack.append(as_bytes.decode("utf-8"))
- opcodes[PY3STRING] = load_py3string
def load_py2string(self):
as_bytes = self._read_byte_string()
@@ -919,21 +785,17 @@ class Unserializer(object):
else:
s = as_bytes
self.stack.append(s)
- opcodes[PY2STRING] = load_py2string
def load_bytes(self):
s = self._read_byte_string()
self.stack.append(s)
- opcodes[BYTES] = load_bytes
def load_unicode(self):
self.stack.append(self._read_byte_string().decode("utf-8"))
- opcodes[UNICODE] = load_unicode
def load_newlist(self):
length = self._read_int4()
self.stack.append([None] * length)
- opcodes[NEWLIST] = load_newlist
def load_setitem(self):
if len(self.stack) < 3:
@@ -941,19 +803,140 @@ class Unserializer(object):
value = self.stack.pop()
key = self.stack.pop()
self.stack[-1][key] = value
- opcodes[SETITEM] = load_setitem
def load_newdict(self):
self.stack.append({})
- opcodes[NEWDICT] = load_newdict
def load_buildtuple(self):
length = self._read_int4()
tup = tuple(self.stack[-length:])
del self.stack[-length:]
self.stack.append(tup)
- opcodes[BUILDTUPLE] = load_buildtuple
def load_stop(self):
raise _Stop
- opcodes[STOP] = load_stop
+
+# automatically build opcodes and byte-encoding
+
+class opcode:
+ """ container for name -> num mappings. """
+
+def _buildopcodes():
+ l = []
+ for name, func in Unserializer.__dict__.items():
+ if name.startswith("load_"):
+ opname = name[5:].upper()
+ l.append((opname, func))
+ l.sort()
+ for i,(opname, func) in enumerate(l):
+ assert i < 26, "xxx"
+ i = b(chr(64+i))
+ Unserializer.num2func[i] = func
+ setattr(opcode, opname, i)
+
+_buildopcodes()
+
+class Serializer(object):
+ dispatch = {}
+
+ def __init__(self, stream):
+ self.stream = stream
+
+ def save(self, obj):
+ self._save(obj)
+ self.stream.write(opcode.STOP)
+
+ def _save(self, obj):
+ tp = type(obj)
+ try:
+ dispatch = self.dispatch[tp]
+ except KeyError:
+ raise SerializationError("can't serialize %s" % (tp,))
+ dispatch(self, obj)
+
+
+ def save_none(self, non):
+ self.stream.write(opcode.NONE)
+ dispatch[type(None)] = save_none
+
+ def save_bool(self, boolean):
+ if boolean:
+ self.stream.write(opcode.TRUE)
+ else:
+ self.stream.write(opcode.FALSE)
+ dispatch[bool] = save_bool
+
+ def save_bytes(self, bytes_):
+ self.stream.write(opcode.BYTES)
+ self._write_byte_sequence(bytes_)
+ dispatch[type("".encode('ascii'))] = save_bytes
+
+ if ISPY3:
+ def save_string(self, s):
+ self.stream.write(opcode.PY3STRING)
+ self._write_unicode_string(s)
+ else:
+ def save_string(self, s):
+ self.stream.write(opcode.PY2STRING)
+ self._write_byte_sequence(s)
+
+ def save_unicode(self, s):
+ self.stream.write(opcode.UNICODE)
+ self._write_unicode_string(s)
+ dispatch[unicode] = save_unicode
+ dispatch[str] = save_string
+
+ def _write_unicode_string(self, s):
+ try:
+ as_bytes = s.encode("utf-8")
+ except UnicodeEncodeError:
+ raise SerializationError("strings must be utf-8 encodable")
+ self._write_byte_sequence(as_bytes)
+
+ def _write_byte_sequence(self, bytes_):
+ self._write_int4(len(bytes_), "string is too long")
+ self.stream.write(bytes_)
+
+ def save_int(self, i):
+ self.stream.write(opcode.INT)
+ self._write_int4(i)
+ dispatch[int] = save_int
+ if not ISPY3:
+ dispatch[long] = save_int
+
+ def save_float(self, flt):
+ self.stream.write(opcode.FLOAT)
+ self.stream.write(struct.pack(FLOAT_FORMAT, flt))
+ dispatch[float] = save_float
+
+ def _write_int4(self, i, error="int must be less than %i" %
+ (FOUR_BYTE_INT_MAX,)):
+ if i > FOUR_BYTE_INT_MAX:
+ raise SerializationError(error)
+ self.stream.write(struct.pack("!i", i))
+
+ def save_list(self, L):
+ self.stream.write(opcode.NEWLIST)
+ self._write_int4(len(L), "list is too long")
+ for i, item in enumerate(L):
+ self._write_setitem(i, item)
+ dispatch[list] = save_list
+
+ def _write_setitem(self, key, value):
+ self._save(key)
+ self._save(value)
+ self.stream.write(opcode.SETITEM)
+
+ def save_dict(self, d):
+ self.stream.write(opcode.NEWDICT)
+ for key, value in d.items():
+ self._write_setitem(key, value)
+ dispatch[dict] = save_dict
+
+ def save_tuple(self, tup):
+ for item in tup:
+ self._save(item)
+ self.stream.write(opcode.BUILDTUPLE)
+ self._write_int4(len(tup), "tuple is too long")
+ dispatch[tuple] = save_tuple
+
From commits-noreply at bitbucket.org Tue Nov 3 21:21:27 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 20:21:27 +0000 (UTC)
Subject: [execnet-commit] execnet commit dd5b365f3af7: adding examples and
fixing code for __channelexec__ and a new "command" example.
Message-ID: <20091103202127.425BC7EF36@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257279619 -3600
# Node ID dd5b365f3af7f7165c50e6b9c576d23d5725e463
# Parent 4322e331bdc52010664b8b5fe14d7b0413f16a9b
adding examples and fixing code for __channelexec__ and a new "command" example.
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -2,6 +2,47 @@
execnet examples
==============================================================================
+execnet is under active development. If you have questions
+or ideas or otherwise want to contribute please feel welcome
+to join the `execnet-dev`_ mailing list.
+
+.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
+
+Connect to Python2/Numpy from Python3
+----------------------------------------
+
+Here we run a Python3 interpreter to connect to a Python2.6 interpreter
+that has numpy installed. We send items to be added to an array and
+receive back the remote "repr" of the array::
+
+ import execnet
+ gw = execnet.PopenGateway("python2.6")
+ channel = gw.remote_exec("""
+ import numpy
+ array = numpy.array([1,2,3])
+ while 1:
+ x = channel.receive()
+ if x is None:
+ break
+ array = numpy.append(array, x)
+ channel.send(repr(array))
+ """)
+ for x in range(10):
+ channel.send(x)
+ channel.send(None)
+ print (channel.receive())
+
+will print on the CPython3.1 side::
+
+ array([1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+
+A more refined real-life example of python3/python2 interaction
+is the anyvc_ project which uses version-control bindings in
+a Python2 subprocess in order to offer Python3-based library
+functionality.
+
+.. _anyvc: http://bitbucket.org/RonnyPfannschmidt/anyvc/overview/
+
Work with Java objects from CPython
----------------------------------------
@@ -68,7 +109,66 @@ A PopenGateway has the same working dire
>>> ch = gw.remote_exec("import os; channel.send(os.getcwd())")
>>> res = ch.receive()
>>> assert res == os.getcwd()
- >>> gw.exit()
+
+.. _channelexec:
+
+Sending modules with remote_exec
+--------------------------------------------------------------
+
+You can pass a module object to ``remote_exec`` in which case
+its source code will be sent. No dependencies will be transferred
+so the module must be self-contained or only use modules that are
+installed on the "other" side. Module code can detect if it is
+running in a remote_exec situation by checking for the special
+``__name__`` attribute::
+
+ # content of a module remote1.py
+
+ if __name__ == '__channelexec__':
+ channel.send('initialization complete')
+
+You can now send the module like this::
+
+ >>> import execnet, remote1
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec(remote1)
+ >>> print (ch.receive())
+
+which will print the 'initialization complete' string.
+
+.. _command:
+
+A simple command pattern
+--------------------------------------------------------------
+
+If you want the remote side to serve a number
+of synchronous function calls, here is a base
+implementation::
+
+ # contents of: remotecmd.py
+ def simple(arg):
+ return arg + 1
+
+ if __name__ == '__channelexec__':
+ for item in channel:
+ funcname = item[0]
+ func = globals()[funcname]
+ args = item[1:]
+ result = func(*args)
+ channel.send(result)
+
+Then on the local side you can do::
+
+ >>> import execnet, remotecmd
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec(remotecmd)
+ >>> ch.send(('simple', 10)) # execute func-call remotely
+ >>> ch.receive()
+ 11
+
+It's straight forward to build a proxy-object
+that would hide the details and perform remote
+function calls with basic input and output values.
Synchronously receive results from two sub processes
-----------------------------------------------------
@@ -145,19 +245,3 @@ socketserver::
print socketgw._rinfo() # print some info about the remote environment
-
-Sending a module / checking if run through remote_exec
---------------------------------------------------------------
-
-You can pass a module object to ``remote_exec`` in which case
-its source code will be sent. No dependencies will be transferred
-so the module must be self-contained or only use modules that are
-installed on the "other" side. Module code can detect if it is
-running in a remote_exec situation by checking for the special
-``__name__`` attribute like this::
-
- if __name__ == '__channelexec__':
- # ... call module functions ...
-
-
-
--- a/doc/example/svn-sync-repo.py
+++ b/doc/example/svn-sync-repo.py
@@ -7,7 +7,7 @@ uses execnet.
"""
-import py
+import execnet
import sys, os
def usage():
@@ -84,7 +84,7 @@ def main(args):
def svn_load(repo, dumpchannel, maxcount=100):
# every maxcount we will send an ACK to the other
# side in order to synchronise and avoid our side
- # growing buffers (py.execnet does not control
+ # growing buffers (execnet does not control
# RAM usage or receive queue sizes)
dumpchannel.send(maxcount)
f = os.popen("svnadmin load -q %s" %(repo, ), "w")
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -8,6 +8,7 @@ import textwrap
import execnet
from execnet.gateway_base import Message, Popen2IO, SocketIO
from execnet import gateway_base
+ModuleType = type(os)
debug = False
@@ -104,7 +105,10 @@ class Gateway(gateway_base.BaseGateway):
and has the sister 'channel' object in its global
namespace.
"""
- source = textwrap.dedent(str(source))
+ if isinstance(source, ModuleType):
+ source = inspect.getsource(source)
+ else:
+ source = textwrap.dedent(str(source))
channel = self.newchannel()
self._send(Message.CHANNEL_OPEN(channel.id, source))
return channel
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -23,6 +23,15 @@ class TestBasicRemoteExecution:
name = channel.receive()
assert name == "__channelexec__"
+ def test_remote_exec_module(self, tmpdir, gw):
+ p = tmpdir.join("remotetest.py")
+ p.write("channel.send(1)")
+ mod = type(os)("remotetest")
+ mod.__file__ = str(p)
+ channel = gw.remote_exec(mod)
+ name = channel.receive()
+ assert name == 1
+
def test_correct_setup_no_py(self, gw):
channel = gw.remote_exec("""
import sys
--- /dev/null
+++ b/doc/example/remotecmd.py
@@ -0,0 +1,14 @@
+
+
+# contents of: remotecmd.py
+def simple(arg):
+ return arg + 1
+
+if __name__ == '__channelexec__':
+ for item in channel:
+ funcname = item[0]
+ func = globals()[funcname]
+ args = item[1:]
+ result = func(*args)
+ channel.send(result)
+
--- /dev/null
+++ b/doc/example/py3topy2.py
@@ -0,0 +1,17 @@
+
+import execnet
+gw = execnet.PopenGateway("python2.6")
+channel = gw.remote_exec("""
+ import numpy
+ array = numpy.array([1,2,3])
+ while 1:
+ x = channel.receive()
+ if x is None:
+ break
+ array = numpy.append(array, x)
+ channel.send(repr(array))
+""")
+for x in range(10):
+ channel.send(x)
+channel.send(None)
+print (channel.receive())
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -48,6 +48,12 @@ Here is an self-contained example for re
>>> remote_pid != os.getpid()
True
+If you'd like to avoid inlining source strings take a look
+at the channelexec_ and command_ example.
+
+
+.. _channelexec: examples.html#channelexec
+.. _command: examples.html#command
.. _`Channel`:
.. _`channel-api`:
From commits-noreply at bitbucket.org Tue Nov 3 21:21:29 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 20:21:29 +0000 (UTC)
Subject: [execnet-commit] execnet commit dea537cf22d4: automatically find
"save_typename" methods instead of redundant registration
Message-ID: <20091103202129.131EF7EF37@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257269569 -3600
# Node ID dea537cf22d47f35d430069e02b313d9162667e9
# Parent 5302d8c04c02a193d1e2463aa0cfd615ebfe32de
automatically find "save_typename" methods instead of redundant registration
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -837,10 +837,10 @@ def _buildopcodes():
_buildopcodes()
class Serializer(object):
- dispatch = {}
def __init__(self, stream):
self.stream = stream
+ self.dispatch = {}
def save(self, obj):
self._save(obj)
@@ -851,40 +851,38 @@ class Serializer(object):
try:
dispatch = self.dispatch[tp]
except KeyError:
- raise SerializationError("can't serialize %s" % (tp,))
- dispatch(self, obj)
+ methodname = 'save_' + tp.__name__.lower()
+ meth = getattr(self, methodname, None)
+ if meth is None:
+ raise SerializationError("can't serialize %s" % (tp,))
+ dispatch = self.dispatch[tp] = meth
+ dispatch(obj)
-
- def save_none(self, non):
+ def save_nonetype(self, non):
self.stream.write(opcode.NONE)
- dispatch[type(None)] = save_none
def save_bool(self, boolean):
if boolean:
self.stream.write(opcode.TRUE)
else:
self.stream.write(opcode.FALSE)
- dispatch[bool] = save_bool
def save_bytes(self, bytes_):
self.stream.write(opcode.BYTES)
self._write_byte_sequence(bytes_)
- dispatch[type("".encode('ascii'))] = save_bytes
if ISPY3:
- def save_string(self, s):
+ def save_str(self, s):
self.stream.write(opcode.PY3STRING)
self._write_unicode_string(s)
else:
- def save_string(self, s):
+ def save_str(self, s):
self.stream.write(opcode.PY2STRING)
self._write_byte_sequence(s)
def save_unicode(self, s):
self.stream.write(opcode.UNICODE)
self._write_unicode_string(s)
- dispatch[unicode] = save_unicode
- dispatch[str] = save_string
def _write_unicode_string(self, s):
try:
@@ -900,14 +898,11 @@ class Serializer(object):
def save_int(self, i):
self.stream.write(opcode.INT)
self._write_int4(i)
- dispatch[int] = save_int
- if not ISPY3:
- dispatch[long] = save_int
+ save_long = save_int # only used from python2
def save_float(self, flt):
self.stream.write(opcode.FLOAT)
self.stream.write(struct.pack(FLOAT_FORMAT, flt))
- dispatch[float] = save_float
def _write_int4(self, i, error="int must be less than %i" %
(FOUR_BYTE_INT_MAX,)):
@@ -920,7 +915,6 @@ class Serializer(object):
self._write_int4(len(L), "list is too long")
for i, item in enumerate(L):
self._write_setitem(i, item)
- dispatch[list] = save_list
def _write_setitem(self, key, value):
self._save(key)
@@ -931,12 +925,9 @@ class Serializer(object):
self.stream.write(opcode.NEWDICT)
for key, value in d.items():
self._write_setitem(key, value)
- dispatch[dict] = save_dict
def save_tuple(self, tup):
for item in tup:
self._save(item)
self.stream.write(opcode.BUILDTUPLE)
self._write_int4(len(tup), "tuple is too long")
- dispatch[tuple] = save_tuple
-
From commits-noreply at bitbucket.org Tue Nov 3 21:21:31 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 20:21:31 +0000 (UTC)
Subject: [execnet-commit] execnet commit 53e5b0719e1a: hide a spurious
finalization issue
Message-ID: <20091103202131.481AC7EF3A@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257279609 -3600
# Node ID 53e5b0719e1a886799d197988383964e1ad5b9ad
# Parent 4983a8c1c244abf3c741ec47426a8e9e5623b478
hide a spurious finalization issue
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -292,7 +292,10 @@ class Channel(object):
Msg = Message.CHANNEL_LAST_MESSAGE
else:
Msg = Message.CHANNEL_CLOSE
- self.gateway._send(Msg(self.id))
+ try:
+ self.gateway._send(Msg(self.id))
+ except ValueError: # XXX IO operation on closed file, why?
+ pass
def _getremoteerror(self):
try:
From commits-noreply at bitbucket.org Tue Nov 3 21:21:31 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 20:21:31 +0000 (UTC)
Subject: [execnet-commit] execnet commit 4983a8c1c244: use per-gateway
serializers and unserializers
Message-ID: <20091103202131.04C7F7EF39@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257270498 -3600
# Node ID 4983a8c1c244abf3c741ec47426a8e9e5623b478
# Parent dea537cf22d47f35d430069e02b313d9162667e9
use per-gateway serializers and unserializers
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -70,18 +70,20 @@ def test_io_message(anypython, tmpdir):
temp_out = BytesIO()
temp_in = BytesIO()
io = Popen2IO(temp_out, temp_in)
+ serializer = Serializer(io)
+ unserializer = Unserializer(io)
for i, msg_cls in Message._types.items():
print ("checking %s %s" %(i, msg_cls))
for data in "hello", "hello".encode('ascii'):
msg1 = msg_cls(i, data)
- msg1.writeto(io)
+ msg1.writeto(serializer)
x = io.outfile.getvalue()
io.outfile.truncate(0)
io.outfile.seek(0)
io.infile.seek(0)
io.infile.write(x)
io.infile.seek(0)
- msg2 = Message.readfrom(io)
+ msg2 = Message.readfrom(unserializer)
assert msg1.channelid == msg2.channelid, (msg1, msg2)
assert msg1.data == msg2.data
print ("all passed")
@@ -160,10 +162,12 @@ class TestMessage:
def test_wire_protocol(self):
for cls in Message._types.values():
one = py.io.BytesIO()
+ serializer = gateway_base.Serializer(one)
data = '23'.encode('ascii')
- cls(42, data).writeto(one)
+ cls(42, data).writeto(serializer)
two = py.io.BytesIO(one.getvalue())
- msg = Message.readfrom(two)
+ unserializer = gateway_base.Unserializer(two)
+ msg = Message.readfrom(unserializer)
assert isinstance(msg, cls)
assert msg.channelid == 42
assert msg.data == data
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -133,13 +133,11 @@ class Message:
self.channelid = channelid
self.data = data
- def writeto(self, io):
- ser = Serializer(io)
- ser.save((self.msgtype, self.channelid, self.data))
+ def writeto(self, serializer):
+ serializer.save((self.msgtype, self.channelid, self.data))
- def readfrom(cls, io):
- unser = Unserializer(io)
- msgtype, senderid, data = unser.load()
+ def readfrom(cls, unserializer):
+ msgtype, senderid, data = unserializer.load()
return cls._types[msgtype](senderid, data)
readfrom = classmethod(readfrom)
@@ -544,6 +542,7 @@ class BaseGateway(object):
self._io = io
self._channelfactory = ChannelFactory(self, _startcount)
self._receivelock = threading.RLock()
+ self._serializer = Serializer(io)
def _initreceive(self):
self._receiverthread = threading.Thread(name="receiver",
@@ -563,10 +562,11 @@ class BaseGateway(object):
def _thread_receiver(self):
self._trace("starting to receive")
+ unserializer = Unserializer(self._io)
try:
while 1:
try:
- msg = Message.readfrom(self._io)
+ msg = Message.readfrom(unserializer)
self._trace("received <- %r" % msg)
_receivelock = self._receivelock
_receivelock.acquire()
@@ -596,7 +596,7 @@ class BaseGateway(object):
def _send(self, msg):
assert isinstance(msg, Message)
- msg.writeto(self._io)
+ msg.writeto(self._serializer)
self._trace('sent -> %r' % msg)
def _stopsend(self):
From commits-noreply at bitbucket.org Tue Nov 3 21:21:31 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 20:21:31 +0000 (UTC)
Subject: [execnet-commit] execnet commit 4322e331bdc5: add and use apipkg.py
for lazy loading.
Message-ID: <20091103202131.6C3B77EF3B@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257279612 -3600
# Node ID 4322e331bdc52010664b8b5fe14d7b0413f16a9b
# Parent 53e5b0719e1a886799d197988383964e1ad5b9ad
add and use apipkg.py for lazy loading.
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -4,12 +4,18 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
+__version__ = "1.0.0b1"
-__version__ = "1.0.0b1"
-__author__ = "holger krekel and others"
+import execnet.apipkg
-from execnet.gateway import PopenGateway, SocketGateway, SshGateway
-from execnet.gateway import HostNotFound
-from execnet.xspec import makegateway, XSpec
-from execnet.multi import MultiGateway,MultiChannel
-from execnet.rsync import RSync
+execnet.apipkg.initpkg(__name__, {
+ 'PopenGateway': '.gateway:PopenGateway',
+ 'SocketGateway': '.gateway:SocketGateway',
+ 'SshGateway': '.gateway:SshGateway',
+ 'HostNotFound': '.gateway:HostNotFound',
+ 'makegateway': '.xspec:makegateway',
+ 'XSpec': '.xspec:XSpec',
+ 'MultiGateway': '.multi:MultiGateway',
+ 'MultiChannel': '.multi:MultiChannel',
+ 'RSync': '.rsync:RSync',
+})
--- /dev/null
+++ b/execnet/apipkg.py
@@ -0,0 +1,69 @@
+"""
+apipkg: control the exported namespace of a python package.
+
+see http://pypi.python.org/pypi/apipkg
+
+(c) holger krekel, 2009 - MIT license
+"""
+import sys
+from types import ModuleType
+
+__version__ = "1.0b2"
+
+def initpkg(pkgname, exportdefs):
+ """ initialize given package from the export definitions. """
+ mod = ApiModule(pkgname, exportdefs, implprefix=pkgname)
+ oldmod = sys.modules[pkgname]
+ mod.__file__ = getattr(oldmod, '__file__', None)
+ mod.__version__ = getattr(oldmod, '__version__', None)
+ mod.__path__ = getattr(oldmod, '__path__', None)
+ sys.modules[pkgname] = mod
+
+def importobj(modpath, attrname):
+ module = __import__(modpath, None, None, ['__doc__'])
+ return getattr(module, attrname)
+
+class ApiModule(ModuleType):
+ def __init__(self, name, importspec, implprefix=None):
+ self.__name__ = name
+ self.__all__ = list(importspec)
+ self.__map__ = {}
+ self.__implprefix__ = implprefix or name
+ for name, importspec in importspec.items():
+ if isinstance(importspec, dict):
+ subname = '%s.%s'%(self.__name__, name)
+ apimod = ApiModule(subname, importspec, implprefix)
+ sys.modules[subname] = apimod
+ setattr(self, name, apimod)
+ else:
+ modpath, attrname = importspec.split(':')
+ if modpath[0] == '.':
+ modpath = implprefix + modpath
+ if name == '__doc__':
+ self.__doc__ = importobj(modpath, attrname)
+ else:
+ self.__map__[name] = (modpath, attrname)
+
+ def __repr__(self):
+ return '' % (self.__name__,)
+
+ def __getattr__(self, name):
+ try:
+ modpath, attrname = self.__map__[name]
+ except KeyError:
+ raise AttributeError(name)
+ else:
+ result = importobj(modpath, attrname)
+ setattr(self, name, result)
+ del self.__map__[name]
+ return result
+
+ def __dict__(self):
+ # force all the content of the module to be loaded when __dict__ is read
+ dictdescr = ModuleType.__dict__['__dict__']
+ dict = dictdescr.__get__(self)
+ if dict is not None:
+ for name in self.__all__:
+ hasattr(self, name) # force attribute load, ignore errors
+ return dict
+ __dict__ = property(__dict__)
From commits-noreply at bitbucket.org Tue Nov 3 21:45:13 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 20:45:13 +0000 (UTC)
Subject: [execnet-commit] execnet commit 39e4b3a1397a: packaging a 1.0.0b1
release
Message-ID: <20091103204513.68FC87EEFD@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257281076 -3600
# Node ID 39e4b3a1397adad17ecfcd9e65a08521d90b2795
# Parent dd5b365f3af7f7165c50e6b9c576d23d5725e463
packaging a 1.0.0b1 release
--- a/setup.py
+++ b/setup.py
@@ -30,7 +30,7 @@ def main():
author='holger krekel and others',
author_email='holger at merlinux.eu',
classifiers=[
- 'Development Status :: 3 - Alpha',
+ 'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Operating System :: POSIX',
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,21 @@
+LICENSE
+CHANGELOG
+README.txt
+setup.py
+execnet/__init__.py
+execnet/apipkg.py
+execnet/gateway.py
+execnet/gateway_base.py
+execnet/multi.py
+execnet/rsync.py
+execnet/rsync_remote.py
+execnet/serializer.py
+execnet/threadpool.py
+execnet/xspec.py
+execnet/script/__init__.py
+execnet/script/loop_socketserver.py
+execnet/script/quitserver.py
+execnet/script/shell.py
+execnet/script/socketserver.py
+execnet/script/socketserverservice.py
+execnet/script/xx.py
--- a/LICENSE
+++ b/LICENSE
@@ -3,15 +3,15 @@ License (GPL), version 2 or later.
See http://www.fsf.org/licensing/licenses/ for more information.
-This package contains code and contributions from
+This package contains some code and contributions from
Armin Rigo, Benjamin Peterson, Carl Friedrich Bolz,
Maciej Fijalkowski, Ronny Pfannschmidt and others
-which is useable under the MIT license.
+which are useable under the MIT license.
If you have questions and/or want to use parts of
the code under a different license than the GPL
-please don't hesitate to contact me.
+please contact me.
-holger krekel, October 2009, holger at merlinux eu
+holger krekel, November 2009, holger at merlinux eu
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,12 @@
+
+1.0.0b1
+----------------------------
+
+* added new examples for NumPy, Jython, IronPython
+* improved documentation
+* include apipkg.py for lazy-importing
+* integrated new serializer code from Benjamin Peterson
+* improved support for Jython-2.5.1
1.0.0alpha2
----------------------------
From commits-noreply at bitbucket.org Tue Nov 3 21:45:15 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 3 Nov 2009 20:45:15 +0000 (UTC)
Subject: [execnet-commit] execnet commit 0a17bf52bab1: Added tag 1.0.0b1 for
changeset 39e4b3a1397a
Message-ID: <20091103204515.4CE067EF34@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257281087 -3600
# Node ID 0a17bf52bab1572b21fdb79031fa4a1a43c055c5
# Parent 39e4b3a1397adad17ecfcd9e65a08521d90b2795
Added tag 1.0.0b1 for changeset 39e4b3a1397a
--- a/.hgtags
+++ b/.hgtags
@@ -0,0 +1,1 @@
+39e4b3a1397adad17ecfcd9e65a08521d90b2795 1.0.0b1
From commits-noreply at bitbucket.org Wed Nov 4 16:03:32 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 4 Nov 2009 15:03:32 +0000 (UTC)
Subject: [execnet-commit] execnet commit 0868fc4f0c55: tweaks to the home
page
Message-ID: <20091104150332.3E5F47EEFE@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257282806 -3600
# Node ID 0868fc4f0c550dfc33cfaaefaeb89850443dd6da
# Parent 0a17bf52bab1572b21fdb79031fa4a1a43c055c5
tweaks to the home page
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -15,7 +15,7 @@ package installation is only required at
interoperation between CPython 2.4-3.1, Jython 2.5.1, PyPy 1.1 and IronPython
and works well on Windows, Linux and OSX systems.
-execnet was written and is maintained by `Holger Krekel`_ with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
+execnet was written and is maintained by `Holger Krekel`_ (blog_) with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
.. note::
Support of Jython and IronPython are experimental at this point.
@@ -25,7 +25,7 @@ execnet was written and is maintained by
Getting Started
====================
-To install a public `pypi release`_ via `easy_install`_::
+Install a public `pypi release`_ via `easy_install`_ or ``pip``::
easy_install -U execnet
@@ -49,7 +49,8 @@ You are welcome to:
* join the #pylib IRC channel on Freenode
* subscribe to the `execnet-commit`_ mailing list
-.. _`Holger Krekel`: http://tetamap.wordpress.com
+.. _`Holger Krekel`: http://twitter.com/hpk42
+.. _`blog`: http://tetamap.wordpress.com
.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
.. _`open an issue`: http://bitbucket.org/hpk42/execnet/issues/
From commits-noreply at bitbucket.org Wed Nov 4 16:03:34 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 4 Nov 2009 15:03:34 +0000 (UTC)
Subject: [execnet-commit] execnet commit 22303634ba47: make tracing
dependent on existence of EXECNET_DEBUG instead of module-global
Message-ID: <20091104150334.6AF877EEFC@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257346995 -3600
# Node ID 22303634ba47b233b76f2f4bbf500dea288d0359
# Parent 0868fc4f0c550dfc33cfaaefaeb89850443dd6da
make tracing dependent on existence of EXECNET_DEBUG instead of module-global
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -39,8 +39,8 @@ class Gateway(gateway_base.BaseGateway):
_cleanup = GatewayCleanup()
def __init__(self, io):
+ super(Gateway, self).__init__(io=io, _startcount=1)
self._remote_bootstrap_gateway(io)
- super(Gateway, self).__init__(io=io, _startcount=1)
self._initreceive()
self._cleanup.register(self)
@@ -51,15 +51,14 @@ class Gateway(gateway_base.BaseGateway):
else:
addr = ''
try:
- r = (self._receiverthread.isAlive() and "receiving" or
+ r = (self._receiverthread.isAlive() and "receiver-alive" or
"not receiving")
- s = "sending" # XXX
i = len(self._channelfactory.channels())
except AttributeError:
- r = s = "uninitialized"
+ r = "uninitialized"
i = "no"
- return "<%s%s %s/%s (%s active channels)>" %(
- self.__class__.__name__, addr, r, s, i)
+ return "<%s%s %s (%s active channels)>" %(
+ self.__class__.__name__, addr, r, i)
def exit(self):
""" Try to stop all exec and IO activity. """
@@ -261,7 +260,7 @@ class SocketGateway(Gateway):
# execute the above socketserverbootstrap on the other side
channel = gateway.remote_exec(socketserverbootstrap)
(realhost, realport) = channel.receive()
- #gateway._trace("new_remote received"
+ #self._trace("new_remote received"
# "port=%r, hostname = %r" %(realport, hostname))
return SocketGateway(host, realport)
new_remote = classmethod(new_remote)
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -545,7 +545,13 @@ class TestThreads:
gw.remote_init_threads(3)
py.test.raises(IOError, gw.remote_init_threads, 3)
+def test_debug(monkeypatch):
+ monkeypatch.setenv('EXECNET_DEBUG', "1")
+ source = py.std.inspect.getsource(gateway_base)
+ d = {}
+ gateway_base.do_exec(source, d)
+ assert 'debugfile' in d
def test_nodebug():
from execnet import gateway_base
- assert not gateway_base.debug
+ assert not hasattr(gateway_base, 'debugfile')
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -22,10 +22,21 @@ else:
"def reraise(cls, val, tb): raise cls, val, tb\n")
bytes = str
-default_encoding = "UTF-8"
sysex = (KeyboardInterrupt, SystemExit)
-debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid() , 'w')
+if os.environ.get('EXECNET_DEBUG'):
+ debugfile = open('/tmp/execnet-debug-%d' % os.getpid() , 'w')
+ def trace(msg):
+ try:
+ debugfile.write(unicode(msg) + "\n")
+ debugfile.flush()
+ except sysex:
+ raise
+ except:
+ sys.stderr.write("exception during tracing\n")
+else:
+ def trace(msg):
+ pass
# ___________________________________________________________________________
@@ -546,6 +557,7 @@ class BaseGateway(object):
self._channelfactory = ChannelFactory(self, _startcount)
self._receivelock = threading.RLock()
self._serializer = Serializer(io)
+ self._trace = trace # globals may be NONE at process-termination
def _initreceive(self):
self._receiverthread = threading.Thread(name="receiver",
@@ -553,16 +565,6 @@ class BaseGateway(object):
self._receiverthread.setDaemon(1)
self._receiverthread.start()
- def _trace(self, msg):
- if debug:
- try:
- debug.write(unicode(msg) + "\n")
- debug.flush()
- except sysex:
- raise
- except:
- sys.stderr.write("exception during tracing\n")
-
def _thread_receiver(self):
self._trace("starting to receive")
unserializer = Unserializer(self._io)
@@ -650,7 +652,7 @@ class SlaveGateway(BaseGateway):
except self._StopExecLoop:
break
finally:
- self._trace("serve")
+ self._trace("serving execution finished")
if joining:
self.join()
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -133,3 +133,12 @@ More info on the RSync class:
.. currentmodule:: execnet
.. autoclass:: RSync
:members: add_target,send
+
+
+Debugging execnet
+===============================================================
+
+If you set the enviornment variable ``EXECNET_DEBUG`` to any value
+trace-files will be written to ``execnet-debug-PID`` files in
+the system temporary directory. ``NUM`` will be the process id.
+
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+1.0.0b2
+--------------------------------
+* setting the environment variable EXECNET_DEBUG will generate per
+ process trace-files for debugging
1.0.0b1
----------------------------
From commits-noreply at bitbucket.org Wed Nov 4 16:27:50 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 4 Nov 2009 15:27:50 +0000 (UTC)
Subject: [execnet-commit] execnet commit 413beb635298: fix empty
tuple-handling (thanks ronny)
Message-ID: <20091104152750.AA4507EEF5@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257348460 -3600
# Node ID 413beb635298066aae894a07dfb1cadee7ccdb3a
# Parent 22303634ba47b233b76f2f4bbf500dea288d0359
fix empty tuple-handling (thanks ronny)
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -202,3 +202,9 @@ def test_none(py2, py3):
assert s == "None"
tp, s = py3.load(p)
assert s == "None"
+
+def test_tuple_nested_with_empty_in_between(py2):
+ p = py2.dump("(1, (), 3)")
+ tp, s = py2.load(p)
+ assert tp == 'tuple'
+ assert s == "(1, (), 3)"
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -814,8 +814,11 @@ class Unserializer(object):
def load_buildtuple(self):
length = self._read_int4()
- tup = tuple(self.stack[-length:])
- del self.stack[-length:]
+ if length:
+ tup = tuple(self.stack[-length:])
+ del self.stack[-length:]
+ else:
+ tup = ()
self.stack.append(tup)
def load_stop(self):
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
1.0.0b2
--------------------------------
+* fix a seralization bug with nested tuples containing empty tuples
+ (thanks to ronny for discovering it)
+
* setting the environment variable EXECNET_DEBUG will generate per
process trace-files for debugging
From commits-noreply at bitbucket.org Wed Nov 4 17:34:09 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 4 Nov 2009 16:34:09 +0000 (UTC)
Subject: [execnet-commit] execnet commit b66d332b2ad8: reduce boilerplate
around options, add a test,
Message-ID: <20091104163409.777497EEE8@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257352382 -3600
# Node ID b66d332b2ad8afbab89288d4d1989b7c86fae1c5
# Parent 413beb635298066aae894a07dfb1cadee7ccdb3a
reduce boilerplate around options, add a test,
try harder to import correct execnet, bump version.
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -28,16 +28,11 @@ def setup_module(mod):
else:
mod._py3_wrapper = PythonWrapper(_find_version("3"))
mod._py2_wrapper = PythonWrapper(py.path.local(sys.executable))
- mod._old_pypath = os.environ.get("PYTHONPATH")
- p = os.path.dirname(os.path.dirname(execnet.__file__))
- os.environ["PYTHONPATH"] = p
def teardown_module(mod):
TEMPDIR.remove(True)
- if _old_pypath is not None:
- os.environ["PYTHONPATH"] = _old_pypath
-
+pyimportdir = str(py.path.local(execnet.__file__).dirpath().dirpath())
class PythonWrapper(object):
def __init__(self, executable):
@@ -46,26 +41,28 @@ class PythonWrapper(object):
def dump(self, obj_rep):
script_file = TEMPDIR.join("dump.py")
script_file.write("""
+import sys
+sys.path.insert(0, %r)
from execnet import gateway_base as serializer
-import sys
if sys.version_info > (3, 0): # Need binary output
sys.stdout = sys.stdout.detach()
saver = serializer.Serializer(sys.stdout)
-saver.save(%s)""" % (obj_rep,))
+saver.save(%s)""" % (pyimportdir, obj_rep,))
return self.executable.sysexec(script_file)
- def load(self, data, option_args=""):
+ def load(self, data, option_args="__class__"):
script_file = TEMPDIR.join("load.py")
script_file.write(r"""
+import sys
+sys.path.insert(0, %r)
from execnet import gateway_base as serializer
-import sys
if sys.version_info > (3, 0):
sys.stdin = sys.stdin.detach()
-options = serializer.UnserializationOptions(%s)
-loader = serializer.Unserializer(sys.stdin, options)
+loader = serializer.Unserializer(sys.stdin)
+loader.%s
obj = loader.load()
sys.stdout.write(type(obj).__name__ + "\n")
-sys.stdout.write(repr(obj))""" % (option_args,))
+sys.stdout.write(repr(obj))""" % (pyimportdir, option_args,))
popen = subprocess.Popen([str(self.executable), str(script_file)],
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -152,14 +149,17 @@ def test_bytes(py2, py3):
assert tp == "bytes"
assert v == "b'hi'"
-def test_string(py2, py3):
+def test_str(py2, py3):
p = py2.dump("'xyz'")
tp, s = py2.load(p)
assert tp == "str"
assert s == "'xyz'"
- tp, s = py3.load(p)
- assert tp == "str" # depends on unserialization defaults
+ tp, s = py3.load(p, "py2str_as_py3str=True")
+ assert tp == "str"
assert s == "'xyz'"
+ tp, s = py3.load(p, "py2str_as_py3str=False")
+ assert s == "b'xyz'"
+ assert tp == "bytes"
def test_unicode(py2, py3):
p = py2.dump("u'hi'")
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -703,33 +703,16 @@ FOUR_BYTE_INT_MAX = 2147483647
FLOAT_FORMAT = "!d"
FLOAT_FORMAT_SIZE = struct.calcsize(FLOAT_FORMAT)
-class _UnserializationOptions(object):
- pass
-
-class _Py2UnserializationOptions(_UnserializationOptions):
-
- def __init__(self, py3_strings_as_str=False):
- self.py3_strings_as_str = py3_strings_as_str
-
-class _Py3UnserializationOptions(_UnserializationOptions):
-
- def __init__(self, py2_strings_as_str=True):
- self.py2_strings_as_str = py2_strings_as_str
-
-if ISPY3:
- UnserializationOptions = _Py3UnserializationOptions
-else:
- UnserializationOptions = _Py2UnserializationOptions
-
class _Stop(Exception):
pass
class Unserializer(object):
num2func = {} # is filled after this class definition
+ py2str_as_py3str = False # True
+ py3str_as_py2str = False # false means py2 will get unicode
- def __init__(self, stream, options=UnserializationOptions()):
+ def __init__(self, stream):
self.stream = stream
- self.options = options
def load(self):
self.stack = []
@@ -777,7 +760,7 @@ class Unserializer(object):
def load_py3string(self):
as_bytes = self._read_byte_string()
- if not ISPY3 and self.options.py3_strings_as_str:
+ if not ISPY3 and self.py3str_as_py2str:
# XXX Should we try to decode into latin-1?
self.stack.append(as_bytes)
else:
@@ -785,7 +768,7 @@ class Unserializer(object):
def load_py2string(self):
as_bytes = self._read_byte_string()
- if ISPY3 and self.options.py2_strings_as_str:
+ if ISPY3 and self.py2str_as_py3str:
s = as_bytes.decode("latin-1")
else:
s = as_bytes
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -4,7 +4,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.0b1"
+__version__ = "1.0.0b2"
import execnet.apipkg
From commits-noreply at bitbucket.org Wed Nov 4 20:16:20 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 4 Nov 2009 19:16:20 +0000 (UTC)
Subject: [execnet-commit] execnet commit f92bb37b9b25: only write to the
stream if serialization succeeded in toto.
Message-ID: <20091104191620.5A7127EEEB@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257362151 -3600
# Node ID f92bb37b9b25c5aa6002627ba9e3d2da4ff5703e
# Parent b66d332b2ad8afbab89288d4d1989b7c86fae1c5
only write to the stream if serialization succeeded in toto.
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -157,6 +157,22 @@ def test_stdouterrin_setnull():
assert not out
assert not err
+class PseudoChannel:
+ def __init__(self):
+ self._sent = []
+ self._closed = []
+ def send(self, obj):
+ self._sent.append(obj)
+ def close(self, errortext=None):
+ self._closed.append(errortext)
+
+def test_exectask():
+ io = py.io.BytesIO()
+ gw = gateway_base.SlaveGateway(io)
+ ch = PseudoChannel()
+ gw.executetask((ch, "raise ValueError()"))
+ assert "ValueError" in str(ch._closed[0])
+
class TestMessage:
def test_wire_protocol(self):
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -828,14 +828,23 @@ def _buildopcodes():
_buildopcodes()
class Serializer(object):
+ WRITE_ON_SUCCESS=True # more robust against serialize failures
def __init__(self, stream):
- self.stream = stream
self.dispatch = {}
+ self._stream = stream
def save(self, obj):
+ if self.WRITE_ON_SUCCESS:
+ self.streamlist = []
+ self._write = self.streamlist.append
+ else:
+ self._write = self.stream.write
self._save(obj)
- self.stream.write(opcode.STOP)
+ self._write(opcode.STOP)
+ if self.WRITE_ON_SUCCESS:
+ for x in self.streamlist:
+ self._stream.write(x)
def _save(self, obj):
tp = type(obj)
@@ -850,29 +859,29 @@ class Serializer(object):
dispatch(obj)
def save_nonetype(self, non):
- self.stream.write(opcode.NONE)
+ self._write(opcode.NONE)
def save_bool(self, boolean):
if boolean:
- self.stream.write(opcode.TRUE)
+ self._write(opcode.TRUE)
else:
- self.stream.write(opcode.FALSE)
+ self._write(opcode.FALSE)
def save_bytes(self, bytes_):
- self.stream.write(opcode.BYTES)
+ self._write(opcode.BYTES)
self._write_byte_sequence(bytes_)
if ISPY3:
def save_str(self, s):
- self.stream.write(opcode.PY3STRING)
+ self._write(opcode.PY3STRING)
self._write_unicode_string(s)
else:
def save_str(self, s):
- self.stream.write(opcode.PY2STRING)
+ self._write(opcode.PY2STRING)
self._write_byte_sequence(s)
def save_unicode(self, s):
- self.stream.write(opcode.UNICODE)
+ self._write(opcode.UNICODE)
self._write_unicode_string(s)
def _write_unicode_string(self, s):
@@ -884,25 +893,25 @@ class Serializer(object):
def _write_byte_sequence(self, bytes_):
self._write_int4(len(bytes_), "string is too long")
- self.stream.write(bytes_)
+ self._write(bytes_)
def save_int(self, i):
- self.stream.write(opcode.INT)
+ self._write(opcode.INT)
self._write_int4(i)
save_long = save_int # only used from python2
def save_float(self, flt):
- self.stream.write(opcode.FLOAT)
- self.stream.write(struct.pack(FLOAT_FORMAT, flt))
+ self._write(opcode.FLOAT)
+ self._write(struct.pack(FLOAT_FORMAT, flt))
def _write_int4(self, i, error="int must be less than %i" %
(FOUR_BYTE_INT_MAX,)):
if i > FOUR_BYTE_INT_MAX:
raise SerializationError(error)
- self.stream.write(struct.pack("!i", i))
+ self._write(struct.pack("!i", i))
def save_list(self, L):
- self.stream.write(opcode.NEWLIST)
+ self._write(opcode.NEWLIST)
self._write_int4(len(L), "list is too long")
for i, item in enumerate(L):
self._write_setitem(i, item)
@@ -910,15 +919,15 @@ class Serializer(object):
def _write_setitem(self, key, value):
self._save(key)
self._save(value)
- self.stream.write(opcode.SETITEM)
+ self._write(opcode.SETITEM)
def save_dict(self, d):
- self.stream.write(opcode.NEWDICT)
+ self._write(opcode.NEWDICT)
for key, value in d.items():
self._write_setitem(key, value)
def save_tuple(self, tup):
for item in tup:
self._save(item)
- self.stream.write(opcode.BUILDTUPLE)
+ self._write(opcode.BUILDTUPLE)
self._write_int4(len(tup), "tuple is too long")
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -11,6 +11,11 @@ queue = py.builtin._tryimport('queue', '
TESTTIMEOUT = 10.0 # seconds
skiponjython = py.test.mark.skipif("sys.platform.startswith('java')")
+def test_serialize_error(gw):
+ ch = gw.remote_exec("channel.send(ValueError(42))")
+ excinfo = py.test.raises(ch.RemoteError, "ch.receive()")
+ assert "can't serialize" in str(excinfo.value)
+
class TestBasicRemoteExecution:
def test_correct_setup(self, gw):
assert gw._receiverthread.isAlive()
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
1.0.0b2
--------------------------------
+
+* make internal protocols more robust against serialization failures
+
* fix a seralization bug with nested tuples containing empty tuples
(thanks to ronny for discovering it)
From commits-noreply at bitbucket.org Sun Nov 8 20:38:49 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 8 Nov 2009 19:38:49 +0000 (UTC)
Subject: [execnet-commit] execnet commit d157364437d2: fixing some docs,
removing issue tracker.
Message-ID: <20091108193849.7F70A7EED0@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257530358 -3600
# Node ID d157364437d2b0816ebbbbe1f6152d32edea0bf0
# Parent f92bb37b9b25c5aa6002627ba9e3d2da4ff5703e
fixing some docs, removing issue tracker.
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -40,22 +40,23 @@ Next checkout the basic api and examples
examples
changelog
-Contact channels
+Issues / Contact channels
===========================
-You are welcome to:
+If you have a questions, issues or suggestions you are welcome to join
+and post to the `execnet-dev`_ mailing list. Alternatively you
+you get see to help on the #pylib IRC channel on Freenode.
-* join and post to the `execnet-dev`_ mailing list or `open an issue`_
-* join the #pylib IRC channel on Freenode
-* subscribe to the `execnet-commit`_ mailing list
+There also is the `execnet-commit`_ mailing list
+which relays commit mails to the `bitbucket repository`_.
.. _`Holger Krekel`: http://twitter.com/hpk42
.. _`blog`: http://tetamap.wordpress.com
.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
-.. _`open an issue`: http://bitbucket.org/hpk42/execnet/issues/
.. _`easy_install`: http://peak.telecommunity.com/DevCenter/EasyInstall
+.. _`bitbucket repository`: http://bitbucket.org/hpk42/execnet/
.. _`execnet mercurial repository`: http://bitbucket.org/hpk42/execnet/
.. _`pypi release`: http://pypi.python.org/pypi/execnet
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -2,11 +2,9 @@
execnet API in a nutshell
==============================================================================
-execnet allows to create **gateways** which establish
-a self-bootstrapping connection to another
-local or remote Python interpreter. Through this
-connection you can execute code on the other side
-and maintain a data exchange via channels.
+execnet ad-hoc instantiates **gateways** to Python
+interpreter processes with which you can **remote execute
+code** and exchange basic python objects through **channels**.
.. image:: _static/basic1.png
@@ -71,7 +69,7 @@ two asynchronously running programs.
.. automethod:: Channel.send(item)
.. automethod:: Channel.receive()
- .. automethod:: Channel.setcallback(callback, endmarker)
+ .. automethod:: Channel.setcallback(callback, endmarker=_NOENDMARKER)
.. automethod:: Channel.makefile(mode, proxyclose=False)
.. automethod:: Channel.close(error)
.. automethod:: Channel.waitclose(timeout)
--- a/doc/_templates/layout.html
+++ b/doc/_templates/layout.html
@@ -2,10 +2,10 @@
{% block rootrellink %}
home |
- api |
+ basics | examples |
- issues |
- pypi |
+ mailing-list |
+ pypi-home | docindex »
{% endblock %}
From commits-noreply at bitbucket.org Sun Nov 8 20:38:51 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 8 Nov 2009 19:38:51 +0000 (UTC)
Subject: [execnet-commit] execnet commit 14fb410db7ef: * disallow closing a
channel from within remote_exec code
Message-ID: <20091108193851.2E29A7EEE1@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257703819 -3600
# Node ID 14fb410db7efb8259f982e430463c31f34071865
# Parent d157364437d2b0816ebbbbe1f6152d32edea0bf0
* disallow closing a channel from within remote_exec code
* add more tracing
* bump version
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -61,13 +61,15 @@ class Gateway(gateway_base.BaseGateway):
self.__class__.__name__, addr, r, i)
def exit(self):
- """ Try to stop all exec and IO activity. """
+ """ exit gateway (stop local execution, stop sending)"""
+ self._trace("exiting gateway")
try:
self._cleanup.unregister(self)
except KeyError:
return # we assume it's already happened
self._stopexec()
self._stopsend()
+ #self.join(timeout=timeout) # receiverthread receive close() messages
def _remote_bootstrap_gateway(self, io, extra=''):
""" return Gateway with a asynchronously remotely
@@ -186,6 +188,7 @@ class PopenCmdGateway(Gateway):
def exit(self):
super(PopenCmdGateway, self).exit()
+ self._trace("polling Popen subprocess")
self._popen.poll()
popen_bootstrapline = "import sys ; exec(eval(sys.stdin.readline()))"
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -234,6 +234,7 @@ NO_ENDMARKER_WANTED = object()
class Channel(object):
"""Communication channel between two Python Interpreter execution points."""
RemoteError = RemoteError
+ _executing = False
def __init__(self, gateway, id):
assert isinstance(id, int)
@@ -335,7 +336,14 @@ class Channel(object):
raise ValueError("mode %r not availabe" %(mode,))
def close(self, error=None):
- """ close down this channel with an optional error message. """
+ """ close down this channel with an optional error message.
+ Note that closing of a channel tied to remote_exec happens
+ automatically at the end of execution and cannot be done explicitely.
+ """
+ if self._executing:
+ raise IOError("cannot explicitly close channel within remote_exec")
+ if self._closed:
+ trace("%r channel already closed, close() called." % (self, ))
if not self._closed:
# state transition "opened/sendonly" --> "closed"
# threads warning: the channel might be closed under our feet,
@@ -345,6 +353,7 @@ class Channel(object):
put(Message.CHANNEL_CLOSE_ERROR(self.id, error))
else:
put(Message.CHANNEL_CLOSE(self.id))
+ trace("%r: sent channel close message" %(self,))
if isinstance(error, RemoteError):
self._remoteerrors.append(error)
self._closed = True # --> "closed"
@@ -584,7 +593,8 @@ class BaseGateway(object):
except EOFError:
break
except:
- self._trace(geterrortext(self.exc_info()))
+ self._trace("RECEIVERTHREAD: %s " %(
+ geterrortext(self.exc_info()), ))
break
finally:
# XXX we need to signal fatal error states to
@@ -606,7 +616,7 @@ class BaseGateway(object):
def _stopsend(self):
self._io.close_write()
- self._trace('closing IO')
+ self._trace('closed IO write pipe')
def _stopexec(self):
pass
@@ -622,14 +632,15 @@ class BaseGateway(object):
def newchannel(self):
return self._channelfactory.new()
- def join(self, joinexec=True):
- """ Wait for all IO (and by default all execution activity)
- to stop. the joinexec parameter is obsolete.
- """
+ def join(self, timeout=None):
+ """ Wait for receiverthread to terminate. """
current = threading.currentThread()
if self._receiverthread.isAlive():
self._trace("joining receiver thread")
- self._receiverthread.join()
+ self._receiverthread.join(timeout)
+ else:
+ self._trace("gateway.join() called while receiverthread "
+ "already finished")
class SlaveGateway(BaseGateway):
def _stopexec(self):
@@ -652,7 +663,7 @@ class SlaveGateway(BaseGateway):
except self._StopExecLoop:
break
finally:
- self._trace("serving execution finished")
+ self._trace("slavegateway.serve finished")
if joining:
self.join()
@@ -661,19 +672,19 @@ class SlaveGateway(BaseGateway):
try:
loc = {'channel' : channel, '__name__': '__channelexec__'}
self._trace("execution starts: %s" % repr(source)[:50])
+ channel._executing = True
try:
co = compile(source+'\n', '', 'exec')
do_exec(co, loc)
finally:
+ channel._executing = False
self._trace("execution finished")
- except sysex:
- pass
except self._StopExecLoop:
channel.close()
raise
except:
excinfo = self.exc_info()
- self._trace("got exception %s" % excinfo[1])
+ self._trace("got exception: %s" % (excinfo[1],))
errortext = geterrortext(excinfo)
channel.close(errortext)
else:
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -4,7 +4,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.0b2"
+__version__ = "1.0.0b3"
import execnet.apipkg
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -63,6 +63,12 @@ class TestBasicRemoteExecution:
channel.waitclose(TESTTIMEOUT)
py.test.raises(IOError, channel.send, 0)
+ def test_remote_exec_no_explicit_close(self, gw):
+ channel = gw.remote_exec('channel.close()')
+ excinfo = py.test.raises(channel.RemoteError,
+ "channel.waitclose(TESTTIMEOUT)")
+ assert "explicit" in excinfo.value.formatted
+
def test_remote_exec_channel_anonymous(self, gw):
channel = gw.remote_exec('''
obj = channel.receive()
@@ -408,7 +414,7 @@ class TestChannelFile:
gw._cache_rinfo = rinfo
gw.remote_exec("import os ; os.chdir(%r)" % old).waitclose()
-def test_join_blocked_execution_gateway():
+def test_join_blocked_slave_execution_gateway():
gateway = execnet.PopenGateway()
channel = gateway.remote_exec("""
import time
@@ -416,7 +422,7 @@ def test_join_blocked_execution_gateway(
""")
def doit():
gateway.exit()
- gateway.join()
+ gateway.join(timeout=3.0)
return 17
pool = WorkerPool()
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+1.0.0b3
+--------------------------------
+
+* disallow explicit close in remote_exec situation
+* perform some more detailed tracing with EXECNET_DEBUG
+
1.0.0b2
--------------------------------
From commits-noreply at bitbucket.org Mon Nov 9 16:08:45 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 9 Nov 2009 15:08:45 +0000 (UTC)
Subject: [execnet-commit] execnet commit 38c616690338: make remote_status
work with remote_init_threads
Message-ID: <20091109150845.698207EF10@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257779282 -3600
# Node ID 38c6166903389d1dc29a020c53cb4e069bec79c4
# Parent 06731e9242279b7b5877aae1168303076f50a83f
make remote_status work with remote_init_threads
also fix atomic-send behaviour and add some more tracing
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -106,7 +106,7 @@ class Gateway(gateway_base.BaseGateway):
statusdict = channel.receive()
# the other side didn't actually instantiate a channel
# so we just delete the internal id/channel mapping
- self._channelfactory._no_longer_opened(channel.id)
+ self._channelfactory._local_close(channel.id)
return RemoteStatus(statusdict)
def remote_exec(self, source):
--- a/execnet/threadpool.py
+++ b/execnet/threadpool.py
@@ -222,11 +222,15 @@ if __name__ == '__channelexec__':
maxthreads = channel.receive()
execpool = WorkerPool(maxthreads=maxthreads)
gw = channel.gateway
+ gw._trace("instantiated thread work pool maxthreads=%s" %(maxthreads,))
while 1:
+ gw._trace("waiting for new exec task")
task = gw._execqueue.get()
if task is None:
- gw._stopsend()
+ gw._trace("thread-dispatcher got None, exiting")
execpool.shutdown()
execpool.join()
+ gw._stopsend()
raise gw._StopExecLoop
+ gw._trace("dispatching exec task to thread pool")
execpool.dispatch(gw.executetask, task)
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -161,6 +161,7 @@ class PseudoChannel:
def __init__(self):
self._sent = []
self._closed = []
+ self.id = 1000
def send(self, obj):
self._sent.append(obj)
def close(self, errortext=None):
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -687,7 +687,7 @@ class SlaveGateway(BaseGateway):
channel, source = item
try:
loc = {'channel' : channel, '__name__': '__channelexec__'}
- self._trace("execution starts: %s" % repr(source)[:50])
+ self._trace("execution starts[%s]: %s" % (channel.id, repr(source)[:50]))
channel._executing = True
try:
co = compile(source+'\n', '', 'exec')
@@ -751,7 +751,8 @@ class Unserializer(object):
try:
loader = self.num2func[opcode]
except KeyError:
- raise UnserializationError("unkown opcode %s" % (opcode,))
+ raise UnserializationError("unkown opcode %r - "
+ "wire protocol corruption?" % (opcode,))
loader(self)
except _Stop:
if len(self.stack) != 1:
@@ -870,8 +871,9 @@ class Serializer(object):
self._save(obj)
self._write(opcode.STOP)
if self.WRITE_ON_SUCCESS:
- for x in self.streamlist:
- self._stream.write(x)
+ # atomic write! (compatible to python3 and python2)
+ s = type(self.streamlist[0])().join(self.streamlist)
+ self._stream.write(s)
def _save(self, obj):
tp = type(obj)
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -38,6 +38,9 @@ class TestBasicRemoteExecution:
numchan = gw._channelfactory.channels()
st = gw.remote_status()
numchan2 = gw._channelfactory.channels()
+ # note that on CPython this can not really
+ # fail because refcounting leads to immediate
+ # closure of temporary channels
assert numchan2 == numchan
def test_gateway_status_busy(self, gw):
@@ -582,6 +585,24 @@ class TestThreads:
res = c1.receive()
assert res == 42
+ def test_status_with_threads(self):
+ gw = execnet.PopenGateway()
+ gw.remote_init_threads(3)
+ c1 = gw.remote_exec("channel.send(1) ; channel.receive()")
+ c2 = gw.remote_exec("channel.send(2) ; channel.receive()")
+ c1.receive()
+ c2.receive()
+ rstatus = gw.remote_status()
+ assert rstatus.numexecuting == 2 + 1
+ assert rstatus.execqsize == 0
+ c1.send(1)
+ c2.send(1)
+ c1.waitclose()
+ c2.waitclose()
+ rstatus = gw.remote_status()
+ assert rstatus.numexecuting == 0 + 1
+ assert rstatus.execqsize == 0
+
def test_threads_twice(self):
gw = execnet.PopenGateway()
gw.remote_init_threads(3)
From commits-noreply at bitbucket.org Mon Nov 9 16:08:45 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 9 Nov 2009 15:08:45 +0000 (UTC)
Subject: [execnet-commit] execnet commit 06731e924227: introduce slightly
experimental remote_status method
Message-ID: <20091109150845.389737EF0E@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257779140 -3600
# Node ID 06731e9242279b7b5877aae1168303076f50a83f
# Parent 14fb410db7efb8259f982e430463c31f34071865
introduce slightly experimental remote_status method
to get information about the remote execution status
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -100,6 +100,15 @@ class Gateway(gateway_base.BaseGateway):
self._cache_rinfo = RInfo(ch.receive())
return self._cache_rinfo
+ def remote_status(self):
+ channel = self.newchannel()
+ self._send(Message.STATUS(channel.id))
+ statusdict = channel.receive()
+ # the other side didn't actually instantiate a channel
+ # so we just delete the internal id/channel mapping
+ self._channelfactory._no_longer_opened(channel.id)
+ return RemoteStatus(statusdict)
+
def remote_exec(self, source):
""" return channel object and connect it to a remote
execution thread where the given 'source' executes
@@ -159,7 +168,6 @@ class Gateway(gateway_base.BaseGateway):
return Handle()
-
class RInfo:
def __init__(self, kwargs):
self.__dict__.update(kwargs)
@@ -168,6 +176,8 @@ class RInfo:
for item in self.__dict__.items()])
return "" % info
+RemoteStatus = RInfo
+
rinfo_source = """
import sys, os
channel.send(dict(
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -162,6 +162,22 @@ class Message:
self.channelid, self.data)
def _setupmessages():
+ class STATUS(Message):
+ def received(self, gateway):
+ # we use self.channelid to send back information
+ # but don't instantiate a channel object
+ active_channels = gateway._channelfactory.channels()
+ numexec = 0
+ for ch in active_channels:
+ if getattr(ch, '_executing', False):
+ numexec += 1
+ d = {'receiving': True,
+ 'execqsize': gateway._execqueue.qsize(),
+ 'numchannels': len(active_channels),
+ 'numexecuting': numexec
+ }
+ gateway._send(Message.CHANNEL_DATA(self.channelid, d))
+
class CHANNEL_OPEN(Message):
def received(self, gateway):
channel = gateway._channelfactory.new(self.channelid)
@@ -191,7 +207,7 @@ def _setupmessages():
def received(self, gateway):
gateway._channelfactory._local_close(self.channelid, sendonly=True)
- classes = [CHANNEL_OPEN, CHANNEL_NEW, CHANNEL_DATA,
+ classes = [STATUS, CHANNEL_OPEN, CHANNEL_NEW, CHANNEL_DATA,
CHANNEL_CLOSE, CHANNEL_CLOSE_ERROR, CHANNEL_LAST_MESSAGE]
for i, cls in enumerate(classes):
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -28,6 +28,37 @@ class TestBasicRemoteExecution:
name = channel.receive()
assert name == "__channelexec__"
+ def test_gateway_status_simple(self, gw):
+ status = gw.remote_status()
+ assert status.receiving
+ assert not status.execqsize
+ assert status.numexecuting == 0
+
+ def test_gateway_status_no_real_channel(self, gw):
+ numchan = gw._channelfactory.channels()
+ st = gw.remote_status()
+ numchan2 = gw._channelfactory.channels()
+ assert numchan2 == numchan
+
+ def test_gateway_status_busy(self, gw):
+ ch1 = gw.remote_exec("channel.send(1); channel.receive()")
+ ch2 = gw.remote_exec("channel.receive()")
+ ch1.receive()
+ status = gw.remote_status()
+ assert status.receiving
+ assert status.numexecuting == 1 # number of active execution threads
+ assert status.execqsize == 1 # one more queued
+ assert status.numchannels == 2
+ ch1.send(None)
+ ch2.send(None)
+ ch1.waitclose()
+ ch2.waitclose()
+ status = gw.remote_status()
+ assert status.receiving
+ assert status.execqsize == 0
+ assert status.numexecuting == 0
+ assert status.numchannels == 0
+
def test_remote_exec_module(self, tmpdir, gw):
p = tmpdir.join("remotetest.py")
p.write("channel.send(1)")
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,8 @@
1.0.0b3
--------------------------------
+* introduce remote_status() method which on the low level gives
+ information about the remote side of a gateway
* disallow explicit close in remote_exec situation
* perform some more detailed tracing with EXECNET_DEBUG
From commits-noreply at bitbucket.org Mon Nov 9 22:48:44 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 9 Nov 2009 21:48:44 +0000 (UTC)
Subject: [execnet-commit] execnet commit ac44b2b936df: add support for set
and frozenset
Message-ID: <20091109214844.304657EF0B@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User Benjamin Peterson
# Date 1257803298 21600
# Node ID ac44b2b936dfa797622ae22c117030c9e26b263d
# Parent 38c6166903389d1dc29a020c53cb4e069bec79c4
add support for set and frozenset
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -125,6 +125,39 @@ def test_simple(tp_name, repr, dump, loa
assert tp == tp_name
assert v == repr
+def test_set(py2, py3):
+ for dump in py2.dump, py3.dump:
+ p = dump("set((1, 2, 3))")
+ tp, v = py2.load(p)
+ assert tp == "set"
+ assert v == "set([1, 2, 3])"
+ tp, v = py3.load(p)
+ assert tp == "set"
+ assert v == "{1, 2, 3}"
+ p = dump("set()")
+ tp, v = py2.load(p)
+ assert tp == "set"
+ assert v == "set([])"
+ tp, v = py3.load(p)
+ assert tp == "set"
+ assert v == "set()"
+
+def test_frozenset(py2, py3):
+ for dump in py2.dump, py3.dump:
+ p = dump("frozenset((1, 2, 3))")
+ tp, v = py2.load(p)
+ assert tp == "frozenset"
+ assert v == "frozenset([1, 2, 3])"
+ tp, v = py3.load(p)
+ assert tp == "frozenset"
+ assert v == "frozenset({1, 2, 3})"
+ p = dump("frozenset()")
+ tp, v = py2.load(p)
+ assert tp == "frozenset"
+ assert v == "frozenset([])"
+ tp, v = py3.load(p)
+ assert tp == "frozenset"
+ assert v == "frozenset()"
@py.test.mark.xfail
# I'm not sure if we need the complexity.
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -823,14 +823,23 @@ class Unserializer(object):
def load_newdict(self):
self.stack.append({})
- def load_buildtuple(self):
+ def _load_tuple(self):
length = self._read_int4()
if length:
tup = tuple(self.stack[-length:])
del self.stack[-length:]
else:
tup = ()
- self.stack.append(tup)
+ return tup
+
+ def load_buildtuple(self):
+ self.stack.append(self._load_tuple())
+
+ def load_set(self):
+ self.stack.append(set(self._load_tuple()))
+
+ def load_frozenset(self):
+ self.stack.append(frozenset(self._load_tuple()))
def load_stop(self):
raise _Stop
@@ -960,3 +969,15 @@ class Serializer(object):
self._save(item)
self._write(opcode.BUILDTUPLE)
self._write_int4(len(tup), "tuple is too long")
+
+ def _write_set(self, s, op):
+ for item in s:
+ self._save(item)
+ self._write(op)
+ self._write_int4(len(s), "set is too long")
+
+ def save_set(self, s):
+ self._write_set(s, opcode.SET)
+
+ def save_frozenset(self, s):
+ self._write_set(s, opcode.FROZENSET)
From commits-noreply at bitbucket.org Mon Nov 9 23:34:00 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 9 Nov 2009 22:34:00 +0000 (UTC)
Subject: [execnet-commit] execnet commit b161226fbabf: add proper long
support
Message-ID: <20091109223400.E035E7EF06@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User Benjamin Peterson
# Date 1257806191 21600
# Node ID b161226fbabfe4f9c5272c4a3dab6120de93ea1e
# Parent ac44b2b936dfa797622ae22c117030c9e26b263d
add proper long support
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -48,7 +48,15 @@ if sys.version_info > (3, 0): # Need bin
sys.stdout = sys.stdout.detach()
saver = serializer.Serializer(sys.stdout)
saver.save(%s)""" % (pyimportdir, obj_rep,))
- return self.executable.sysexec(script_file)
+ popen = subprocess.Popen([str(self.executable), str(script_file)],
+ stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ ret = popen.wait()
+ if ret:
+ raise py.process.cmdexec.Error(ret, ret, str(self.executable),
+ stdout, stderr)
+ return popen.stdout.read()
def load(self, data, option_args="__class__"):
script_file = TEMPDIR.join("load.py")
@@ -67,7 +75,7 @@ sys.stdout.write(repr(obj))""" % (pyimpo
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
- stdout, stderr = popen.communicate(data.encode("latin-1"))
+ stdout, stderr = popen.communicate(data)
ret = popen.returncode
if ret:
raise py.process.cmdexec.Error(ret, ret, str(self.executable),
@@ -159,6 +167,31 @@ def test_frozenset(py2, py3):
assert tp == "frozenset"
assert v == "frozenset()"
+def test_long(py2, py3):
+ really_big = "9223372036854775807324234"
+ p = py2.dump(really_big)
+ tp, v = py2.load(p)
+ assert tp == "long"
+ assert v == really_big + "L"
+ tp, v = py3.load(p)
+ assert tp == "int"
+ assert v == really_big
+ p = py3.dump(really_big)
+ tp, v == py3.load(p)
+ assert tp == "int"
+ assert v == really_big
+ tp, v = py2.load(p)
+ assert tp == "long"
+ assert v == really_big + "L"
+
+def test_small_long(py2, py3):
+ p = py2.dump("123L")
+ tp, s = py2.load(p)
+ assert s == "123L"
+ tp, s = py3.load(p)
+ assert s == "123"
+
+
@py.test.mark.xfail
# I'm not sure if we need the complexity.
def test_recursive_list(py2, py3):
@@ -168,11 +201,6 @@ def test_recursive_list(py2, py3):
tp, rep = py2.load(l)
assert tp == "list"
-def test_bigint_should_fail():
- py.test.raises(serializer.SerializationError,
- serializer.Serializer(py.io.BytesIO()).save,
- 123456678900)
-
def test_bytes(py2, py3):
p = py3.dump("b'hi'")
tp, v = py2.load(p)
@@ -210,13 +238,6 @@ def test_unicode(py2, py3):
assert tp == "unicode" # depends on unserialization defaults
assert s == "u'hi'"
-def test_long(py2, py3):
- p = py2.dump("123L")
- tp, s = py2.load(p)
- assert s == "123"
- tp, s = py3.load(p)
- assert s == "123"
-
def test_bool(py2, py3):
p = py2.dump("True")
tp, s = py2.load(p)
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -6,7 +6,7 @@ NOTE: aims to be compatible to Python 2.
(C) 2004-2009 Holger Krekel, Armin Rigo, Benjamin Peterson, and others
"""
import sys, os, weakref
-import threading, traceback, socket, struct
+import threading, traceback, socket, struct, binascii
try:
import queue
except ImportError:
@@ -17,10 +17,12 @@ if ISPY3:
exec("def do_exec(co, loc): exec(co, loc)\n"
"def reraise(cls, val, tb): raise val\n")
unicode = str
+ _long_type = int
else:
exec("def do_exec(co, loc): exec co in loc\n"
"def reraise(cls, val, tb): raise cls, val, tb\n")
bytes = str
+ _long_type = long
sysex = (KeyboardInterrupt, SystemExit)
@@ -721,7 +723,7 @@ class UnserializationError(SerializeErro
if ISPY3:
def b(s):
- return s.encode("ascii")
+ return s.encode("latin-1")
else:
b = str
@@ -774,6 +776,22 @@ class Unserializer(object):
i = self._read_int4()
self.stack.append(i)
+ def load_longint(self):
+ l = _decode_long(self._read_byte_string())
+ self.stack.append(int(l))
+
+ if ISPY3:
+ load_long = load_int
+ load_longlong = load_longint
+ else:
+ def load_long(self):
+ i = self._read_int4()
+ self.stack.append(long(i))
+
+ def load_longlong(self):
+ l = _decode_long(self._read_byte_string())
+ self.stack.append(l)
+
def load_float(self):
binary = self.stream.read(FLOAT_FORMAT_SIZE)
self.stack.append(struct.unpack(FLOAT_FORMAT, binary)[0])
@@ -863,7 +881,59 @@ def _buildopcodes():
setattr(opcode, opname, i)
_buildopcodes()
-
+
+# Copied straight from pickle.py.
+def _encode_long(x):
+ """Encode a long to a two's complement little-endian binary string."""
+ if x > 0:
+ ashex = hex(x)
+ assert ashex.startswith("0x")
+ njunkchars = 2 + ashex.endswith('L')
+ nibbles = len(ashex) - njunkchars
+ if nibbles & 1:
+ # need an even # of nibbles for unhexlify
+ ashex = "0x0" + ashex[2:]
+ elif int(ashex[2], 16) >= 8:
+ # "looks negative", so need a byte of sign bits
+ ashex = "0x00" + ashex[2:]
+ else:
+ # Build the 256's-complement: (1L << nbytes) + x. The trick is
+ # to find the number of bytes in linear time (although that should
+ # really be a constant-time task).
+ ashex = hex(-x)
+ njunkchars = 2 + ashex.endswith('L')
+ nibbles = len(ashex) - njunkchars
+ if nibbles & 1:
+ # Extend to a full byte.
+ nibbles += 1
+ nbits = nibbles * 4
+ x += 1 << nbits
+ ashex = hex(x)
+ njunkchars = 2 + ashex.endswith('L')
+ newnibbles = len(ashex) - njunkchars
+ if newnibbles < nibbles:
+ ashex = "0x" + "0" * (nibbles - newnibbles) + ashex[2:]
+ if int(ashex[2], 16) < 8:
+ # "looks positive", so need a byte of sign bits
+ ashex = "0xff" + ashex[2:]
+
+ if ashex.endswith('L'):
+ ashex = ashex[2:-1]
+ else:
+ ashex = ashex[2:]
+ binary = binascii.unhexlify(ashex)
+ return binary[::-1]
+
+def _decode_long(data):
+ """Decode a long from a two's complement little-endian binary string."""
+ nbytes = len(data)
+ ashex = binascii.hexlify(data[::-1])
+ n = _long_type(ashex, 16) # quadratic time before Python 2.3; linear now
+ if data[-1:] >= b('\x80'):
+ n -= 1 << (nbytes * 8)
+ return n
+
+
class Serializer(object):
WRITE_ON_SUCCESS=True # more robust against serialize failures
@@ -933,10 +1003,19 @@ class Serializer(object):
self._write_int4(len(bytes_), "string is too long")
self._write(bytes_)
+ def _save_integral(self, i, short_op, long_op):
+ if i <= FOUR_BYTE_INT_MAX:
+ self._write(short_op)
+ self._write_int4(i)
+ else:
+ self._write(long_op)
+ self._write_byte_sequence(_encode_long(i))
+
def save_int(self, i):
- self._write(opcode.INT)
- self._write_int4(i)
- save_long = save_int # only used from python2
+ self._save_integral(i, opcode.INT, opcode.LONGINT)
+
+ def save_long(self, l):
+ self._save_integral(l, opcode.LONG, opcode.LONGLONG)
def save_float(self, flt):
self._write(opcode.FLOAT)
From commits-noreply at bitbucket.org Mon Nov 9 23:50:28 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 9 Nov 2009 22:50:28 +0000 (UTC)
Subject: [execnet-commit] execnet commit 36fdf3d6180f: use
popen.communicate()
Message-ID: <20091109225028.495027EF0C@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User Benjamin Peterson
# Date 1257807180 21600
# Node ID 36fdf3d6180ffe9bdf521a27e32c0e9fd34debe3
# Parent b161226fbabfe4f9c5272c4a3dab6120de93ea1e
use popen.communicate()
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -52,11 +52,12 @@ saver.save(%s)""" % (pyimportdir, obj_re
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
- ret = popen.wait()
+ stdout, stderr = popen.communicate()
+ ret = popen.returncode
if ret:
raise py.process.cmdexec.Error(ret, ret, str(self.executable),
stdout, stderr)
- return popen.stdout.read()
+ return stdout
def load(self, data, option_args="__class__"):
script_file = TEMPDIR.join("load.py")
From commits-noreply at bitbucket.org Tue Nov 10 00:00:37 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 9 Nov 2009 23:00:37 +0000 (UTC)
Subject: [execnet-commit] execnet commit 24b1ef2beadd: take the simple way
out with long encoding: use the repr
Message-ID: <20091109230037.CD49C7EF0E@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User Benjamin Peterson
# Date 1257807766 21600
# Node ID 24b1ef2beadd85ceaea922cd9a4110319524851f
# Parent 36fdf3d6180ffe9bdf521a27e32c0e9fd34debe3
take the simple way out with long encoding: use the repr
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -6,7 +6,7 @@ NOTE: aims to be compatible to Python 2.
(C) 2004-2009 Holger Krekel, Armin Rigo, Benjamin Peterson, and others
"""
import sys, os, weakref
-import threading, traceback, socket, struct, binascii
+import threading, traceback, socket, struct
try:
import queue
except ImportError:
@@ -777,8 +777,8 @@ class Unserializer(object):
self.stack.append(i)
def load_longint(self):
- l = _decode_long(self._read_byte_string())
- self.stack.append(int(l))
+ s = self._read_byte_string()
+ self.stack.append(int(s))
if ISPY3:
load_long = load_int
@@ -789,8 +789,8 @@ class Unserializer(object):
self.stack.append(long(i))
def load_longlong(self):
- l = _decode_long(self._read_byte_string())
- self.stack.append(l)
+ l = self._read_byte_string()
+ self.stack.append(long(l))
def load_float(self):
binary = self.stream.read(FLOAT_FORMAT_SIZE)
@@ -882,58 +882,6 @@ def _buildopcodes():
_buildopcodes()
-# Copied straight from pickle.py.
-def _encode_long(x):
- """Encode a long to a two's complement little-endian binary string."""
- if x > 0:
- ashex = hex(x)
- assert ashex.startswith("0x")
- njunkchars = 2 + ashex.endswith('L')
- nibbles = len(ashex) - njunkchars
- if nibbles & 1:
- # need an even # of nibbles for unhexlify
- ashex = "0x0" + ashex[2:]
- elif int(ashex[2], 16) >= 8:
- # "looks negative", so need a byte of sign bits
- ashex = "0x00" + ashex[2:]
- else:
- # Build the 256's-complement: (1L << nbytes) + x. The trick is
- # to find the number of bytes in linear time (although that should
- # really be a constant-time task).
- ashex = hex(-x)
- njunkchars = 2 + ashex.endswith('L')
- nibbles = len(ashex) - njunkchars
- if nibbles & 1:
- # Extend to a full byte.
- nibbles += 1
- nbits = nibbles * 4
- x += 1 << nbits
- ashex = hex(x)
- njunkchars = 2 + ashex.endswith('L')
- newnibbles = len(ashex) - njunkchars
- if newnibbles < nibbles:
- ashex = "0x" + "0" * (nibbles - newnibbles) + ashex[2:]
- if int(ashex[2], 16) < 8:
- # "looks positive", so need a byte of sign bits
- ashex = "0xff" + ashex[2:]
-
- if ashex.endswith('L'):
- ashex = ashex[2:-1]
- else:
- ashex = ashex[2:]
- binary = binascii.unhexlify(ashex)
- return binary[::-1]
-
-def _decode_long(data):
- """Decode a long from a two's complement little-endian binary string."""
- nbytes = len(data)
- ashex = binascii.hexlify(data[::-1])
- n = _long_type(ashex, 16) # quadratic time before Python 2.3; linear now
- if data[-1:] >= b('\x80'):
- n -= 1 << (nbytes * 8)
- return n
-
-
class Serializer(object):
WRITE_ON_SUCCESS=True # more robust against serialize failures
@@ -1009,7 +957,7 @@ class Serializer(object):
self._write_int4(i)
else:
self._write(long_op)
- self._write_byte_sequence(_encode_long(i))
+ self._write_byte_sequence(str(i).rstrip("L").encode("ascii"))
def save_int(self, i):
self._save_integral(i, opcode.INT, opcode.LONGINT)
From commits-noreply at bitbucket.org Tue Nov 10 00:10:26 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 9 Nov 2009 23:10:26 +0000 (UTC)
Subject: [execnet-commit] execnet commit 493fc337a1f1: adding doc about new
remote_status() method, updated Changelog
Message-ID: <20091109231026.676257EF0C@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257808172 -3600
# Node ID 493fc337a1f1cb0452bfe6070844b5b1a3a540ae
# Parent 24b1ef2beadd85ceaea922cd9a4110319524851f
adding doc about new remote_status() method, updated Changelog
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -101,6 +101,7 @@ class Gateway(gateway_base.BaseGateway):
return self._cache_rinfo
def remote_status(self):
+ """ return information object about remote execution status. """
channel = self.newchannel()
self._send(Message.STATUS(channel.id))
statusdict = channel.receive()
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -75,9 +75,23 @@ two asynchronously running programs.
.. automethod:: Channel.waitclose(timeout)
.. autoattribute:: Channel.RemoteError
+
+remote_status: get low-level execution info
+===================================================
+
+.. currentmodule:: execnet.gateway
+
+All gateways offer a simple method to obtain some status
+information from the remote side.
+
+.. automethod:: Gateway.remote_status(source)
+
+Calling this method tells you e.g. how many execution
+tasks are queued, how many are executing and how many
+channels are active.
+
.. _xspec:
-
creating gateways from a generic string format
===============================================================
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,8 @@
1.0.0b3
--------------------------------
+* add support for serializing longs, sets and frozensets (thanks
+ Benjamin Peterson)
* introduce remote_status() method which on the low level gives
information about the remote side of a gateway
* disallow explicit close in remote_exec situation
From commits-noreply at bitbucket.org Thu Nov 19 21:55:29 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 20:55:29 +0000 (UTC)
Subject: [execnet-commit] execnet commit 83d9978c473e: fix EXECNET_DEBUG to
work with win32
Message-ID: <20091119205529.B3A7E7EEEB@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1257809070 -3600
# Node ID 83d9978c473ee74a34592de68311aabd771508d3
# Parent 493fc337a1f1cb0452bfe6070844b5b1a3a540ae
fix EXECNET_DEBUG to work with win32
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -27,7 +27,9 @@ else:
sysex = (KeyboardInterrupt, SystemExit)
if os.environ.get('EXECNET_DEBUG'):
- debugfile = open('/tmp/execnet-debug-%d' % os.getpid() , 'w')
+ import tempfile, os.path
+ fn = os.path.join(tempfile.gettempdir(), 'execnet-debug-%d' % os.getpid())
+ debugfile = open(fn, 'w')
def trace(msg):
try:
debugfile.write(unicode(msg) + "\n")
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
1.0.0b3
--------------------------------
+* fix EXECNET_DEBUG to work with win32
* add support for serializing longs, sets and frozensets (thanks
Benjamin Peterson)
* introduce remote_status() method which on the low level gives
From commits-noreply at bitbucket.org Thu Nov 19 21:55:31 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 20:55:31 +0000 (UTC)
Subject: [execnet-commit] execnet commit 44271e00ae78: re-org and refine
examples, make them tested by default, refine gateway __repr__
Message-ID: <20091119205531.BC41A7EEF0@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258572019 -3600
# Node ID 44271e00ae788aebea2c2fb65b3c44ed3fc3a2c7
# Parent 83d9978c473ee74a34592de68311aabd771508d3
re-org and refine examples, make them tested by default, refine gateway __repr__
--- /dev/null
+++ b/doc/example/test_channelexec.txt
@@ -0,0 +1,23 @@
+
+remote execute modules using their __name__
+--------------------------------------------------------------
+
+You can pass a module object to ``remote_exec`` in which case
+its source code will be sent. No dependencies will be transferred
+so the module must be self-contained or only use modules that are
+installed on the "other" side. Module code can detect if it is
+running in a remote_exec situation by checking for the special
+``__name__`` attribute.
+
+.. include:: remote1.py
+ :literal:
+
+You can now remote-execute the module like this::
+
+ >>> import execnet, remote1
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec(remote1)
+ >>> print (ch.receive())
+ initialization complete
+
+which will print the 'initialization complete' string.
--- /dev/null
+++ b/doc/example/conftest.py
@@ -0,0 +1,13 @@
+
+import py, sys
+
+# make execnet and example code importable
+cand = py.path.local(__file__).dirpath().dirpath().dirpath()
+if cand.join("execnet", "__init__.py").check():
+ if str(cand) not in sys.path:
+ sys.path.insert(0, str(cand))
+cand = py.path.local(__file__).dirpath()
+if str(cand) not in sys.path:
+ sys.path.insert(0, str(cand))
+
+pytest_plugins = ['doctest']
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -51,13 +51,13 @@ class Gateway(gateway_base.BaseGateway):
else:
addr = ''
try:
- r = (self._receiverthread.isAlive() and "receiver-alive" or
- "not receiving")
+ r = (self._receiverthread.isAlive() and "receive-live" or
+ "not-receiving")
i = len(self._channelfactory.channels())
except AttributeError:
r = "uninitialized"
i = "no"
- return "<%s%s %s (%s active channels)>" %(
+ return "<%s%s %s, %s active channels>" %(
self.__class__.__name__, addr, r, i)
def exit(self):
--- a/doc/example/popen_read_multiple.py
+++ b/doc/example/popen_read_multiple.py
@@ -3,7 +3,7 @@ example
reading results from possibly blocking code running in sub processes.
"""
-import py
+import execnet
NUM_PROCESSES = 5
--- /dev/null
+++ b/doc/example/test_syncreceive2.txt
@@ -0,0 +1,14 @@
+
+Synchronously receive results from two sub processes
+-----------------------------------------------------
+
+Use MultiChannels for receiving multiple results from remote code::
+
+ >>> import execnet
+ >>> ch1 = execnet.PopenGateway().remote_exec("channel.send(1)")
+ >>> ch2 = execnet.PopenGateway().remote_exec("channel.send(2)")
+ >>> mch = execnet.MultiChannel([ch1, ch2])
+ >>> l = mch.receive_each()
+ >>> assert len(l) == 2
+ >>> assert 1 in l
+ >>> assert 2 in l
--- /dev/null
+++ b/doc/example/remote1.py
@@ -0,0 +1,4 @@
+# content of a module remote1.py
+
+if __name__ == '__channelexec__':
+ channel.send('initialization complete')
--- /dev/null
+++ b/doc/example/test_remotecmd.txt
@@ -0,0 +1,24 @@
+A simple command pattern
+--------------------------------------------------------------
+
+If you want the remote side to serve a number
+of synchronous function you can setup a serving
+loop and invent your own local protocol
+
+.. include:: remotecmd.py
+ :literal:
+
+Then on the local side you can do::
+
+ >>> import execnet, remotecmd
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec(remotecmd)
+ >>> ch.send('simple(10)') # execute func-call remotely
+ >>> ch.receive()
+ 11
+
+Our remotecmd module starts up remote serving
+through the ``for item in channel`` loop which
+will terminate when the channel closes. It evaluates
+all incoming requests in the global name space and
+sends back the results.
--- a/doc/example/remotecmd.py
+++ b/doc/example/remotecmd.py
@@ -1,4 +1,3 @@
-
# contents of: remotecmd.py
def simple(arg):
@@ -6,9 +5,4 @@ def simple(arg):
if __name__ == '__channelexec__':
for item in channel:
- funcname = item[0]
- func = globals()[funcname]
- args = item[1:]
- result = func(*args)
- channel.send(result)
-
+ channel.send(eval(item))
--- /dev/null
+++ b/doc/example/test_asyncreceive2.txt
@@ -0,0 +1,21 @@
+
+Asynchronously receive results from two sub processes
+-----------------------------------------------------
+
+Use ``MultiChannel.make_receive_queue()`` for asynchronously receiving
+multiple results from remote code. This standard Queue provides
+``(channel, result)`` tuples which allows to determine where
+a result comes from::
+
+ >>> import execnet
+ >>> ch1 = execnet.PopenGateway().remote_exec("channel.send(1)")
+ >>> ch2 = execnet.PopenGateway().remote_exec("channel.send(2)")
+ >>> mch = execnet.MultiChannel([ch1, ch2])
+ >>> queue = mch.make_receive_queue()
+ >>> chan1, res1 = queue.get() # you may also specify a timeout
+ >>> chan2, res2 = queue.get()
+ >>> res1 + res2
+ 3
+ >>> assert chan1 in (ch1, ch2)
+ >>> assert chan2 in (ch1, ch2)
+ >>> assert chan1 != chan2
--- a/.hgignore
+++ b/.hgignore
@@ -6,3 +6,4 @@ dist/
syntax:glob
*.pyc
*$py.class
+*.orig
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -99,111 +99,21 @@ using Mono 2.0 and IronPython-1.1 this w
.. _IronPython: http://www.IronPython.org
-Compare cwd() of Popen Gateways
-----------------------------------------
-A PopenGateway has the same working directory as the instantiatior::
-
- >>> import execnet, os
- >>> gw = execnet.PopenGateway()
- >>> ch = gw.remote_exec("import os; channel.send(os.getcwd())")
- >>> res = ch.receive()
- >>> assert res == os.getcwd()
+.. include example/test_cwd.txt
.. _channelexec:
-Sending modules with remote_exec
---------------------------------------------------------------
-
-You can pass a module object to ``remote_exec`` in which case
-its source code will be sent. No dependencies will be transferred
-so the module must be self-contained or only use modules that are
-installed on the "other" side. Module code can detect if it is
-running in a remote_exec situation by checking for the special
-``__name__`` attribute::
-
- # content of a module remote1.py
-
- if __name__ == '__channelexec__':
- channel.send('initialization complete')
-
-You can now send the module like this::
-
- >>> import execnet, remote1
- >>> gw = execnet.PopenGateway()
- >>> ch = gw.remote_exec(remote1)
- >>> print (ch.receive())
-
-which will print the 'initialization complete' string.
+.. include:: example/test_channelexec.txt
.. _command:
-A simple command pattern
---------------------------------------------------------------
+.. include:: example/test_remotecmd.txt
-If you want the remote side to serve a number
-of synchronous function calls, here is a base
-implementation::
+.. include:: example/test_syncreceive2.txt
- # contents of: remotecmd.py
- def simple(arg):
- return arg + 1
-
- if __name__ == '__channelexec__':
- for item in channel:
- funcname = item[0]
- func = globals()[funcname]
- args = item[1:]
- result = func(*args)
- channel.send(result)
-
-Then on the local side you can do::
-
- >>> import execnet, remotecmd
- >>> gw = execnet.PopenGateway()
- >>> ch = gw.remote_exec(remotecmd)
- >>> ch.send(('simple', 10)) # execute func-call remotely
- >>> ch.receive()
- 11
-
-It's straight forward to build a proxy-object
-that would hide the details and perform remote
-function calls with basic input and output values.
-
-Synchronously receive results from two sub processes
------------------------------------------------------
-
-Use MultiChannels for receiving multiple results from remote code::
-
- >>> import execnet
- >>> ch1 = execnet.PopenGateway().remote_exec("channel.send(1)")
- >>> ch2 = execnet.PopenGateway().remote_exec("channel.send(2)")
- >>> mch = execnet.MultiChannel([ch1, ch2])
- >>> l = mch.receive_each()
- >>> assert len(l) == 2
- >>> assert 1 in l
- >>> assert 2 in l
+.. include:: example/test_asyncreceive2.txt
-Asynchronously receive results from two sub processes
------------------------------------------------------
-
-Use ``MultiChannel.make_receive_queue()`` for asynchronously receiving
-multiple results from remote code. This standard Queue provides
-``(channel, result)`` tuples which allows to determine where
-a result comes from::
-
- >>> import execnet
- >>> ch1 = execnet.PopenGateway().remote_exec("channel.send(1)")
- >>> ch2 = execnet.PopenGateway().remote_exec("channel.send(2)")
- >>> mch = execnet.MultiChannel([ch1, ch2])
- >>> queue = mch.make_receive_queue()
- >>> chan1, res1 = queue.get() # you may also specify a timeout
- >>> chan2, res2 = queue.get()
- >>> res1 + res2
- 3
- >>> assert chan1 in (ch1, ch2)
- >>> assert chan2 in (ch1, ch2)
- >>> assert chan1 != chan2
Receive file contents from remote SSH account
-----------------------------------------------------
--- /dev/null
+++ b/doc/example/test_cwd.txt
@@ -0,0 +1,10 @@
+Compare cwd() of Popen Gateways
+----------------------------------------
+
+A PopenGateway has the same working directory as the instantiatior::
+
+ >>> import execnet, os
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec("import os; channel.send(os.getcwd())")
+ >>> res = ch.receive()
+ >>> assert res == os.getcwd()
From commits-noreply at bitbucket.org Thu Nov 19 21:55:33 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 20:55:33 +0000 (UTC)
Subject: [execnet-commit] execnet commit a5abe9344482: introduce
execnet.Group to manage multiple gateways including
Message-ID: <20091119205533.D7E617EEF5@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258650088 -3600
# Node ID a5abe93444823019796d6e3f60b5c25a438f1eb5
# Parent 44271e00ae788aebea2c2fb65b3c44ed3fc3a2c7
introduce execnet.Group to manage multiple gateways including
their automatic termination. Route all global gateway
creation through a default Group instance.
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -16,7 +16,7 @@ that has numpy installed. We send items
receive back the remote "repr" of the array::
import execnet
- gw = execnet.PopenGateway("python2.6")
+ gw = execnet.makegateway("popen//python=python2.6")
channel = gw.remote_exec("""
import numpy
array = numpy.array([1,2,3])
@@ -50,7 +50,7 @@ Use your CPython interpreter to connect
and work with Java types::
import execnet
- gw = execnet.PopenGateway("jython")
+ gw = execnet.makegateway("popen//python=jython")
channel = gw.remote_exec("""
from java.util import Vector
v = Vector()
@@ -78,7 +78,7 @@ which can work with C# classes. Here is
a CLR Array instance and sending back its representation::
import execnet
- gw = execnet.PopenGateway("ipy")
+ gw = execnet.makegateway("popen//python=ipy")
channel = gw.remote_exec("""
import clr
@@ -110,6 +110,10 @@ using Mono 2.0 and IronPython-1.1 this w
.. include:: example/test_remotecmd.txt
+.. _group:
+
+.. include:: example/test_group.txt
+
.. include:: example/test_syncreceive2.txt
.. include:: example/test_asyncreceive2.txt
@@ -123,7 +127,7 @@ contents of remote files::
import execnet
# open a gateway to a fresh child process
- gw = execnet.SshGateway('codespeak.net')
+ gw = execnet.makegateway("ssh=codespeak.net")
channel = gw.remote_exec("""
for fn in channel:
f = open(fn, 'rb')
@@ -153,5 +157,5 @@ socketserver::
popengw = execnet.PopenGateway()
socketgw = execnet.SocketGateway.new_remote(popengw, ("127.0.0.1", 0))
- print socketgw._rinfo() # print some info about the remote environment
+ print socketgw.remote_status() # print some info about the remote environment
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,13 @@
+1.0.0
+--------------------------------
+
+* introduce execnet.Group for grouping gateways, scoping termination.
+ introduce execnet.default_group through which all "global" calls
+ are routed. Use makegateway() intead of the Popen/Socket/Ssh classes
+ in examples.
+
+* refine and automatically test some documentation examples
+
1.0.0b3
--------------------------------
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -3,46 +3,19 @@ gateway code for initiating popen, socke
(c) 2004-2009, Holger Krekel and others
"""
-import sys, os, inspect, socket, atexit, weakref
+import sys, os, inspect, socket, types
import textwrap
import execnet
from execnet.gateway_base import Message, Popen2IO, SocketIO
from execnet import gateway_base
-ModuleType = type(os)
-
-debug = False
-
-class GatewayCleanup:
- def __init__(self):
- self._activegateways = weakref.WeakKeyDictionary()
- atexit.register(self.cleanup_atexit)
-
- def register(self, gateway):
- assert gateway not in self._activegateways
- self._activegateways[gateway] = True
-
- def unregister(self, gateway):
- del self._activegateways[gateway]
-
- def cleanup_atexit(self):
- if debug:
- debug.writeslines(["="*20, "cleaning up", "=" * 20])
- debug.flush()
- for gw in list(self._activegateways):
- gw.exit()
- #gw.join() # should work as well
class Gateway(gateway_base.BaseGateway):
""" Gateway to a local or remote Python Intepreter. """
- # XXX put the next two global variables into an Execnet object
- # which intiaties gateways and passes in appropriate values.
- _cleanup = GatewayCleanup()
def __init__(self, io):
super(Gateway, self).__init__(io=io, _startcount=1)
self._remote_bootstrap_gateway(io)
self._initreceive()
- self._cleanup.register(self)
def __repr__(self):
""" return string representing gateway type and status. """
@@ -61,12 +34,13 @@ class Gateway(gateway_base.BaseGateway):
self.__class__.__name__, addr, r, i)
def exit(self):
- """ exit gateway (stop local execution, stop sending)"""
- self._trace("exiting gateway")
+ """ trigger gateway exit. """
+ self._trace("trigger gateway exit")
try:
- self._cleanup.unregister(self)
+ self._group._unregister(self)
except KeyError:
return # we assume it's already happened
+ self._trace("stopping exec and closing write connection")
self._stopexec()
self._stopsend()
#self.join(timeout=timeout) # receiverthread receive close() messages
@@ -116,7 +90,7 @@ class Gateway(gateway_base.BaseGateway):
and has the sister 'channel' object in its global
namespace.
"""
- if isinstance(source, ModuleType):
+ if isinstance(source, types.ModuleType):
source = inspect.getsource(source)
else:
source = textwrap.dedent(str(source))
@@ -276,7 +250,7 @@ class SocketGateway(Gateway):
(realhost, realport) = channel.receive()
#self._trace("new_remote received"
# "port=%r, hostname = %r" %(realport, hostname))
- return SocketGateway(host, realport)
+ return gateway._group.makegateway("socket=%s:%s" %(host, realport))
new_remote = classmethod(new_remote)
class HostNotFound(Exception):
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -8,21 +8,57 @@ code** and exchange basic python objects
.. image:: _static/basic1.png
+
Gateways: connecting to another Python Interpreter
===================================================
.. currentmodule:: execnet
-Gateway classes allow to ad-hoc instantiate local or
-remote Python Interpreters and deploy code to them.
+All Gateways are instantiated via a call to ``makegateway()``
+passing it a gateway specification or URL.
-.. autoclass:: PopenGateway
+.. autofunction:: execnet.makegateway(xspec)
-.. autoclass:: SshGateway
+Here is an example to instantiate a simple Python subprocess::
-.. autoclass:: SocketGateway
+ >>> gateway = execnet.makegateway("popen")
+You can then use this gateway object to `remote execute code`_ and
+`exchange data`_ bidirectionally.
+examples for valid gateway specifications
+-------------------------------------------
+
+* ``ssh=wyvern//python=python2.4//chdir=mycache`` specifies a Python2.4
+ interpreter on the host ``wyvern``. The remote process will have
+ ``mycache`` as its current working directory.
+
+* ``popen//python=2.5//nice=20`` specification of a python2.5
+ subprocess; running with the lowest CPU priority ("nice" level).
+ By default current dir will be the current dir of the instantiator.
+
+* ``socket=192.168.1.4:8888`` specifies of a Python Socket server
+ process that listens on 192.168.1.4:8888; current dir will be the
+ 'pyexecnet-cache' sub directory which is used a default for all remote
+ processes.
+
+.. _xspec:
+
+recognized Gateway URL parts
+---------------------------------------
+
+The following parameters are recognized as part of a gateway specification::
+
+* ``popen`` for a PopenGateway.
+* ``ssh=host`` for a SshGateway to the given host.
+* ``socket=address:port`` for a SocketGateway at the given address.
+* ``id=NAME`` set the ``id`` of the gateway, must be unique within Group_
+* ``python=executable`` for specifying Python Interpreter executables
+* ``chdir=path`` change remote working dir to given relative or absolute path
+* ``nice=value`` decrease remote nice level if platforms supports it
+
+
+.. _`remote execute code`:
remote_exec: execute source code remotely
===================================================
@@ -34,29 +70,14 @@ in the connected interpreter:
.. automethod:: Gateway.remote_exec(source)
-Here is an self-contained example for reading the process identifier::
-
- >>> import execnet, os
- >>> gw = execnet.PopenGateway()
- >>> channel = gw.remote_exec("""
- ... import os
- ... channel.send(os.getpid())
- ... """)
- >>> remote_pid = channel.receive()
- >>> remote_pid != os.getpid()
- True
-
-If you'd like to avoid inlining source strings take a look
-at the channelexec_ and command_ example.
-
-
-.. _channelexec: examples.html#channelexec
-.. _command: examples.html#command
+.. include:: example/test_pid.txt
.. _`Channel`:
.. _`channel-api`:
.. _`exchange data`:
+.. _`exchange data`:
+
Channels: exchanging data with remote code
=======================================================
@@ -90,44 +111,21 @@ Calling this method tells you e.g. how m
tasks are queued, how many are executing and how many
channels are active.
-.. _xspec:
-creating gateways from a generic string format
-===============================================================
+.. _Group:
-``execnet`` supports a simple extensible format for
-specifying and configuring Gateways for remote execution.
+Grouping Gateways
+============================
-.. autofunction:: execnet.makegateway(xspec)
+All created gateway instances are part of a group. Gateways made
+through the global ``execnet.makegateway()`` will become a
+member of the ``default_group``::
-The following paramters are recognized by default:
+A Group instance can terminate its gateways explicitely
+or at process termination (each group registers with Python's
+``atexit`` mechanism). See the `group examples`_ for details.
-* ``popen`` for a PopenGateway.
-* ``ssh=host`` for a SshGateway to the given host.
-* ``socket=address:port`` for a SocketGateway at the given address.
-* ``python=executable`` for specifying Python Interpreter executables
-* ``chdir=path`` change remote working dir to given relative or absolute path
-* ``nice=value`` decrease remote nice level if platforms supports it
-
-You can use a string specification to, for example,
-instantiate a simple new SshGateway::
-
- gateway = execnet.makegateway("ssh=myhost")
-
-Here are some examples for valid specifications:
-
-* ``ssh=wyvern//python=python2.4//chdir=mycache`` specifies a Python2.4
- interpreter on the host wyvern. The remote process will have
- ``mycache`` as its current working directory.
-
-* ``popen//python=2.5//nice=20`` specification of a python2.5
- subprocess; running with the lowest CPU priority ("nice" level).
- By default current dir will be the current dir of the instantiator.
-
-* ``socket=192.168.1.4:8888`` specifies of a Python Socket server
- process that listens on 192.168.1.4:8888; current dir will be the
- 'pyexecnet-cache' sub directory which is used a default for all remote
- processes.
+.. _`group examples`: examples#group
rsync: synchronise filesystem with remote
===============================================================
@@ -136,7 +134,7 @@ rsync: synchronise filesystem with remot
Here is a basic example::
rsync = execnet.RSync('/tmp/source')
- gw = execnet.PopenGateway()
+ gw = execnet.makegateway("popen")
rsync.add_target(gw, '/tmp/dest')
rsync.send()
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -3,7 +3,7 @@ import py
rsyncdirs = ['../execnet', '.']
-pytest_plugins = ['pytester']
+pytest_plugins = ['pytester', 'doctest']
# configuration information for tests
def pytest_addoption(parser):
group = parser.getgroup("pylib", "py lib testing options")
@@ -69,7 +69,8 @@ def pytest_funcarg__gw(request):
def setup_socket_gateway():
proxygw = execnet.PopenGateway()
- gw = execnet.SocketGateway.new_remote(proxygw, ("127.0.0.1", 0))
+ from execnet.gateway import SocketGateway
+ gw = SocketGateway.new_remote(proxygw, ("127.0.0.1", 0))
gw.proxygw = proxygw
return gw
--- /dev/null
+++ b/doc/example/test_group.txt
@@ -0,0 +1,21 @@
+
+
+Creation and termination of of grouped Gateways
+------------------------------------------------------
+
+::
+
+ >>> import execnet
+ >>> group = execnet.Group()
+ >>> group.makegateway("popen")
+
+ >>> group.makegateway("popen")
+
+ >>> group
+ , ]>
+ >>> group.makegateway("popen")
+
+ >>> group
+
+ >>> group.terminate()
+
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -23,8 +23,7 @@ class TestMultiChannelAndGateway:
assert l == [12,12]
def test_multichannel_send_each(self):
- l = [execnet.PopenGateway() for x in range(2)]
- gm = execnet.MultiGateway(l)
+ gm = execnet.Group(["popen"] * 2)
mc = gm.remote_exec("""
import os
channel.send(channel.receive() + 1)
@@ -34,8 +33,7 @@ class TestMultiChannelAndGateway:
assert l == [42,42]
def test_multichannel_receive_queue_for_two_subprocesses(self):
- l = [execnet.PopenGateway() for x in range(2)]
- gm = execnet.MultiGateway(l)
+ gm = execnet.Group(["popen"] * 2)
mc = gm.remote_exec("""
import os
channel.send(os.getpid())
@@ -57,3 +55,30 @@ class TestMultiChannelAndGateway:
multichannel.waitclose()
assert len(l) == 2
+
+from execnet.multi import Group
+def test_basic_group(monkeypatch):
+ import atexit
+ atexitlist = []
+ monkeypatch.setattr(atexit, 'register', atexitlist.append)
+ group = Group()
+ assert atexitlist == [group._cleanup_atexit]
+ exitlist = []
+ class PseudoGW:
+ def exit(self):
+ exitlist.append(self)
+ gw = PseudoGW()
+ group._register(gw)
+ assert len(exitlist) == 0
+ group._cleanup_atexit()
+ assert len(exitlist) == 1
+ assert exitlist == [gw]
+ group._cleanup_atexit()
+ assert len(exitlist) == 1
+
+def test_group_PopenGateway():
+ group = Group()
+ gw = group.makegateway("popen")
+ assert list(group._activegateways) == [gw]
+ group._cleanup_atexit()
+ assert not group._activegateways
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -9,13 +9,13 @@ __version__ = "1.0.0b3"
import execnet.apipkg
execnet.apipkg.initpkg(__name__, {
- 'PopenGateway': '.gateway:PopenGateway',
- 'SocketGateway': '.gateway:SocketGateway',
- 'SshGateway': '.gateway:SshGateway',
+ 'PopenGateway': '.multi:PopenGateway',
+ 'SocketGateway': '.multi:SocketGateway',
+ 'SshGateway': '.multi:SshGateway',
+ 'makegateway': '.multi:makegateway',
'HostNotFound': '.gateway:HostNotFound',
- 'makegateway': '.xspec:makegateway',
'XSpec': '.xspec:XSpec',
- 'MultiGateway': '.multi:MultiGateway',
+ 'Group': '.multi:Group',
'MultiChannel': '.multi:MultiChannel',
'RSync': '.rsync:RSync',
})
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -1,25 +1,106 @@
"""
-Support for working with multiple channels and gateways
+Managing Gateway Groups and interactions with multiple channels.
(c) 2008-2009, Holger Krekel and others
"""
-import sys
-from execnet.gateway_base import queue, reraise
+import sys, weakref, atexit
+import execnet
+from execnet import XSpec
+from execnet import gateway
+from execnet.gateway_base import queue, reraise, trace
NO_ENDMARKER_WANTED = object()
-class MultiGateway:
- def __init__(self, gateways):
- self.gateways = gateways
+class Group:
+ """ Gateway Groups. """
+ def __init__(self, xspecs=()):
+ """ initialize group and make gateways as specified. """
+ self._activegateways = weakref.WeakKeyDictionary()
+ for xspec in xspecs:
+ self.makegateway(xspec)
+ atexit.register(self._cleanup_atexit)
+
+ def __repr__(self):
+ numgw = len(self._activegateways)
+ if numgw > 2:
+ return "" %(len(self._activegateways))
+ else:
+ gws = ", ".join([repr(x) for x in self._activegateways])
+ return "" %(gws,)
+
+ def makegateway(self, spec):
+ """ create and configure a gateway to a Python interpreter
+ specified by a 'execution specification' string.
+ The format of the string generally is::
+
+ key1=value1//key2=value2//...
+
+ If you leave out the ``=value`` part a True value is assumed.
+ """
+ if not isinstance(spec, XSpec):
+ spec = XSpec(spec)
+ if spec.popen:
+ gw = gateway.PopenGateway(python=spec.python)
+ elif spec.ssh:
+ gw = gateway.SshGateway(spec.ssh, remotepython=spec.python, ssh_config=spec.ssh_config)
+ elif spec.socket:
+ assert not spec.python, (
+ "socket: specifying python executables not supported")
+ hostport = spec.socket.split(":")
+ gw = gateway.SocketGateway(*hostport)
+ else:
+ raise ValueError("no gateway type found for %r" % (spec._spec,))
+ gw.spec = spec
+ self._register(gw)
+ if spec.chdir or spec.nice:
+ channel = gw.remote_exec("""
+ import os
+ path, nice = channel.receive()
+ if path:
+ if not os.path.exists(path):
+ os.mkdir(path)
+ os.chdir(path)
+ if nice and hasattr(os, 'nice'):
+ os.nice(nice)
+ """)
+ nice = spec.nice and int(spec.nice) or 0
+ channel.send((spec.chdir, nice))
+ channel.waitclose()
+ return gw
+
+ def _register(self, gateway):
+ assert gateway not in self._activegateways
+ assert not hasattr(gateway, '_group')
+ self._activegateways[gateway] = True
+ gateway._group = self
+
+ def _unregister(self, gateway):
+ del self._activegateways[gateway]
+
+ def _cleanup_atexit(self):
+ trace("=== atexit cleanup %r ===" %(self,))
+ self.terminate()
+
+ def terminate(self):
+ """ trigger exit of all gateways. """
+ gwlist = []
+ while 1:
+ try:
+ gw, _ = self._activegateways.popitem()
+ except KeyError:
+ break
+ else:
+ gw.exit()
+ gwlist.append(gw)
+ #for gw in gwlist:
+ # gw.join(timeout=1.0)
+
def remote_exec(self, source):
channels = []
- for gw in self.gateways:
+ for gw in list(self._activegateways):
channels.append(gw.remote_exec(source))
return MultiChannel(channels)
- def exit(self):
- for gw in self.gateways:
- gw.exit()
class MultiChannel:
def __init__(self, channels):
@@ -65,3 +146,37 @@ class MultiChannel:
first = sys.exc_info()
if first:
reraise(*first)
+
+
+default_group = Group()
+
+makegateway = default_group.makegateway
+
+def PopenGateway(python=None):
+ """ instantiate a gateway to a subprocess
+ started with the given 'python' executable.
+ """
+ spec = execnet.XSpec("popen")
+ spec.python = python
+ return default_group.makegateway(spec)
+
+def SocketGateway(host, port):
+ """ This Gateway provides interaction with a remote process
+ by connecting to a specified socket. On the remote
+ side you need to manually start a small script
+ (py/execnet/script/socketserver.py) that accepts
+ SocketGateway connections or use the experimental
+ new_remote() method on existing gateways.
+ """
+ spec = execnet.XSpec("socket=%s:%s" %(host, port))
+ return default_group.makegateway(spec)
+
+def SshGateway(sshaddress, remotepython=None, ssh_config=None):
+ """ instantiate a remote ssh process with the
+ given 'sshaddress' and remotepython version.
+ you may specify an ssh_config file.
+ """
+ spec = execnet.XSpec("ssh=%s" % sshaddress)
+ spec.python = remotepython
+ spec.ssh_config = ssh_config
+ return default_group.makegateway(spec)
--- a/execnet/xspec.py
+++ b/execnet/xspec.py
@@ -48,41 +48,3 @@ class XSpec:
def _samefilesystem(self):
return bool(self.popen and not self.chdir)
-def makegateway(spec):
- """ create and configure a gateway to a Python interpreter
- specified by a 'execution specification' string.
- The format of the string generally is::
-
- key1=value1//key2=value2//...
-
- If you leave out the ``=value`` part a True value is assumed.
- """
- if not isinstance(spec, XSpec):
- spec = XSpec(spec)
- if spec.popen:
- gw = execnet.PopenGateway(python=spec.python)
- elif spec.ssh:
- gw = execnet.SshGateway(spec.ssh, remotepython=spec.python)
- elif spec.socket:
- assert not spec.python, (
- "socket: specifying python executables not supported")
- hostport = spec.socket.split(":")
- gw = execnet.SocketGateway(*hostport)
- else:
- raise ValueError("no gateway type found for %r" % (spec._spec,))
- gw.spec = spec
- if spec.chdir or spec.nice:
- channel = gw.remote_exec("""
- import os
- path, nice = channel.receive()
- if path:
- if not os.path.exists(path):
- os.mkdir(path)
- os.chdir(path)
- if nice and hasattr(os, 'nice'):
- os.nice(nice)
- """)
- nice = spec.nice and int(spec.nice) or 0
- channel.send((spec.chdir, nice))
- channel.waitclose()
- return gw
--- /dev/null
+++ b/doc/example/test_pid.txt
@@ -0,0 +1,18 @@
+
+Here is an self-contained example for reading the process identifier.
+
+ >>> import execnet, os
+ >>> gw = execnet.makegateway("popen")
+ >>> channel = gw.remote_exec("""
+ ... import os
+ ... channel.send(os.getpid())
+ ... """)
+ >>> remote_pid = channel.receive()
+ >>> remote_pid != os.getpid()
+ True
+
+If you'd like to avoid inlining source strings take a look
+at the channelexec_ and command_ example.
+
+.. _channelexec: examples.html#channelexec
+.. _command: examples.html#command
From commits-noreply at bitbucket.org Thu Nov 19 21:55:36 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 20:55:36 +0000 (UTC)
Subject: [execnet-commit] execnet commit 1fbf1ba9e24f: some tests and
additions for id-handling with gateways and groups
Message-ID: <20091119205536.070117EEF8@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258654651 -3600
# Node ID 1fbf1ba9e24f59348c7d5ffe7866b1a83e72b865
# Parent 48d78b31ec63289d11279cf636867f6fcb73f26f
some tests and additions for id-handling with gateways and groups
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -19,6 +19,10 @@ class Gateway(gateway_base.BaseGateway):
def __repr__(self):
""" return string representing gateway type and status. """
+ if hasattr(self, 'id'):
+ id = self.id
+ else:
+ id = "???"
if hasattr(self, 'remoteaddress'):
addr = '[%s]' % (self.remoteaddress,)
else:
@@ -30,8 +34,8 @@ class Gateway(gateway_base.BaseGateway):
except AttributeError:
r = "uninitialized"
i = "no"
- return "<%s%s %s, %s active channels>" %(
- self.__class__.__name__, addr, r, i)
+ return "<%s%s id=%r %s, %s active channels>" %(
+ self.__class__.__name__, addr, id, r, i)
def exit(self):
""" trigger gateway exit. """
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -31,6 +31,12 @@ class Group:
gws = ", ".join([repr(x) for x in self._activegateways])
return "" %(gws,)
+ def __getitem__(self, key):
+ return self._id2gateway[key]
+
+ def __contains__(self, key):
+ return key in self._id2gateway
+
def makegateway(self, spec):
""" create and configure a gateway to a Python interpreter
specified by a 'execution specification' string.
@@ -59,7 +65,7 @@ class Group:
else:
raise ValueError("no gateway type found for %r" % (spec._spec,))
gw.spec = spec
- self._register(gw)
+ self._register(gw, id=spec.id)
if spec.chdir or spec.nice:
channel = gw.remote_exec("""
import os
@@ -91,6 +97,7 @@ class Group:
def _unregister(self, gateway):
del self._activegateways[gateway]
+ del self._id2gateway[gateway.id]
def _cleanup_atexit(self):
trace("=== atexit cleanup %r ===" %(self,))
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -8,14 +8,30 @@ Creation and termination of of grouped G
>>> import execnet
>>> group = execnet.Group()
>>> group.makegateway("popen")
-
+
>>> group.makegateway("popen")
-
+
>>> group
- , ]>
+ , ]>
>>> group.makegateway("popen")
-
+
>>> group
>>> group.terminate()
+Group termination will cause all member Gateways to exit.
+
+Working with Groups and Gateway IDs
+------------------------------------------------------
+
+All gateways are created as part of a group and receive
+a per-group unique ``id`` after successful initialization.
+This identification string can be set via an ``id=...`` part
+in an gateway URL as passed to ``group.makegateway``. Example::
+
+ >>> import execnet
+ >>> group = execnet.Group()
+ >>> gw = group.makegateway("popen//id=sub1")
+ >>> assert gw.id == "sub1"
+ >>> group['sub1']
+
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -1,7 +1,5 @@
"""
- tests for
- - multi channels and multi gateways
-
+ tests for multi channels and gateway Groups
"""
import execnet
@@ -57,28 +55,40 @@ class TestMultiChannelAndGateway:
from execnet.multi import Group
-def test_basic_group(monkeypatch):
- import atexit
- atexitlist = []
- monkeypatch.setattr(atexit, 'register', atexitlist.append)
- group = Group()
- assert atexitlist == [group._cleanup_atexit]
- exitlist = []
- class PseudoGW:
- def exit(self):
- exitlist.append(self)
- gw = PseudoGW()
- group._register(gw)
- assert len(exitlist) == 0
- group._cleanup_atexit()
- assert len(exitlist) == 1
- assert exitlist == [gw]
- group._cleanup_atexit()
- assert len(exitlist) == 1
+class TestGroup:
+ def test_basic_group(self, monkeypatch):
+ import atexit
+ atexitlist = []
+ monkeypatch.setattr(atexit, 'register', atexitlist.append)
+ group = Group()
+ assert atexitlist == [group._cleanup_atexit]
+ exitlist = []
+ class PseudoGW:
+ def exit(self):
+ exitlist.append(self)
+ gw = PseudoGW()
+ group._register(gw)
+ assert len(exitlist) == 0
+ group._cleanup_atexit()
+ assert len(exitlist) == 1
+ assert exitlist == [gw]
+ group._cleanup_atexit()
+ assert len(exitlist) == 1
-def test_group_PopenGateway():
- group = Group()
- gw = group.makegateway("popen")
- assert list(group._activegateways) == [gw]
- group._cleanup_atexit()
- assert not group._activegateways
+ def test_group_PopenGateway(self, ):
+ group = Group()
+ gw = group.makegateway("popen")
+ assert list(group._activegateways) == [gw]
+ group._cleanup_atexit()
+ assert not group._activegateways
+
+ def test_gateway_id(self):
+ group = Group()
+ gw = group.makegateway("popen//id=hello")
+ assert group["hello"] == gw
+ py.test.raises(AttributeError, "del group['hello']")
+ py.test.raises(AttributeError, "group['hello'] = 5")
+ assert 'hello' in group
+ gw.exit()
+ assert 'hello' not in group
+ py.test.raises(KeyError, "group['hello']")
From commits-noreply at bitbucket.org Thu Nov 19 21:55:35 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 20:55:35 +0000 (UTC)
Subject: [execnet-commit] execnet commit 48d78b31ec63: introduce
socketserver examples and gateway.id for handling "installvia" spec
Message-ID: <20091119205535.DC0D47EEF6@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258654650 -3600
# Node ID 48d78b31ec63289d11279cf636867f6fcb73f26f
# Parent a5abe93444823019796d6e3f60b5c25a438f1eb5
introduce socketserver examples and gateway.id for handling "installvia" spec
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -17,6 +17,8 @@ class Group:
def __init__(self, xspecs=()):
""" initialize group and make gateways as specified. """
self._activegateways = weakref.WeakKeyDictionary()
+ self._id2gateway = weakref.WeakValueDictionary()
+ self._autoidcounter = 1
for xspec in xspecs:
self.makegateway(xspec)
atexit.register(self._cleanup_atexit)
@@ -46,9 +48,14 @@ class Group:
gw = gateway.SshGateway(spec.ssh, remotepython=spec.python, ssh_config=spec.ssh_config)
elif spec.socket:
assert not spec.python, (
- "socket: specifying python executables not supported")
- hostport = spec.socket.split(":")
- gw = gateway.SocketGateway(*hostport)
+ "socket: specifying python executables not yet supported")
+ gateway_id = spec.installvia
+ if gateway_id:
+ viagw = self._id2gateway[gateway_id]
+ return gateway.SocketGateway.new_remote(viagw)
+ else:
+ hostport = spec.socket.split(":")
+ gw = gateway.SocketGateway(*hostport)
else:
raise ValueError("no gateway type found for %r" % (spec._spec,))
gw.spec = spec
@@ -69,11 +76,18 @@ class Group:
channel.waitclose()
return gw
- def _register(self, gateway):
+ def _register(self, gateway, id=None):
+ assert not hasattr(gateway, '_group')
+ if id is None:
+ id = self._autoidcounter
+ self._autoidcounter += 1
+ id = str(id)
+ assert id not in self._id2gateway
assert gateway not in self._activegateways
- assert not hasattr(gateway, '_group')
self._activegateways[gateway] = True
+ self._id2gateway[id] = gateway
gateway._group = self
+ gateway.id = id
def _unregister(self, gateway):
del self._activegateways[gateway]
--- /dev/null
+++ b/doc/example/test_socketnewremote.txt
@@ -0,0 +1,44 @@
+Instantiate a Socket Gateway
+-----------------------------------------------------
+
+Sometimes there is no direct access method towards a
+machine. If you have a trusted network (like a internal
+developer LAN) you can use a simplistic `socketserver script`_
+which listens on a socket ("8888" by default) and executes
+incoming strings. Download it and run it on the target
+machine like this::
+
+ python socketserver.py localhost:8888
+
+After which you can initiate a Gateway to the socket-server
+running machine like this::
+
+ >>> import execnet
+ >>> gw = execnet.makegateway("socket=localhost:8888") # doctest: +SKIP
+
+
+
+.. _`socketserver script`: http://bitbucket.org/hpk42/execnet/raw/493fc337a1f1/execnet/script/socketserver.py
+
+Instantiate a socket server through an existing Gateway
+--------------------------------------------------------
+
+.. note::
+ Experimental, please send a note if you are using
+ the following feature.
+
+The following example shows a special method to open up
+a SocketServer gateway through another existing gateway.
+
+ >>> import execnet
+ >>> group = execnet.Group()
+ >>> gw = group.makegateway("popen")
+ >>> gw.id
+ '1'
+ >>> group.makegateway("socket//installvia=1") # doctest: +SKIP
+
+
+
+This method will instantiate a Socket based Gateway
+whose remote counterpart will be run via the
+given ``installvia`` specified gateway.
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,10 @@
1.0.0
--------------------------------
+
+* refine socketserver-examples, experimentally introduce a
+ way to indirectly setup a SocketServer ("installvia")
+
* introduce execnet.Group for grouping gateways, scoping termination.
introduce execnet.default_group through which all "global" calls
are routed. Use makegateway() intead of the Popen/Socket/Ssh classes
From commits-noreply at bitbucket.org Thu Nov 19 21:55:36 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 20:55:36 +0000 (UTC)
Subject: [execnet-commit] execnet commit cb9bc8210ab5: bumping version to
the envisioned 1.0.0b4, using setuptools if available
Message-ID: <20091119205536.22FE67EEF9@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258654652 -3600
# Node ID cb9bc8210ab5f0722e58832c214323a97432a0ee
# Parent 1fbf1ba9e24f59348c7d5ffe7866b1a83e72b865
bumping version to the envisioned 1.0.0b4, using setuptools if available
--- a/setup.py
+++ b/setup.py
@@ -13,8 +13,10 @@ well on Windows, Linux and OSX systems.
execnet was written and is maintained by Holger Krekel with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
"""
-import os, sys
-from distutils.core import setup
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
from execnet import __version__
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -4,7 +4,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.0b3"
+__version__ = "1.0.0b4"
import execnet.apipkg
From commits-noreply at bitbucket.org Thu Nov 19 21:55:36 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 20:55:36 +0000 (UTC)
Subject: [execnet-commit] execnet commit 591908a9c739: make groups iterable,
expose and test execnet.default_group
Message-ID: <20091119205536.2FBA87EEFA@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258664083 -3600
# Node ID 591908a9c73927d24723cbfa7af8ad2ba07b8f2a
# Parent cb9bc8210ab5f0722e58832c214323a97432a0ee
make groups iterable, expose and test execnet.default_group
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -16,6 +16,7 @@ class Group:
""" Gateway Groups. """
def __init__(self, xspecs=()):
""" initialize group and make gateways as specified. """
+ # Gateways may evolve to become GC-collectable
self._activegateways = weakref.WeakKeyDictionary()
self._id2gateway = weakref.WeakValueDictionary()
self._autoidcounter = 1
@@ -24,12 +25,9 @@ class Group:
atexit.register(self._cleanup_atexit)
def __repr__(self):
- numgw = len(self._activegateways)
- if numgw > 2:
- return "" %(len(self._activegateways))
- else:
- gws = ", ".join([repr(x) for x in self._activegateways])
- return "" %(gws,)
+ keys = self._id2gateway.keys()
+ keys.sort()
+ return "" %(keys,)
def __getitem__(self, key):
return self._id2gateway[key]
@@ -37,6 +35,9 @@ class Group:
def __contains__(self, key):
return key in self._id2gateway
+ def __iter__(self):
+ return iter(list(self._activegateways))
+
def makegateway(self, spec):
""" create and configure a gateway to a Python interpreter
specified by a 'execution specification' string.
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -18,4 +18,5 @@ execnet.apipkg.initpkg(__name__, {
'Group': '.multi:Group',
'MultiChannel': '.multi:MultiChannel',
'RSync': '.rsync:RSync',
+ 'default_group': '.multi:default_group',
})
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -1,9 +1,8 @@
-
-
Creation and termination of of grouped Gateways
------------------------------------------------------
-::
+You can use ``execnet.Group`` to manage creation and
+termination of Gateways. Example usage::
>>> import execnet
>>> group = execnet.Group()
@@ -12,16 +11,12 @@ Creation and termination of of grouped G
>>> group.makegateway("popen")
>>> group
- , ]>
- >>> group.makegateway("popen")
-
- >>> group
-
- >>> group.terminate()
+
+ >>> list(group) # list all member gateways
+ [, ]
+ >>> group.terminate() # exit all member gateways
-Group termination will cause all member Gateways to exit.
-
-Working with Groups and Gateway IDs
+Acessing Gateways on a group by ID
------------------------------------------------------
All gateways are created as part of a group and receive
@@ -35,3 +30,7 @@ in an gateway URL as passed to ``group.m
>>> assert gw.id == "sub1"
>>> group['sub1']
+
+Using the default_group
+------------------------------------------------------
+
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -75,10 +75,10 @@ class TestGroup:
group._cleanup_atexit()
assert len(exitlist) == 1
- def test_group_PopenGateway(self, ):
+ def test_group_PopenGateway(self):
group = Group()
gw = group.makegateway("popen")
- assert list(group._activegateways) == [gw]
+ assert list(group) == [gw]
group._cleanup_atexit()
assert not group._activegateways
@@ -92,3 +92,12 @@ class TestGroup:
gw.exit()
assert 'hello' not in group
py.test.raises(KeyError, "group['hello']")
+
+ def test_default_group(self):
+ oldlist = list(execnet.default_group)
+ gw = execnet.makegateway("popen")
+ newlist = list(execnet.default_group)
+ assert len(newlist) == len(oldlist) + 1
+ assert gw in newlist
+ assert gw not in oldlist
+
From commits-noreply at bitbucket.org Thu Nov 19 23:58:23 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 22:58:23 +0000 (UTC)
Subject: [execnet-commit] execnet commit d903ea597bb3: actually deprecate
execnet.XYZGateway
Message-ID: <20091119225823.BAAE87EEEB@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258665672 -3600
# Node ID d903ea597bb3594c1ea7c5036085cd9b7c2581a4
# Parent 591908a9c73927d24723cbfa7af8ad2ba07b8f2a
actually deprecate execnet.XYZGateway
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -178,6 +178,7 @@ def PopenGateway(python=None):
""" instantiate a gateway to a subprocess
started with the given 'python' executable.
"""
+ APIWARN("1.0.0b4", "use makegateway('popen')")
spec = execnet.XSpec("popen")
spec.python = python
return default_group.makegateway(spec)
@@ -190,6 +191,7 @@ def SocketGateway(host, port):
SocketGateway connections or use the experimental
new_remote() method on existing gateways.
"""
+ APIWARN("1.0.0b4", "use makegateway('socket=host:port')")
spec = execnet.XSpec("socket=%s:%s" %(host, port))
return default_group.makegateway(spec)
@@ -198,7 +200,13 @@ def SshGateway(sshaddress, remotepython=
given 'sshaddress' and remotepython version.
you may specify an ssh_config file.
"""
+ APIWARN("1.0.0b4", "use makegateway('ssh=host')")
spec = execnet.XSpec("ssh=%s" % sshaddress)
spec.python = remotepython
spec.ssh_config = ssh_config
return default_group.makegateway(spec)
+
+def APIWARN(version, msg, stacklevel=3):
+ import warnings
+ Warn = DeprecationWarning("(since version %s) %s" %(version, msg))
+ warnings.warn(Warn, stacklevel=stacklevel)
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -16,6 +16,14 @@ def test_serialize_error(gw):
excinfo = py.test.raises(ch.RemoteError, "ch.receive()")
assert "can't serialize" in str(excinfo.value)
+def test_deprecation(recwarn):
+ execnet.PopenGateway()
+ assert recwarn.pop(DeprecationWarning)
+ py.test.raises(Exception, 'execnet.SocketGateway("localhost", 8888)')
+ assert recwarn.pop(DeprecationWarning)
+ py.test.raises(Exception, 'execnet.SshGateway("not-existing")')
+ assert recwarn.pop(DeprecationWarning)
+
class TestBasicRemoteExecution:
def test_correct_setup(self, gw):
assert gw._receiverthread.isAlive()
@@ -449,7 +457,7 @@ class TestChannelFile:
gw.remote_exec("import os ; os.chdir(%r)" % old).waitclose()
def test_join_blocked_slave_execution_gateway():
- gateway = execnet.PopenGateway()
+ gateway = execnet.makegateway('popen')
channel = gateway.remote_exec("""
import time
time.sleep(10.0)
@@ -470,7 +478,7 @@ class TestPopenGateway:
def test_chdir_separation(self, tmpdir):
old = tmpdir.chdir()
try:
- gw = execnet.PopenGateway()
+ gw = execnet.makegateway('popen')
finally:
waschangedir = old.chdir()
c = gw.remote_exec("import os ; channel.send(os.getcwd())")
@@ -481,7 +489,7 @@ class TestPopenGateway:
num = 4
l = []
for i in range(num):
- l.append(execnet.PopenGateway())
+ l.append(execnet.makegateway('popen'))
channels = []
for gw in l:
channel = gw.remote_exec("""channel.send(42)""")
@@ -510,7 +518,7 @@ class TestPopenGateway:
@py.test.mark.xfail # "fix needed: dying remote process does not cause waitclose() to fail"
def test_waitclose_on_remote_killed(self):
- gw = execnet.PopenGateway()
+ gw = execnet.makegateway('popen')
channel = gw.remote_exec("""
import os
import time
@@ -528,7 +536,7 @@ class TestPopenGateway:
def test_endmarker_delivery_on_remote_killterm():
if not hasattr(py.std.os, 'kill'):
py.test.skip("no os.kill()")
- gw = execnet.PopenGateway()
+ gw = execnet.makegateway('popen')
try:
q = queue.Queue()
channel = gw.remote_exec(source='''
@@ -547,7 +555,7 @@ def test_endmarker_delivery_on_remote_ki
def test_socket_gw_host_not_found(gw):
py.test.raises(execnet.HostNotFound,
- 'execnet.SocketGateway("qowieuqowe", 9000)'
+ 'execnet.makegateway("socket=qwepoipqwe:9000")'
)
class TestSshPopenGateway:
@@ -559,7 +567,7 @@ class TestSshPopenGateway:
monkeypatch.setattr(subprocess, 'Popen',
lambda *args, **kwargs: l.append(args[0]))
py.test.raises(AttributeError,
- """execnet.SshGateway("xyz", ssh_config='qwe')""")
+ """execnet.makegateway("ssh=xyz//ssh_config=qwe")""")
assert len(l) == 1
popen_args = l[0]
i = popen_args.index('-F')
@@ -570,11 +578,11 @@ class TestSshPopenGateway:
def test_host_not_found(self, gw):
py.test.raises(execnet.HostNotFound,
- "execnet.SshGateway('nowhere.codespeak.net')")
+ "execnet.makegateway('ssh=nowhere.codespeak.net')")
class TestThreads:
def test_threads(self):
- gw = execnet.PopenGateway()
+ gw = execnet.makegateway('popen')
gw.remote_init_threads(3)
c1 = gw.remote_exec("channel.send(channel.receive())")
c2 = gw.remote_exec("channel.send(channel.receive())")
@@ -586,7 +594,7 @@ class TestThreads:
assert res == 42
def test_status_with_threads(self):
- gw = execnet.PopenGateway()
+ gw = execnet.makegateway('popen')
gw.remote_init_threads(3)
c1 = gw.remote_exec("channel.send(1) ; channel.receive()")
c2 = gw.remote_exec("channel.send(2) ; channel.receive()")
@@ -604,7 +612,7 @@ class TestThreads:
assert rstatus.execqsize == 0
def test_threads_twice(self):
- gw = execnet.PopenGateway()
+ gw = execnet.makegateway('popen')
gw.remote_init_threads(3)
py.test.raises(IOError, gw.remote_init_threads, 3)
@@ -618,3 +626,5 @@ def test_debug(monkeypatch):
def test_nodebug():
from execnet import gateway_base
assert not hasattr(gateway_base, 'debugfile')
+
+
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
-1.0.0
+1.0.0b4
--------------------------------
+* deprecate execnet.XYZGateway in favour of makegateway()
* refine socketserver-examples, experimentally introduce a
way to indirectly setup a SocketServer ("installvia")
From commits-noreply at bitbucket.org Thu Nov 19 23:58:25 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 22:58:25 +0000 (UTC)
Subject: [execnet-commit] execnet commit 0dc5d92806f6: enhance docs,
add __len__ and __iter__ to groups
Message-ID: <20091119225825.8D6807EEF4@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258668388 -3600
# Node ID 0dc5d92806f6cc4b8ceaae24306831f3d0505e44
# Parent d903ea597bb3594c1ea7c5036085cd9b7c2581a4
enhance docs, add __len__ and __iter__ to groups
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,17 +1,17 @@
1.0.0b4
--------------------------------
-* deprecate execnet.XYZGateway in favour of makegateway()
+* deprecate execnet.XYZGateway in favour of direct makegateway()
+ Use makegateway() intead of the Popen/Socket/Ssh classes in examples.
* refine socketserver-examples, experimentally introduce a
- way to indirectly setup a SocketServer ("installvia")
+ way to indirectly setup a socket server ("installvia")
-* introduce execnet.Group for grouping gateways, scoping termination.
- introduce execnet.default_group through which all "global" calls
- are routed. Use makegateway() intead of the Popen/Socket/Ssh classes
- in examples.
+* introduce execnet.Group for managing gateway creation
+ and termination. Introduce execnet.default_group through which
+ all "global" calls are routed.
-* refine and automatically test some documentation examples
+* refine and automatically test documentation examples
1.0.0b3
--------------------------------
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -45,9 +45,10 @@ copyright = '2009, holger krekel and oth
# built documents.
#
# The short X.Y version.
-version = '1.0'
+import execnet
+version = execnet.__version__
# The full version, including alpha/beta/rc tags.
-release = '1.0.0alpha'
+release = execnet.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -94,7 +95,8 @@ pygments_style = 'sphinx'
html_theme = 'sphinxdoc'
#html_index = 'index.html'
-#html_sidebars = {'index': 'indexsidebar.html',
+html_sidebars = {'index': 'indexsidebar.html',
+}
# 'basics': 'indexsidebar.html',
#}
#html_additional_pages = {'index': 'index.html'}
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -14,6 +14,8 @@ termination of Gateways. Example usage:
>>> list(group) # list all member gateways
[, ]
+ >>> len(group)
+ 2
>>> group.terminate() # exit all member gateways
Acessing Gateways on a group by ID
@@ -31,6 +33,14 @@ in an gateway URL as passed to ``group.m
>>> group['sub1']
-Using the default_group
+
+Gateways live in a default_group
------------------------------------------------------
+Each time you create a gateway with ``execnet.makegateway()``
+you actually use the ``execnet.default_group``::
+
+ >>> import execnet
+ >>> gw = execnet.makegateway("popen")
+ >>> gw in execnet.default_group
+ True
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -82,13 +82,15 @@ class TestGroup:
group._cleanup_atexit()
assert not group._activegateways
- def test_gateway_id(self):
+ def test_gateway_and_id(self):
group = Group()
gw = group.makegateway("popen//id=hello")
assert group["hello"] == gw
py.test.raises(AttributeError, "del group['hello']")
py.test.raises(AttributeError, "group['hello'] = 5")
assert 'hello' in group
+ assert gw in group
+ assert len(group) == 1
gw.exit()
assert 'hello' not in group
py.test.raises(KeyError, "group['hello']")
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -33,7 +33,10 @@ class Group:
return self._id2gateway[key]
def __contains__(self, key):
- return key in self._id2gateway
+ return key in self._id2gateway or key in self._activegateways
+
+ def __len__(self):
+ return len(self._activegateways)
def __iter__(self):
return iter(list(self._activegateways))
--- a/doc/_templates/indexsidebar.html
+++ b/doc/_templates/indexsidebar.html
@@ -7,7 +7,8 @@
released versions in the Python
Package Index .
{% else %}
-Current version: {{ version }}
+Current: {{ version }}
+[Changes ]
Get execnet from the Python Package
Index , or install it with:
easy_install -U execnet
@@ -17,5 +18,4 @@ Index, or install it with:Join
execnet-dev mailing list
-open an issue .
come to #pylib on FreeNode
From commits-noreply at bitbucket.org Thu Nov 19 23:58:27 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 19 Nov 2009 22:58:27 +0000 (UTC)
Subject: [execnet-commit] execnet commit e03e84d72df8: avoid closing a
channel that was remotely triggered to be closed already
Message-ID: <20091119225827.2425C7EEF5@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258671366 -3600
# Node ID e03e84d72df8541bf179217df0f44fbe06fae931
# Parent 0dc5d92806f6cc4b8ceaae24306831f3d0505e44
avoid closing a channel that was remotely triggered to be closed already
--- a/execnet/threadpool.py
+++ b/execnet/threadpool.py
@@ -230,7 +230,6 @@ if __name__ == '__channelexec__':
gw._trace("thread-dispatcher got None, exiting")
execpool.shutdown()
execpool.join()
- gw._stopsend()
raise gw._StopExecLoop
gw._trace("dispatching exec task to thread pool")
execpool.dispatch(gw.executetask, task)
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -363,17 +363,20 @@ class Channel(object):
if self._executing:
raise IOError("cannot explicitly close channel within remote_exec")
if self._closed:
- trace("%r channel already closed, close() called." % (self, ))
+ trace("%r redundant call to close(), ignoring" %(self,))
if not self._closed:
# state transition "opened/sendonly" --> "closed"
# threads warning: the channel might be closed under our feet,
# but it's never damaging to send too many CHANNEL_CLOSE messages
- put = self.gateway._send
- if error is not None:
- put(Message.CHANNEL_CLOSE_ERROR(self.id, error))
- else:
- put(Message.CHANNEL_CLOSE(self.id))
- trace("%r: sent channel close message" %(self,))
+ # however, if the other side triggered a close already, we
+ # do not send back a closed message.
+ if not self._receiveclosed.isSet():
+ put = self.gateway._send
+ if error is not None:
+ put(Message.CHANNEL_CLOSE_ERROR(self.id, error))
+ else:
+ put(Message.CHANNEL_CLOSE(self.id))
+ trace("%r: sent channel close message" %(self,))
if isinstance(error, RemoteError):
self._remoteerrors.append(error)
self._closed = True # --> "closed"
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -616,6 +616,20 @@ class TestThreads:
gw.remote_init_threads(3)
py.test.raises(IOError, gw.remote_init_threads, 3)
+def test_close_initiating_remote_no_error(testdir):
+ import subprocess
+ p = testdir.makepyfile("""
+ import execnet
+ gw = execnet.makegateway("popen")
+ gw.remote_init_threads(num=2)
+ ch = gw.remote_exec("channel.receive()")
+ ch.close()
+ """)
+ popen = subprocess.Popen([sys.executable, str(p)],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = popen.communicate()
+ assert not stderr
+
def test_debug(monkeypatch):
monkeypatch.setenv('EXECNET_DEBUG', "1")
source = py.std.inspect.getsource(gateway_base)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,8 @@
1.0.0b4
--------------------------------
+* cleanup gateway termination a bit
+
* deprecate execnet.XYZGateway in favour of direct makegateway()
Use makegateway() intead of the Popen/Socket/Ssh classes in examples.
From commits-noreply at bitbucket.org Tue Nov 24 10:39:56 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 24 Nov 2009 09:39:56 +0000 (UTC)
Subject: [execnet-commit] execnet commit a4fe6d21ca1a: preparing final
execnet-1.0.0
Message-ID: <20091124093956.9489E7EF14@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258993666 -3600
# Node ID a4fe6d21ca1ae691b2f7ff1553e49261d9c0d806
# Parent e03e84d72df8541bf179217df0f44fbe06fae931
preparing final execnet-1.0.0
--- a/setup.py
+++ b/setup.py
@@ -32,7 +32,7 @@ def main():
author='holger krekel and others',
author_email='holger at merlinux.eu',
classifiers=[
- 'Development Status :: 4 - Beta',
+ 'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Operating System :: POSIX',
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -4,7 +4,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.0b4"
+__version__ = "1.0.0"
import execnet.apipkg
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,17 +1,15 @@
-1.0.0b4
+1.0.0
--------------------------------
-* cleanup gateway termination a bit
+* introduce execnet.Group for managing gateway creation
+ and termination. Introduce execnet.default_group through which
+ all "global" calls are routed. cleanup gateway termination a bit.
-* deprecate execnet.XYZGateway in favour of direct makegateway()
- Use makegateway() intead of the Popen/Socket/Ssh classes in examples.
+* deprecate execnet.XYZGateway in favour of direct makegateway() calls.
* refine socketserver-examples, experimentally introduce a
way to indirectly setup a socket server ("installvia")
-
-* introduce execnet.Group for managing gateway creation
- and termination. Introduce execnet.default_group through which
- all "global" calls are routed.
+ through a gateway url.
* refine and automatically test documentation examples
From commits-noreply at bitbucket.org Tue Nov 24 10:39:58 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 24 Nov 2009 09:39:58 +0000 (UTC)
Subject: [execnet-commit] execnet commit 467184964d4a: fixing docs and some
tests for jython
Message-ID: <20091124093958.7C8837EF19@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259055576 -3600
# Node ID 467184964d4af2a9c724a5801bedf107d6464d4a
# Parent 9b2015e7e393d3a95dbe4a1430a1c68a66bf86a7
fixing docs and some tests for jython
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -13,7 +13,7 @@ skiponjython = py.test.mark.skipif("sys.
def test_serialize_error(gw):
ch = gw.remote_exec("channel.send(ValueError(42))")
- excinfo = py.test.raises(ch.RemoteError, "ch.receive()")
+ excinfo = py.test.raises(ch.RemoteError, ch.receive)
assert "can't serialize" in str(excinfo.value)
def test_deprecation(recwarn):
--- /dev/null
+++ b/doc/rel-1.0.0.txt
@@ -0,0 +1,19 @@
+Hi all,
+
+execnet enables zero-install ad-hoc instantiation of local or remote
+Python processes. It establishes channels for basic data communication.
+Data Serialization is independent from Pickle and is tested between
+Python2.4, 2.5, 2.6, 3.1 and Jython interpreters.
+
+execnet-1.0.0 (compared to 1.0.0b3) has bug fixes, new tested
+examples and introduces execnet.Group for managing a dynamic
+bunch of hosts. See the improved docs
+
+ http://codespeak.net/execnet/
+
+and below the changelog. I am set to improve and develop
+execnet further and thus am very interested in feedback
+and suggestions.
+
+cheers,
+holger
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -74,7 +74,6 @@ in the connected interpreter:
.. _`Channel`:
.. _`channel-api`:
-.. _`exchange data`:
.. _`exchange data`:
@@ -130,17 +129,20 @@ or at process termination (each group re
rsync: synchronise filesystem with remote
===============================================================
+.. currentmodule:: execnet
+
+
``execnet`` implements a simple efficient rsyncing protocol.
-Here is a basic example::
+Here is a basic example for using RSync::
rsync = execnet.RSync('/tmp/source')
gw = execnet.makegateway("popen")
rsync.add_target(gw, '/tmp/dest')
rsync.send()
-More info on the RSync class:
-.. currentmodule:: execnet
+And here is API info about the RSync class.
+
.. autoclass:: RSync
:members: add_target,send
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -139,10 +139,15 @@ def test_set(py2, py3):
p = dump("set((1, 2, 3))")
tp, v = py2.load(p)
assert tp == "set"
- assert v == "set([1, 2, 3])"
+ #assert v == "set([1, 2, 3])" # ordering prevents this assertion
+ assert v.startswith("set([") and v.endswith("])")
+ assert '1' in v and '2' in v and '3' in v
+
tp, v = py3.load(p)
assert tp == "set"
- assert v == "{1, 2, 3}"
+ #assert v == "{1, 2, 3}" # ordering prevents this assertion
+ assert v.startswith("{") and v.endswith("}")
+ assert '1' in v and '2' in v and '3' in v
p = dump("set()")
tp, v = py2.load(p)
assert tp == "set"
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,7 +3,9 @@ 1.0.0
* introduce execnet.Group for managing gateway creation
and termination. Introduce execnet.default_group through which
- all "global" calls are routed. cleanup gateway termination a bit.
+ all "global" calls are routed. cleanup gateway termination.
+ All Gateways get an id through which they can be
+ retrieved from a group object.
* deprecate execnet.XYZGateway in favour of direct makegateway() calls.
From commits-noreply at bitbucket.org Tue Nov 24 10:39:58 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 24 Nov 2009 09:39:58 +0000 (UTC)
Subject: [execnet-commit] execnet commit 9b2015e7e393: some python3 and
deprecation related fixes
Message-ID: <20091124093958.6CE8D7EF15@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1258995078 -3600
# Node ID 9b2015e7e393d3a95dbe4a1430a1c68a66bf86a7
# Parent a4fe6d21ca1ae691b2f7ff1553e49261d9c0d806
some python3 and deprecation related fixes
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -150,6 +150,7 @@ class Gateway(gateway_base.BaseGateway):
class RInfo:
def __init__(self, kwargs):
self.__dict__.update(kwargs)
+
def __repr__(self):
info = ", ".join(["%s=%s" % item
for item in self.__dict__.items()])
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -25,7 +25,7 @@ class Group:
atexit.register(self._cleanup_atexit)
def __repr__(self):
- keys = self._id2gateway.keys()
+ keys = list(self._id2gateway)
keys.sort()
return "" %(keys,)
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -50,7 +50,7 @@ def pytest_funcarg__gw(request):
scope = "session"
if request.param == "popen":
return request.cached_setup(
- setup=execnet.PopenGateway,
+ setup=lambda: execnet.makegateway("popen"),
teardown=lambda gw: gw.exit(),
extrakey=request.param,
scope=scope)
@@ -68,9 +68,8 @@ def pytest_funcarg__gw(request):
scope=scope)
def setup_socket_gateway():
- proxygw = execnet.PopenGateway()
- from execnet.gateway import SocketGateway
- gw = SocketGateway.new_remote(proxygw, ("127.0.0.1", 0))
+ proxygw = execnet.makegateway("popen")
+ gw = execnet.makegateway("socket//installvia=%s" % proxygw.id)
gw.proxygw = proxygw
return gw
@@ -80,5 +79,5 @@ def teardown_socket_gateway(gw):
def setup_ssh_gateway(request):
sshhost = request.getfuncargvalue('specssh').ssh
- gw = execnet.SshGateway(sshhost)
+ gw = execnet.makegateway("ssh=%s" %(sshhost,))
return gw
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -742,7 +742,7 @@ class _Stop(Exception):
class Unserializer(object):
num2func = {} # is filled after this class definition
- py2str_as_py3str = False # True
+ py2str_as_py3str = True # True
py3str_as_py2str = False # false means py2 will get unicode
def __init__(self, stream):
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -86,8 +86,8 @@ class TestGroup:
group = Group()
gw = group.makegateway("popen//id=hello")
assert group["hello"] == gw
- py.test.raises(AttributeError, "del group['hello']")
- py.test.raises(AttributeError, "group['hello'] = 5")
+ py.test.raises((TypeError, AttributeError), "del group['hello']")
+ py.test.raises((TypeError, AttributeError), "group['hello'] = 5")
assert 'hello' in group
assert gw in group
assert len(group) == 1
From commits-noreply at bitbucket.org Tue Nov 24 14:47:28 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 24 Nov 2009 13:47:28 +0000 (UTC)
Subject: [execnet-commit] execnet commit 47e73b3c07c5: "" or "0.0.0.0" do
not work on my VirtualBox/Win XP2 box
Message-ID: <20091124134728.607637EF07@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259058730 28800
# Node ID 47e73b3c07c55bcb808bec88896dc072f9f20df2
# Parent 467184964d4af2a9c724a5801bedf107d6464d4a
"" or "0.0.0.0" do not work on my VirtualBox/Win XP2 box
so fallback to localhost.
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -255,7 +255,9 @@ class SocketGateway(Gateway):
(realhost, realport) = channel.receive()
#self._trace("new_remote received"
# "port=%r, hostname = %r" %(realport, hostname))
- return gateway._group.makegateway("socket=%s:%s" %(host, realport))
+ if not realhost or realhost=="0.0.0.0":
+ realhost = "localhost"
+ return gateway._group.makegateway("socket=%s:%s" %(realhost, realport))
new_remote = classmethod(new_remote)
class HostNotFound(Exception):
From commits-noreply at bitbucket.org Tue Nov 24 17:46:24 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 24 Nov 2009 16:46:24 +0000 (UTC)
Subject: [execnet-commit] execnet commit 6cb40cacafd7: Added tag 1.0.0 for
changeset 7cf86a1811b1
Message-ID: <20091124164624.A8F9F7EF15@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259080915 -3600
# Node ID 6cb40cacafd7bdc87a57d9e82b3c1034e2da8c33
# Parent 7cf86a1811b11aa58112a255e6afba870c8d855e
Added tag 1.0.0 for changeset 7cf86a1811b1
--- a/.hgtags
+++ b/.hgtags
@@ -1,1 +1,2 @@
39e4b3a1397adad17ecfcd9e65a08521d90b2795 1.0.0b1
+7cf86a1811b11aa58112a255e6afba870c8d855e 1.0.0
From commits-noreply at bitbucket.org Tue Nov 24 17:46:26 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 24 Nov 2009 16:46:26 +0000 (UTC)
Subject: [execnet-commit] execnet commit 5d1c438e335d: merge in accidental
branch
Message-ID: <20091124164626.937427EF3C@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259081120 -3600
# Node ID 5d1c438e335dc369834207882ec5f3227d365c11
# Parent 47e73b3c07c55bcb808bec88896dc072f9f20df2
# Parent 6cb40cacafd7bdc87a57d9e82b3c1034e2da8c33
merge in accidental branch
From commits-noreply at bitbucket.org Tue Nov 24 17:46:28 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 24 Nov 2009 16:46:28 +0000 (UTC)
Subject: [execnet-commit] execnet commit c75295641d3c: Added tag 1.0.0 for
changeset 5d1c438e335d
Message-ID: <20091124164628.1BEB47EF3E@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259081137 -3600
# Node ID c75295641d3c589777ca92a70559e0e654d03d1b
# Parent 5d1c438e335dc369834207882ec5f3227d365c11
Added tag 1.0.0 for changeset 5d1c438e335d
--- a/.hgtags
+++ b/.hgtags
@@ -1,2 +1,4 @@
39e4b3a1397adad17ecfcd9e65a08521d90b2795 1.0.0b1
7cf86a1811b11aa58112a255e6afba870c8d855e 1.0.0
+7cf86a1811b11aa58112a255e6afba870c8d855e 1.0.0
+5d1c438e335dc369834207882ec5f3227d365c11 1.0.0
From commits-noreply at bitbucket.org Tue Nov 24 17:46:29 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 24 Nov 2009 16:46:29 +0000 (UTC)
Subject: [execnet-commit] execnet commit 7cf86a1811b1: include tests,
mark 'installvia' experimental
Message-ID: <20091124164629.DA9387EF3F@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259076721 -3600
# Node ID 7cf86a1811b11aa58112a255e6afba870c8d855e
# Parent 467184964d4af2a9c724a5801bedf107d6464d4a
include tests, mark 'installvia' experimental
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -147,7 +147,7 @@ contents of remote files::
Instantiate a socket server in a new subprocess
-----------------------------------------------------
-The following example opens a PopenGateway, i.e. a python
+(experimental) The following example opens a PopenGateway, i.e. a python
child process, and starts a socket server within that process
and then opens a second gateway to the freshly started
socketserver::
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,13 @@
+include CHANGELOG
+include README.txt
+include setup.py
+include LICENSE
+graft doc
+graft testing
+prune doc/_build
+exclude *.orig
+exclude *.rej
+recursive-exclude execnet *.pyc *$py.class
+recursive-exclude testing *.pyc *$py.class
+prune .svn
+prune .hg
From commits-noreply at bitbucket.org Wed Nov 25 12:03:58 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 25 Nov 2009 11:03:58 +0000 (UTC)
Subject: [execnet-commit] execnet commit cb65283a1bc3: porting some issues
from bb-py-trunk tracker
Message-ID: <20091125110358.863817EEE2@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259147024 -3600
# Node ID cb65283a1bc3f795d5eeaf08c549ecda7b71639d
# Parent c75295641d3c589777ca92a70559e0e654d03d1b
porting some issues from bb-py-trunk tracker
--- /dev/null
+++ b/ISSUES.txt
@@ -0,0 +1,35 @@
+report back errors when calling callbacks
+---------------------------------------------------
+
+tags: 1.0.1 1.1
+bb: http://bitbucket.org/hpk42/py-trunk/issue/37/
+path: execnet/gateway_base.py
+
+Callbacks are invoked in the receiver-thread
+and any exception will kill the whole receiver-thread.
+This is especially bad if it happens remotely as
+it leaves the other side inaccesssible.
+
+Control-C behaviour / best practises
+-------------------------------------------
+
+tags: 1.1
+bb: http://bitbucket.org/hpk42/py-trunk/issue/36
+path: execnet/gateway_base.py
+
+currently calling channel.receive() in the
+main thread will prevent Control-C from passing.
+Consider an internal timeout so that signals
+get a chance to propagate.
+
+
+rsync links from posix to windows
+-----------------------------------------
+
+tags: 1.1
+bb: http://bitbucket.org/hpk42/py-trunk/issue/35
+path: execnet/rsync.py
+
+currently rsyncing bails out with a strange message when trying to rsync
+links. should provide better message or even some support for doing it.
+
From commits-noreply at bitbucket.org Mon Nov 30 13:10:47 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 30 Nov 2009 12:10:47 +0000 (UTC)
Subject: [execnet-commit] execnet commit 778696674e2d: add some more
issues/thoughts
Message-ID: <20091130121047.A08597EEE7@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259582636 -3600
# Node ID 778696674e2d21287300ea9e3719aea65f82a95a
# Parent cb65283a1bc3f795d5eeaf08c549ecda7b71639d
add some more issues/thoughts
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -1,7 +1,7 @@
report back errors when calling callbacks
---------------------------------------------------
-tags: 1.0.1 1.1
+tags: 1.0
bb: http://bitbucket.org/hpk42/py-trunk/issue/37/
path: execnet/gateway_base.py
@@ -12,7 +12,6 @@ it leaves the other side inaccesssible.
Control-C behaviour / best practises
-------------------------------------------
-
tags: 1.1
bb: http://bitbucket.org/hpk42/py-trunk/issue/36
path: execnet/gateway_base.py
@@ -25,11 +24,60 @@ get a chance to propagate.
rsync links from posix to windows
-----------------------------------------
-
-tags: 1.1
+tags: 1.1 feature
bb: http://bitbucket.org/hpk42/py-trunk/issue/35
path: execnet/rsync.py
currently rsyncing bails out with a strange message when trying to rsync
links. should provide better message or even some support for doing it.
+PopenGateway imports local execnet package
+-------------------------------------------------
+tags: 1.0 feature
+
+For gateway processes instantiated on the same
+machine the local execnet package could be
+imported instead of the current serialization
+of source code. This should result in faster
+sub process creation and better traceback-printing.
+
+a process keeping state across invoactions
+--------------------------------------------------
+tags: 1.1 feature newdist
+
+A building block for keeping permanent network
+connections would be a started-on-first-access process
+that keeps state. Considered:
+
+- PopenGateway imports local execnet package
+ and avoids parse/compile code strings
+
+- a rendevouz process that starts threads for each
+ incoming connection. On each such connection
+ a SocketGateway can be instantiated but it
+ will re-use the already-imported gateway base code
+ so again avoid parse/compile on arbitrary strings.
+
+a channel-IO based gateway
+--------------------------------------------------
+tags: 1.1 feature newdist
+
+A building block for establishing a permanent hierarchy
+of processes is a Gateway that operates on a Channel object.
+This way a gateway can be instantiated through another
+intermediating gateway which bi-directionallry forwads
+Messages through a existing channel.
+
+fix group termination
+---------------------------
+tags: 1.0 bug
+
+This session does not close down gateways like expected:
+
+>>> execnet.makegateway("popen")
+>>> execnet.makegateway("popen")
+>>> execnet.default_group.terminate()
+>>> execnet.default_group
+
+>>> execnet.default_group['1'].remote_exec("channel.send(1)").receive()
+1
From commits-noreply at bitbucket.org Tue Dec 1 16:02:09 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 1 Dec 2009 15:02:09 +0000 (UTC)
Subject: [execnet-commit] execnet commit 4b3e70882e97: make iteration
ordered by gateway id
Message-ID: <20091201150209.9C4CF7EF1C@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259585385 -3600
# Node ID 4b3e70882e978e8a59a16bfa203a84a43edf1a8a
# Parent 778696674e2d21287300ea9e3719aea65f82a95a
make iteration ordered by gateway id
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -39,7 +39,10 @@ class Group:
return len(self._activegateways)
def __iter__(self):
- return iter(list(self._activegateways))
+ l = list(self._id2gateway.items())
+ l.sort()
+ for id, gw in l:
+ yield gw
def makegateway(self, spec):
""" create and configure a gateway to a Python interpreter
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -82,6 +82,17 @@ class TestGroup:
group._cleanup_atexit()
assert not group._activegateways
+ def test_group_ordering(self):
+ group = Group()
+ gw = group.makegateway("popen//id=3")
+ gw = group.makegateway("popen//id=2")
+ gw = group.makegateway("popen//id=5")
+ gwlist = list(group)
+ assert len(gwlist) == 3
+ idlist = [x.id for x in gwlist]
+ assert idlist == list('235')
+ group.terminate()
+
def test_gateway_and_id(self):
group = Group()
gw = group.makegateway("popen//id=hello")
From commits-noreply at bitbucket.org Tue Dec 1 16:02:11 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 1 Dec 2009 15:02:11 +0000 (UTC)
Subject: [execnet-commit] execnet commit 398fae98d769: have popen-gateways
use imports instead of source-strings,
Message-ID: <20091201150211.6C4B67EF1D@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259678705 -3600
# Node ID 398fae98d7699421e05369463dddd8fd13cb02c9
# Parent 4b3e70882e978e8a59a16bfa203a84a43edf1a8a
have popen-gateways use imports instead of source-strings,
also improves debugging/tracebacks, as a side effect
popen-gateway startup can be substantially faster (>30%)
this commit also contains some related bootstrapping cleanups.
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -31,16 +31,6 @@ path: execnet/rsync.py
currently rsyncing bails out with a strange message when trying to rsync
links. should provide better message or even some support for doing it.
-PopenGateway imports local execnet package
--------------------------------------------------
-tags: 1.0 feature
-
-For gateway processes instantiated on the same
-machine the local execnet package could be
-imported instead of the current serialization
-of source code. This should result in faster
-sub process creation and better traceback-printing.
-
a process keeping state across invoactions
--------------------------------------------------
tags: 1.1 feature newdist
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+1.0.1 (pending)
+--------------------------------
+
+- have popen-gateways use imports instead of source-strings,
+ also improves debugging/tracebacks, as a side effect
+ popen-gateway startup can be substantially faster (>30%)
+
1.0.0
--------------------------------
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -8,6 +8,7 @@ import textwrap
import execnet
from execnet.gateway_base import Message, Popen2IO, SocketIO
from execnet import gateway_base
+importdir = os.path.dirname(os.path.dirname(execnet.__file__))
class Gateway(gateway_base.BaseGateway):
""" Gateway to a local or remote Python Intepreter. """
@@ -49,25 +50,16 @@ class Gateway(gateway_base.BaseGateway):
self._stopsend()
#self.join(timeout=timeout) # receiverthread receive close() messages
- def _remote_bootstrap_gateway(self, io, extra=''):
- """ return Gateway with a asynchronously remotely
- initialized counterpart Gateway (which may or may not succeed).
- Note that the other sides gateways starts enumerating
- its channels with even numbers while the sender
- gateway starts with odd numbers. This allows to
- uniquely identify channels across both sides.
+ def _remote_bootstrap_gateway(self, io):
+ """ send gateway bootstrap code to a remote Python interpreter
+ endpoint, which reads from io for a string to execute.
"""
- bootstrap = [extra]
- bootstrap += [inspect.getsource(gateway_base)]
- bootstrap += [io.server_stmt,
- "io.write('1'.encode('ascii'))",
- "SlaveGateway(io=io, _startcount=2).serve()",
- ]
- source = "\n".join(bootstrap)
- self._trace("sending gateway bootstrap code")
- #open("/tmp/bootstrap.py", 'w').write(source)
- repr_source = repr(source) + "\n"
- io.write(repr_source.encode('ascii'))
+ sendexec(io,
+ inspect.getsource(gateway_base),
+ self._remotesetup,
+ "io.write('1'.encode('ascii'))",
+ "serve(io)"
+ )
s = io.read(1)
assert s == "1".encode('ascii')
@@ -170,6 +162,7 @@ channel.send(dict(
"""
class PopenCmdGateway(Gateway):
+ _remotesetup = "io = init_popen_io()"
def __init__(self, args):
from subprocess import Popen, PIPE
self._popen = p = Popen(args, stdin=PIPE, stdout=PIPE)
@@ -195,19 +188,23 @@ class PopenGateway(PopenCmdGateway):
args = [str(python), '-c', popen_bootstrapline]
super(PopenGateway, self).__init__(args)
- def _remote_bootstrap_gateway(self, io, extra=''):
- # have the subprocess use the same PYTHONPATH and py lib
- x = os.path.dirname(os.path.dirname(execnet.__file__))
- ppath = os.environ.get('PYTHONPATH', '')
- plist = [str(x)] + ppath.split(':')
- s = "\n".join([extra,
- "import sys ; sys.path[:0] = %r" % (plist,),
- "import os ; os.environ['PYTHONPATH'] = %r" % ppath,
- inspect.getsource(stdouterrin_setnull),
- "stdouterrin_setnull()",
- ""
- ])
- super(PopenGateway, self)._remote_bootstrap_gateway(io, s)
+ def _remote_bootstrap_gateway(self, io):
+ sendexec(io,
+ "import sys",
+ "sys.stdout.write('1'.encode('ascii'))",
+ "sys.stdout.flush()",
+ popen_bootstrapline)
+ sendexec(io,
+ "import sys ; sys.path.insert(0, %r)" % importdir,
+ "from execnet.gateway_base import serve, init_popen_io",
+ "serve(init_popen_io())",
+ )
+ s = io.read(1)
+ assert s == "1".encode('ascii')
+
+def sendexec(io, *sources):
+ source = "\n".join(sources)
+ io.write((repr(source)+ "\n").encode('ascii'))
class SocketGateway(Gateway):
""" This Gateway provides interaction with a remote process
@@ -216,6 +213,8 @@ class SocketGateway(Gateway):
(py/execnet/script/socketserver.py) that accepts
SocketGateway connections.
"""
+ _remotesetup = "io = SocketIO(clientsock)"
+
def __init__(self, host, port):
""" instantiate a gateway to a process accessed
via a host/port specified socket.
@@ -236,7 +235,7 @@ class SocketGateway(Gateway):
instantiated through the given 'gateway'.
"""
if hostport is None:
- host, port = ('', 0) # XXX works on all platforms?
+ host, port = ('localhost', 0)
else:
host, port = hostport
@@ -268,7 +267,6 @@ class SshGateway(PopenCmdGateway):
established via the 'ssh' command line binary.
The remote side needs to have a Python interpreter executable.
"""
-
def __init__(self, sshaddress, remotepython=None, ssh_config=None):
""" instantiate a remote ssh process with the
given 'sshaddress' and remotepython version.
@@ -284,49 +282,10 @@ class SshGateway(PopenCmdGateway):
args.extend([sshaddress, remotecmd])
super(SshGateway, self).__init__(args)
- def _remote_bootstrap_gateway(self, io, s=""):
- extra = "\n".join([
- inspect.getsource(stdouterrin_setnull),
- "stdouterrin_setnull()"])
+ def _remote_bootstrap_gateway(self, io):
try:
- super(SshGateway, self)._remote_bootstrap_gateway(io, extra)
+ super(SshGateway, self)._remote_bootstrap_gateway(io)
except EOFError:
ret = self._popen.wait()
if ret == 255:
raise HostNotFound(self.remoteaddress)
-
-def stdouterrin_setnull():
- """ redirect file descriptors 0 and 1 (and possibly 2) to /dev/null.
- note that this function may run remotely without py lib support.
- """
- # complete confusion (this is independent from the sys.stdout
- # and sys.stderr redirection that gateway.remote_exec() can do)
- # note that we redirect fd 2 on win too, since for some reason that
- # blocks there, while it works (sending to stderr if possible else
- # ignoring) on *nix
- import sys, os
- if not hasattr(os, 'dup'): # jython
- return
- try:
- devnull = os.devnull
- except AttributeError:
- if os.name == 'nt':
- devnull = 'NUL'
- else:
- devnull = '/dev/null'
- # stdin
- sys.stdin = os.fdopen(os.dup(0), 'r', 1)
- fd = os.open(devnull, os.O_RDONLY)
- os.dup2(fd, 0)
- os.close(fd)
-
- # stdout
- sys.stdout = os.fdopen(os.dup(1), 'w', 1)
- fd = os.open(devnull, os.O_WRONLY)
- os.dup2(fd, 1)
-
- # stderr for win32
- if os.name == 'nt':
- sys.stderr = os.fdopen(os.dup(2), 'w', 1)
- os.dup2(fd, 2)
- os.close(fd)
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -49,8 +49,6 @@ else:
# ___________________________________________________________________________
class SocketIO:
- server_stmt = "io = SocketIO(clientsock)"
-
error = (socket.error, EOFError)
def __init__(self, sock):
self.sock = sock
@@ -92,12 +90,6 @@ class SocketIO:
self.writeable = None
class Popen2IO:
- server_stmt = """
-import os, sys, tempfile
-io = Popen2IO(sys.stdout, sys.stdin)
-sys.stdout = tempfile.TemporaryFile('w')
-sys.stdin = tempfile.TemporaryFile('r')
-"""
error = (IOError, OSError, EOFError)
def __init__(self, outfile, infile):
@@ -326,7 +318,7 @@ class Channel(object):
Msg = Message.CHANNEL_CLOSE
try:
self.gateway._send(Msg(self.id))
- except ValueError: # XXX IO operation on closed file, why?
+ except (IOError, ValueError): # ignore problems with sending
pass
def _getremoteerror(self):
@@ -1013,3 +1005,42 @@ class Serializer(object):
def save_frozenset(self, s):
self._write_set(s, opcode.FROZENSET)
+
+def init_popen_io():
+ if not hasattr(os, 'dup'): # jython
+ io = Popen2IO(sys.stdout, sys.stdin)
+ import tempfile
+ sys.stdin = tempfile.TemporaryFile('r')
+ sys.stdout = tempfile.TemporaryFile('w')
+ else:
+ try:
+ devnull = os.devnull
+ except AttributeError:
+ if os.name == 'nt':
+ devnull = 'NUL'
+ else:
+ devnull = '/dev/null'
+ # stdin
+ stdin = os.fdopen(os.dup(0), 'r', 1)
+ fd = os.open(devnull, os.O_RDONLY)
+ os.dup2(fd, 0)
+ os.close(fd)
+
+ # stdout
+ stdout = os.fdopen(os.dup(1), 'w', 1)
+ fd = os.open(devnull, os.O_WRONLY)
+ os.dup2(fd, 1)
+
+ # stderr for win32
+ if os.name == 'nt':
+ sys.stderr = os.fdopen(os.dup(2), 'w', 1)
+ os.dup2(fd, 2)
+ os.close(fd)
+ io = Popen2IO(stdout, stdin)
+ sys.stdin = os.fdopen(0, 'r', 1)
+ sys.stdout = os.fdopen(1, 'w', 1)
+ return io
+
+def serve(io):
+ trace("creating slavegateway on %r" %(io,))
+ SlaveGateway(io=io, _startcount=2).serve()
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -485,6 +485,11 @@ class TestPopenGateway:
x = c.receive()
assert x == str(waschangedir)
+ def test_remoteerror_readable_traceback(self, gw):
+ e = py.test.raises(gateway_base.RemoteError,
+ 'gw.remote_exec("x y").waitclose()')
+ assert "gateway_base" in e.value.formatted
+
def test_many_popen(self):
num = 4
l = []
@@ -618,15 +623,18 @@ class TestThreads:
def test_close_initiating_remote_no_error(testdir):
import subprocess
+ dir = py.path.local(execnet.__file__).dirpath().dirpath()
p = testdir.makepyfile("""
+ import sys
+ sys.path.insert(0, %r)
import execnet
gw = execnet.makegateway("popen")
gw.remote_init_threads(num=2)
ch = gw.remote_exec("channel.receive()")
ch.close()
- """)
+ """ % str(dir))
popen = subprocess.Popen([sys.executable, str(p)],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
stdout, stderr = popen.communicate()
assert not stderr
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -4,7 +4,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.0"
+__version__ = "1.0.1a"
import execnet.apipkg
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -12,7 +12,7 @@ def test_subprocess_interaction(anypytho
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def send(line):
popen.stdin.write(line.encode('ascii'))
- if sys.version_info > (3,0): # 3k still buffers
+ if sys.version_info > (3,0) or sys.platform.startswith("java"):
popen.stdin.flush()
def receive():
return popen.stdout.readline().decode('ascii')
@@ -96,7 +96,7 @@ def test_io_message(anypython, tmpdir):
def test_popen_io(anypython, tmpdir):
check = tmpdir.join("check.py")
check.write(py.code.Source(gateway_base, """
- do_exec(Popen2IO.server_stmt, globals())
+ do_exec("io = init_popen_io()", globals())
io.write("hello".encode('ascii'))
s = io.read(1)
assert s == "x".encode('ascii')
@@ -145,9 +145,10 @@ def test_geterrortext(anypython, tmpdir)
print (out)
assert "all passed" in out
+ at py.test.mark.skipif("not hasattr(os, 'dup')")
def test_stdouterrin_setnull():
cap = py.io.StdCaptureFD()
- gateway.stdouterrin_setnull()
+ io = gateway_base.init_popen_io()
import os
os.write(1, "hello".encode('ascii'))
if os.name == "nt":
From commits-noreply at bitbucket.org Tue Dec 1 16:13:47 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 1 Dec 2009 15:13:47 +0000 (UTC)
Subject: [execnet-commit] execnet commit 92de97216048: fix group termination
to work more reliably
Message-ID: <20091201151347.645857EE7F@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259680403 -3600
# Node ID 92de97216048d44408050c688f65cd1ba18888e8
# Parent 398fae98d7699421e05369463dddd8fd13cb02c9
fix group termination to work more reliably
(and also fix little python3 issue)
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -58,16 +58,3 @@ This way a gateway can be instantiated t
intermediating gateway which bi-directionallry forwads
Messages through a existing channel.
-fix group termination
----------------------------
-tags: 1.0 bug
-
-This session does not close down gateways like expected:
-
->>> execnet.makegateway("popen")
->>> execnet.makegateway("popen")
->>> execnet.default_group.terminate()
->>> execnet.default_group
-
->>> execnet.default_group['1'].remote_exec("channel.send(1)").receive()
-1
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,8 @@ 1.0.1 (pending)
also improves debugging/tracebacks, as a side effect
popen-gateway startup can be substantially faster (>30%)
+- fix group.terminate() termination to work (more reliably)
+
1.0.0
--------------------------------
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -191,7 +191,7 @@ class PopenGateway(PopenCmdGateway):
def _remote_bootstrap_gateway(self, io):
sendexec(io,
"import sys",
- "sys.stdout.write('1'.encode('ascii'))",
+ "sys.stdout.write('1')",
"sys.stdout.flush()",
popen_bootstrapline)
sendexec(io,
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -103,8 +103,8 @@ class Group:
gateway.id = id
def _unregister(self, gateway):
+ del self._id2gateway[gateway.id]
del self._activegateways[gateway]
- del self._id2gateway[gateway.id]
def _cleanup_atexit(self):
trace("=== atexit cleanup %r ===" %(self,))
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -17,6 +17,8 @@ termination of Gateways. Example usage:
>>> len(group)
2
>>> group.terminate() # exit all member gateways
+ >>> group
+
Acessing Gateways on a group by ID
------------------------------------------------------
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -82,7 +82,7 @@ class TestGroup:
group._cleanup_atexit()
assert not group._activegateways
- def test_group_ordering(self):
+ def test_group_ordering_and_termination(self):
group = Group()
gw = group.makegateway("popen//id=3")
gw = group.makegateway("popen//id=2")
@@ -92,6 +92,8 @@ class TestGroup:
idlist = [x.id for x in gwlist]
assert idlist == list('235')
group.terminate()
+ assert not group
+ assert repr(group) == ""
def test_gateway_and_id(self):
group = Group()
From commits-noreply at bitbucket.org Tue Dec 1 18:00:17 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 1 Dec 2009 17:00:17 +0000 (UTC)
Subject: [execnet-commit] execnet commit a2b92aa8e4eb: skip/adapt tests
properly to pass on pypy-c and jython
Message-ID: <20091201170017.3D0A47EF06@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259686052 -3600
# Node ID a2b92aa8e4ebc43e05a3cec33c6037f924dd8d17
# Parent 92de97216048d44408050c688f65cd1ba18888e8
skip/adapt tests properly to pass on pypy-c and jython
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -8,7 +8,7 @@ def test_subprocess_interaction(anypytho
line = gateway.popen_bootstrapline
compile(line, 'xyz', 'exec')
args = [str(anypython), '-c', line]
- popen = subprocess.Popen(args, bufsize=0, stderr=subprocess.STDOUT,
+ popen = subprocess.Popen(args, bufsize=0,
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def send(line):
popen.stdin.write(line.encode('ascii'))
@@ -30,6 +30,7 @@ def test_subprocess_interaction(anypytho
send("world\n")
s = receive()
assert s == "received: world\n"
+ send('\n') # terminate loop
finally:
popen.stdin.close()
popen.stdout.close()
@@ -42,6 +43,8 @@ def read_write_loop():
while 1:
try:
line = sys.stdin.readline()
+ if not line.strip():
+ break
sys.stdout.write("received: %s" % line)
sys.stdout.flush()
except (IOError, EOFError):
@@ -49,7 +52,8 @@ def read_write_loop():
def pytest_generate_tests(metafunc):
if 'anypython' in metafunc.funcargnames:
- for name in 'python3.1', 'python2.4', 'python2.5', 'python2.6':
+ for name in ('python3.1', 'python2.4', 'python2.5', 'python2.6',
+ 'pypy-c', 'jython'):
metafunc.addcall(id=name, param=name)
def pytest_funcarg__anypython(request):
--- a/testing/test_rsync.py
+++ b/testing/test_rsync.py
@@ -4,7 +4,7 @@ import execnet
def pytest_funcarg__gw1(request):
return request.cached_setup(
- setup=execnet.PopenGateway,
+ setup=lambda: execnet.makegateway("popen"),
teardown=lambda val: val.exit(),
scope="module"
)
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -9,7 +9,8 @@ from execnet.threadpool import WorkerPoo
queue = py.builtin._tryimport('queue', 'Queue')
TESTTIMEOUT = 10.0 # seconds
-skiponjython = py.test.mark.skipif("sys.platform.startswith('java')")
+needs_early_gc = py.test.mark.skipif("not hasattr(sys, 'getrefcount')")
+needs_osdup = py.test.mark.skipif("not hasattr(os, 'dup')")
def test_serialize_error(gw):
ch = gw.remote_exec("channel.send(ValueError(42))")
@@ -279,7 +280,7 @@ class TestChannelBasicBehaviour:
assert l == [0, 100, 200, 300, 400]
return subchannel
- @skiponjython
+ @needs_early_gc
def test_channel_callback_remote_freed(self, gw):
channel = self.check_channel_callback_stays_active(gw, earlyfree=False)
# freed automatically at the end of producer()
@@ -405,7 +406,7 @@ class TestChannelFile:
channel = gw.newchannel()
py.test.raises(ValueError, 'channel.makefile("rw")')
- @skiponjython
+ @needs_osdup
def test_confusion_from_os_write_stdout(self, gw):
channel = gw.remote_exec("""
import os
@@ -420,7 +421,7 @@ class TestChannelFile:
res = channel.receive()
assert res == 42
- @skiponjython
+ @needs_osdup
def test_confusion_from_os_write_stderr(self, gw):
channel = gw.remote_exec("""
import os
From commits-noreply at bitbucket.org Tue Dec 1 18:00:23 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 1 Dec 2009 17:00:23 +0000 (UTC)
Subject: [execnet-commit] execnet commit 9eddac8d8fc3: get rid of custom
source-detection and fix "is not" to "!=" for pypy-c/jython
Message-ID: <20091201170023.1BA367EF12@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259686787 -3600
# Node ID 9eddac8d8fc354c232459185a756c48c6c15de07
# Parent a2b92aa8e4ebc43e05a3cec33c6037f924dd8d17
get rid of custom source-detection and fix "is not" to "!=" for pypy-c/jython
--- a/execnet/rsync.py
+++ b/execnet/rsync.py
@@ -15,8 +15,7 @@ try:
except ImportError:
from Queue import Queue
-remote_file = os.path.join(os.path.dirname(__file__), 'rsync_remote.py')
-REMOTE_SOURCE = open(remote_file, 'r').read() + "\nf()"
+import execnet.rsync_remote
class RSync(object):
""" This class allows to send a directory structure (recursively)
@@ -151,7 +150,7 @@ class RSync(object):
assert name in ('delete',)
def itemcallback(req):
self._receivequeue.put((channel, req))
- channel = gateway.remote_exec(REMOTE_SOURCE)
+ channel = gateway.remote_exec(execnet.rsync_remote)
channel.setcallback(itemcallback, endmarker = None)
channel.send((str(destdir), options))
self._channels[channel] = finishedcallback
--- a/execnet/rsync_remote.py
+++ b/execnet/rsync_remote.py
@@ -1,7 +1,7 @@
"""
(c) 2006-2009, Armin Rigo, Holger Krekel, Maciej Fijalkowski
"""
-def f():
+def serve_rsync(channel):
import os, stat, shutil
try:
from hashlib import md5
@@ -79,7 +79,7 @@ def f():
channel.send(("links", None))
msg = channel.receive()
- while msg is not 42:
+ while msg != 42:
# we get symlink
_type, relpath, linkpoint = msg
assert _type == "link"
@@ -93,3 +93,5 @@ def f():
msg = channel.receive()
channel.send(("done", None))
+if __name__ == '__channelexec__':
+ serve_rsync(channel)
From commits-noreply at bitbucket.org Tue Dec 1 21:03:30 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 1 Dec 2009 20:03:30 +0000 (UTC)
Subject: [execnet-commit] execnet commit b6af4948c926: remove potential
support and tests for stdout/stderr redirection
Message-ID: <20091201200330.3E46D7EF1C@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259689143 -3600
# Node ID b6af4948c926f66fecef13c777d31cad11e14cd8
# Parent 9eddac8d8fc354c232459185a756c48c6c15de07
remove potential support and tests for stdout/stderr redirection
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -106,39 +106,6 @@ class Gateway(gateway_base.BaseGateway):
self._remotechannelthread = self.remote_exec(source)
self._remotechannelthread.send(num)
- def _remote_redirect(self, stdout=None, stderr=None):
- """ return a handle representing a redirection of a remote
- end's stdout to a local file object. with handle.close()
- the redirection will be reverted.
- """
- # XXX implement a remote_exec_in_globals(...)
- # to send ThreadOut implementation over
- clist = []
- for name, out in ('stdout', stdout), ('stderr', stderr):
- if out:
- outchannel = self.newchannel()
- outchannel.setcallback(getattr(out, 'write', out))
- channel = self.remote_exec("""
- import sys
- outchannel = channel.receive()
- ThreadOut(sys, %r).setdefaultwriter(outchannel.send)
- """ % name)
- channel.send(outchannel)
- clist.append(channel)
- for c in clist:
- c.waitclose()
- class Handle:
- def close(_):
- for name, out in ('stdout', stdout), ('stderr', stderr):
- if out:
- c = self.remote_exec("""
- import sys
- channel.gateway._ThreadOut(sys, %r).resetdefault()
- """ % name)
- c.waitclose()
- return Handle()
-
-
class RInfo:
def __init__(self, kwargs):
self.__dict__.update(kwargs)
--- a/testing/test_serializer.py
+++ b/testing/test_serializer.py
@@ -197,16 +197,6 @@ def test_small_long(py2, py3):
tp, s = py3.load(p)
assert s == "123"
-
- at py.test.mark.xfail
-# I'm not sure if we need the complexity.
-def test_recursive_list(py2, py3):
- l = [1, 2, 3]
- l.append(l)
- p = py2.dump(l)
- tp, rep = py2.load(l)
- assert tp == "list"
-
def test_bytes(py2, py3):
p = py3.dump("b'hi'")
tp, v = py2.load(p)
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -313,32 +313,6 @@ class TestChannelBasicBehaviour:
assert err
assert str(err).find("ValueError") != -1
- @py.test.mark.xfail
- def test_remote_redirect_stdout(self, gw):
- out = py.io.TextIO()
- handle = gw._remote_redirect(stdout=out)
- c = gw.remote_exec("print 42")
- c.waitclose(TESTTIMEOUT)
- handle.close()
- s = out.getvalue()
- assert s.strip() == "42"
-
- @py.test.mark.xfail
- def test_remote_exec_redirect_multi(self, gw):
- num = 3
- l = [[] for x in range(num)]
- channels = [gw.remote_exec("print %d" % i,
- stdout=l[i].append)
- for i in range(num)]
- for x in channels:
- x.waitclose(TESTTIMEOUT)
-
- for i in range(num):
- subl = l[i]
- assert subl
- s = subl[0]
- assert s.strip() == str(i)
-
class TestChannelFile:
def test_channel_file_write(self, gw):
channel = gw.remote_exec("""
From commits-noreply at bitbucket.org Tue Dec 1 21:03:30 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Tue, 1 Dec 2009 20:03:30 +0000 (UTC)
Subject: [execnet-commit] execnet commit 2c58c2663ecd: refine gateway
termination such that proper and unexpected exits
Message-ID: <20091201200330.641967EF1D@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259697442 -3600
# Node ID 2c58c2663ecdc212860bebbab8af6d500408692e
# Parent b6af4948c926f66fecef13c777d31cad11e14cd8
refine gateway termination such that proper and unexpected exits
can be better distinguished:
- channel.receive and channel.waitclose() will now
raise EOFError if the reading connection unexpectedly closed
- channel.send will raise an IOError if the writing connection
is closed
- callback endmarkers are delivered on unexpected closes
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -40,14 +40,15 @@ class Gateway(gateway_base.BaseGateway):
def exit(self):
""" trigger gateway exit. """
- self._trace("trigger gateway exit")
+ self._trace("gateway.exit() called")
try:
self._group._unregister(self)
except KeyError:
return # we assume it's already happened
- self._trace("stopping exec and closing write connection")
+ self._trace("--> stopping exec and sending GATEWAY_TERMINATE")
self._stopexec()
- self._stopsend()
+ self._send(Message.GATEWAY_TERMINATE(0, ''))
+ self._io.close_write()
#self.join(timeout=timeout) # receiverthread receive close() messages
def _remote_bootstrap_gateway(self, io):
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -203,9 +203,15 @@ def _setupmessages():
def received(self, gateway):
gateway._channelfactory._local_close(self.channelid, sendonly=True)
- classes = [STATUS, CHANNEL_OPEN, CHANNEL_NEW, CHANNEL_DATA,
- CHANNEL_CLOSE, CHANNEL_CLOSE_ERROR, CHANNEL_LAST_MESSAGE]
+ class GATEWAY_TERMINATE(Message):
+ def received(self, gateway):
+ gateway._io.close_read()
+ raise SystemExit(0)
+ classes = [
+ STATUS, CHANNEL_OPEN, CHANNEL_NEW, CHANNEL_DATA, CHANNEL_CLOSE,
+ CHANNEL_CLOSE_ERROR, CHANNEL_LAST_MESSAGE, GATEWAY_TERMINATE
+ ]
for i, cls in enumerate(classes):
Message._types[i] = cls
cls.msgtype = i
@@ -318,13 +324,17 @@ class Channel(object):
Msg = Message.CHANNEL_CLOSE
try:
self.gateway._send(Msg(self.id))
- except (IOError, ValueError): # ignore problems with sending
+ except IOError: # ignore problems with sending
pass
def _getremoteerror(self):
try:
return self._remoteerrors.pop(0)
except IndexError:
+ try:
+ return self.gateway._error
+ except AttributeError:
+ pass
return None
#
@@ -382,8 +392,10 @@ class Channel(object):
""" wait until this channel is closed (or the remote side
otherwise signalled that no more data was being sent).
The channel may still hold receiveable items, but not receive
- any more after waitclose() has returned. exceptions from executing
+ any more after waitclose() has returned. Exceptions from executing
code on the other side are reraised as local channel.RemoteErrors.
+ EOFError is raised if the reading-connection was prematurely closed,
+ which often indicates a dying process.
"""
self._receiveclosed.wait(timeout=timeout) # wait for non-"opened" state
if not self._receiveclosed.isSet():
@@ -395,7 +407,9 @@ class Channel(object):
def send(self, item):
"""sends the given item to the other side of the channel,
possibly blocking if the sender queue is full.
- Note that an item needs to be marshallable.
+ The item must be a simple python type and will be
+ copied to the other side by value. IOError is
+ raised if the write pipe was prematurely closed.
"""
if self.isclosed():
raise IOError("cannot send to %r" %(self,))
@@ -606,20 +620,14 @@ class BaseGateway(object):
except sysex:
break
except EOFError:
+ self._error = sys.exc_info()[1]
break
except:
self._trace("RECEIVERTHREAD: %s " %(
geterrortext(self.exc_info()), ))
break
finally:
- # XXX we need to signal fatal error states to
- # channels/callbacks, particularly ones
- # where the other side just died.
self._stopexec()
- try:
- self._stopsend()
- except IOError:
- self._trace('IOError on _stopsend()')
self._channelfactory._finished_receiving()
if threading: # might be None during shutdown/finalization
self._trace('leaving %r' % threading.currentThread())
@@ -629,10 +637,6 @@ class BaseGateway(object):
msg.writeto(self._serializer)
self._trace('sent -> %r' % msg)
- def _stopsend(self):
- self._io.close_write()
- self._trace('closed IO write pipe')
-
def _stopexec(self):
pass
@@ -671,7 +675,7 @@ class SlaveGateway(BaseGateway):
while 1:
item = self._execqueue.get()
if item is None:
- self._stopsend()
+ self._io.close_write()
break
try:
self.executetask(item)
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -496,41 +496,43 @@ class TestPopenGateway:
assert rinfo.cwd == py.std.os.getcwd()
assert rinfo.version_info == py.std.sys.version_info
- @py.test.mark.xfail # "fix needed: dying remote process does not cause waitclose() to fail"
def test_waitclose_on_remote_killed(self):
gw = execnet.makegateway('popen')
channel = gw.remote_exec("""
import os
import time
channel.send(os.getpid())
- while 1:
- channel.send("#" * 100)
+ time.sleep(100)
""")
remotepid = channel.receive()
py.process.kill(remotepid)
- py.test.raises(channel.RemoteError, "channel.waitclose(TESTTIMEOUT)")
- py.test.raises(EOFError, channel.send, None)
+ py.test.raises(EOFError, "channel.waitclose(TESTTIMEOUT)")
+ py.test.raises(IOError, channel.send, None)
py.test.raises(EOFError, channel.receive)
- at py.test.mark.xfail
+ def test_receive_on_remote_io_closed(self):
+ gw = execnet.makegateway('popen')
+ channel = gw.remote_exec("""
+ raise SystemExit()
+ """)
+ py.test.raises(EOFError, channel.receive)
+
+ at py.test.mark.skipif("not hasattr(os, 'kill')")
+ at py.test.mark.xfail("sys.platform.startswith('java')")
def test_endmarker_delivery_on_remote_killterm():
- if not hasattr(py.std.os, 'kill'):
- py.test.skip("no os.kill()")
gw = execnet.makegateway('popen')
- try:
- q = queue.Queue()
- channel = gw.remote_exec(source='''
- import os
- os.kill(os.getpid(), 15)
- ''')
- channel.setcallback(q.put, endmarker=999)
- val = q.get(TESTTIMEOUT)
- assert val == 999
- err = channel._getremoteerror()
- finally:
- gw.exit()
- assert "killed" in str(err)
- assert "15" in str(err)
+ q = queue.Queue()
+ channel = gw.remote_exec(source='''
+ import os
+ os.kill(os.getpid(), 15)
+ ''')
+ channel.setcallback(q.put, endmarker=999)
+ val = q.get(TESTTIMEOUT)
+ assert val == 999
+ # XXX jython does not get its io-read pipe closed
+ # when the remote side died?!
+ err = channel._getremoteerror()
+ assert isinstance(err, EOFError)
def test_socket_gw_host_not_found(gw):
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,11 @@ 1.0.1 (pending)
- fix group.terminate() termination to work (more reliably)
+- EOFError on channel.receive/waitclose if the other
+ side unexpectedly went away. When a gateway exits
+ it now internally sends an explicit termination message
+ instead of abruptly closing.
+
1.0.0
--------------------------------
From commits-noreply at bitbucket.org Wed Dec 2 11:02:35 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 2 Dec 2009 10:02:35 +0000 (UTC)
Subject: [execnet-commit] execnet commit 43c47dc35331: introduce
TimeoutError and receive(timeout) parameter
Message-ID: <20091202100235.ED8D67EF11@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259747951 -3600
# Node ID 43c47dc35331d81c146c16422b3d25f7b1fa5ffc
# Parent 2c58c2663ecdc212860bebbab8af6d500408692e
introduce TimeoutError and receive(timeout) parameter
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -10,18 +10,6 @@ and any exception will kill the whole re
This is especially bad if it happens remotely as
it leaves the other side inaccesssible.
-Control-C behaviour / best practises
--------------------------------------------
-tags: 1.1
-bb: http://bitbucket.org/hpk42/py-trunk/issue/36
-path: execnet/gateway_base.py
-
-currently calling channel.receive() in the
-main thread will prevent Control-C from passing.
-Consider an internal timeout so that signals
-get a chance to propagate.
-
-
rsync links from posix to windows
-----------------------------------------
tags: 1.1 feature
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -246,12 +246,16 @@ class RemoteError(EOFError):
# XXX do this better
sys.stderr.write("Warning: unhandled %r\n" % (self,))
+class TimeoutError(IOError):
+ """ Exception indicating that a timeout was reached. """
+
NO_ENDMARKER_WANTED = object()
class Channel(object):
"""Communication channel between two Python Interpreter execution points."""
RemoteError = RemoteError
+ TimeoutError = TimeoutError
_executing = False
def __init__(self, gateway, id):
@@ -396,10 +400,12 @@ class Channel(object):
code on the other side are reraised as local channel.RemoteErrors.
EOFError is raised if the reading-connection was prematurely closed,
which often indicates a dying process.
+ self.TimeoutError is raised after the specified number of seconds
+ (default is None, i.e. wait indefinitely).
"""
- self._receiveclosed.wait(timeout=timeout) # wait for non-"opened" state
+ self._receiveclosed.wait(timeout=timeout) # wait for non-"opened" state
if not self._receiveclosed.isSet():
- raise IOError("Timeout")
+ raise self.TimeoutError("Timeout after %r seconds" % timeout)
error = self._getremoteerror()
if error:
raise error
@@ -419,19 +425,34 @@ class Channel(object):
data = Message.CHANNEL_DATA(self.id, item)
self.gateway._send(data)
- def receive(self):
- """receives an item that was sent from the other side,
- possibly blocking if there is none.
- Note that exceptions from the other side will be
+ def receive(self, timeout=-1):
+ """receive a data item that was sent from the other side.
+ timeout: -1 [default] blocked waiting, but wake up periodically
+ to let CTRL-C through. A positive number indicates the
+ number of seconds after which a channel.TimeoutError exception
+ will be raised if no item was received.
+ Note that exceptions from the remotely executing code will be
reraised as channel.RemoteError exceptions containing
a textual representation of the remote traceback.
"""
- queue = self._items
- if queue is None:
- raise IOError("calling receive() on channel with receiver callback")
- x = queue.get()
+ itemqueue = self._items
+ if itemqueue is None:
+ raise IOError("cannot receive(), channel has receiver callback")
+ if timeout < 0:
+ internal_timeout = 1000
+ else:
+ internal_timeout = timeout
+
+ while 1:
+ try:
+ x = itemqueue.get(timeout=internal_timeout)
+ break
+ except queue.Empty:
+ if internal_timeout < 0:
+ continue
+ raise self.TimeoutError("no item after %r seconds" %(timeout))
if x is ENDMARKER:
- queue.put(x) # for other receivers
+ itemqueue.put(x) # for other receivers
raise self._getremoteerror() or EOFError()
else:
return x
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -134,6 +134,18 @@ class TestChannelBasicBehaviour:
py.test.raises(EOFError, channel.receive)
py.test.raises(EOFError, channel.receive)
+ def test_waitclose_timeouterror(self, gw):
+ channel = gw.remote_exec("channel.receive()")
+ py.test.raises(channel.TimeoutError, channel.waitclose, 0.02)
+ channel.send(1)
+ channel.waitclose(timeout=TESTTIMEOUT)
+
+ def test_channel_receive_timeout(self, gw):
+ channel = gw.remote_exec('channel.send(channel.receive())')
+ py.test.raises(channel.TimeoutError, "channel.receive(timeout=0.2)")
+ channel.send(1)
+ x = channel.receive(timeout=0.1)
+
def test_channel_close_and_then_receive_error_multiple(self, gw):
channel = gw.remote_exec('channel.send(42) ; raise ValueError')
x = channel.receive()
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,10 @@ 1.0.1 (pending)
it now internally sends an explicit termination message
instead of abruptly closing.
+- introduce a timeout parameter to channel.receive()
+ and default to periodically internally wake up
+ to let KeyboardInterrupts pass through.
+
1.0.0
--------------------------------
From commits-noreply at bitbucket.org Thu Dec 3 03:11:57 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 02:11:57 +0000 (UTC)
Subject: [execnet-commit] execnet commit 81dde6121815: refactor gateway
termination and tests to allow for timely termination.
Message-ID: <20091203021157.63A397EF4E@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259806144 -3600
# Node ID 81dde61218151c0c0f7000b8ab19fb0289b4bdce
# Parent 87be32c84857879b4016ddc78e0f5db3641f6cc9
refactor gateway termination and tests to allow for timely termination.
It should now be more reliable and easy to properly
terminate (a group of) gateways.
Sorry, ronny - a somewhat larger commit but it'd
be hard to split this up into smaller bits as
termination is a rather pervasive issue.
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -5,7 +5,6 @@ import os, sys, time
import py
import execnet
from execnet import gateway_base, gateway
-from execnet.threadpool import WorkerPool
queue = py.builtin._tryimport('queue', 'Queue')
TESTTIMEOUT = 10.0 # seconds
@@ -443,22 +442,6 @@ class TestChannelFile:
gw._cache_rinfo = rinfo
gw.remote_exec("import os ; os.chdir(%r)" % old).waitclose()
-def test_join_blocked_slave_execution_gateway():
- gateway = execnet.makegateway('popen')
- channel = gateway.remote_exec("""
- import time
- time.sleep(10.0)
- """)
- def doit():
- gateway.exit()
- gateway.join(timeout=3.0)
- return 17
-
- pool = WorkerPool()
- reply = pool.dispatch(doit)
- x = reply.get(timeout=5.0)
- assert x == 17
-
class TestPopenGateway:
gwtype = 'popen'
@@ -529,24 +512,6 @@ class TestPopenGateway:
""")
py.test.raises(EOFError, channel.receive)
- at py.test.mark.skipif("not hasattr(os, 'kill')")
- at py.test.mark.xfail("sys.platform.startswith('java')")
-def test_endmarker_delivery_on_remote_killterm():
- gw = execnet.makegateway('popen')
- q = queue.Queue()
- channel = gw.remote_exec(source='''
- import os
- os.kill(os.getpid(), 15)
- ''')
- channel.setcallback(q.put, endmarker=999)
- val = q.get(TESTTIMEOUT)
- assert val == 999
- # XXX jython does not get its io-read pipe closed
- # when the remote side died?!
- err = channel._getremoteerror()
- assert isinstance(err, EOFError)
-
-
def test_socket_gw_host_not_found(gw):
py.test.raises(execnet.HostNotFound,
'execnet.makegateway("socket=qwepoipqwe:9000")'
@@ -610,22 +575,6 @@ class TestThreads:
gw.remote_init_threads(3)
py.test.raises(IOError, gw.remote_init_threads, 3)
-def test_close_initiating_remote_no_error(testdir):
- import subprocess
- dir = py.path.local(execnet.__file__).dirpath().dirpath()
- p = testdir.makepyfile("""
- import sys
- sys.path.insert(0, %r)
- import execnet
- gw = execnet.makegateway("popen")
- gw.remote_init_threads(num=2)
- ch = gw.remote_exec("channel.receive()")
- ch.close()
- """ % str(dir))
- popen = subprocess.Popen([sys.executable, str(p)],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
- stdout, stderr = popen.communicate()
- assert not stderr
class TestTracing:
def test_debug(self, monkeypatch):
@@ -649,6 +598,7 @@ class TestTracing:
py.test.fail("did not find %r in tracefile" %(slave_line,))
gw.exit()
+ @needs_osdup
def test_popen_stderr_tracing(self, capfd, monkeypatch):
monkeypatch.setenv('EXECNET_DEBUG', "2")
gw = execnet.makegateway("popen")
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,7 +5,9 @@ 1.0.1 (pending)
also improves debugging/tracebacks, as a side effect
popen-gateway startup can be substantially faster (>30%)
-- fix group.terminate() termination to work (more reliably)
+- refine internal gateway exit/termination procedure
+ and introduce group.terminate(timeout) for timely
+ termination.
- EOFError on channel.receive/waitclose if the other
side unexpectedly went away. When a gateway exits
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -39,17 +39,24 @@ class Gateway(gateway_base.BaseGateway):
self.__class__.__name__, addr, id, r, i)
def exit(self):
- """ trigger gateway exit. """
+ """ trigger gateway exit. Defer waiting for finishing
+ of receiver-thread and subprocess activity to when
+ group.terminate() is called.
+ """
self._trace("gateway.exit() called")
try:
self._group._unregister(self)
except KeyError:
- return # we assume it's already happened
- self._trace("--> stopping exec and sending GATEWAY_TERMINATE")
- self._stopexec()
- self._send(Message.GATEWAY_TERMINATE(0, ''))
- self._io.close_write()
- #self.join(timeout=timeout) # receiverthread receive close() messages
+ self._trace("gateway already unregistered with group")
+ return
+ self._trace("--> sending GATEWAY_TERMINATE")
+ try:
+ self._send(Message.GATEWAY_TERMINATE(0, ''))
+ self._io.close_write()
+ except IOError:
+ v = sys.exc_info()[1]
+ self._trace("io-error: could not send termination sequence")
+ self._trace(" exception: %r" % v)
def _remote_bootstrap_gateway(self, io):
""" send gateway bootstrap code to a remote Python interpreter
@@ -137,11 +144,6 @@ class PopenCmdGateway(Gateway):
io = Popen2IO(p.stdin, p.stdout)
super(PopenCmdGateway, self).__init__(io=io)
- def exit(self):
- super(PopenCmdGateway, self).exit()
- self._trace("polling Popen subprocess")
- self._popen.poll()
-
popen_bootstrapline = "import sys ; exec(eval(sys.stdin.readline()))"
class PopenGateway(PopenCmdGateway):
""" This Gateway provides interaction with a newly started
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -4,7 +4,7 @@ Managing Gateway Groups and interactions
(c) 2008-2009, Holger Krekel and others
"""
-import sys, weakref, atexit
+import sys, weakref, atexit, time
import execnet
from execnet import XSpec
from execnet import gateway
@@ -20,6 +20,7 @@ class Group:
self._activegateways = weakref.WeakKeyDictionary()
self._id2gateway = weakref.WeakValueDictionary()
self._autoidcounter = 1
+ self._gateways_to_join = []
for xspec in xspecs:
self.makegateway(xspec)
atexit.register(self._cleanup_atexit)
@@ -105,24 +106,33 @@ class Group:
def _unregister(self, gateway):
del self._id2gateway[gateway.id]
del self._activegateways[gateway]
+ self._gateways_to_join.append(gateway)
def _cleanup_atexit(self):
trace("=== atexit cleanup %r ===" %(self,))
- self.terminate()
+ self.terminate(timeout=2.0)
- def terminate(self):
- """ trigger exit of all gateways. """
- gwlist = []
- while 1:
- try:
- gw, _ = self._activegateways.popitem()
- except KeyError:
- break
- else:
- gw.exit()
- gwlist.append(gw)
- #for gw in gwlist:
- # gw.join(timeout=1.0)
+ def terminate(self, timeout=None):
+ """ trigger exit of member gateways and and wait for completion
+ of receiver-threads of previously exited member gateways and any
+ started subprocesses. If after timeout seconds (None for no-timeout)
+ waiting does not finish a TimeoutError will be raised.
+ """
+ endtime = timeout and (time.time() + timeout) or None
+ for gw in self:
+ gw.exit()
+ def join_receiver_and_wait_for_subprocesses():
+ for gw in self._gateways_to_join:
+ gw.join()
+ while self._gateways_to_join:
+ gw = self._gateways_to_join.pop(0)
+ if hasattr(gw, '_popen'):
+ gw._popen.wait()
+ self._gateways_to_join[:] = []
+ from execnet.threadpool import WorkerPool
+ pool = WorkerPool(1)
+ reply = pool.dispatch(join_receiver_and_wait_for_subprocesses)
+ reply.get(timeout=timeout)
def remote_exec(self, source):
channels = []
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -27,8 +27,8 @@ else:
sysex = (KeyboardInterrupt, SystemExit)
DEBUG = os.environ.get('EXECNET_DEBUG')
+pid = os.getpid()
if DEBUG == '2':
- pid = os.getpid()
def trace(msg):
sys.stderr.write("[%s] %s\n" % (pid, msg))
sys.stderr.flush()
@@ -38,12 +38,17 @@ elif DEBUG:
debugfile = open(fn, 'w')
def trace(msg):
try:
- debugfile.write(unicode(msg) + "\n")
+ debugfile.write(msg + "\n")
debugfile.flush()
except sysex:
raise
except:
- sys.stderr.write("exception during tracing\n")
+ v = sys.exc_info()[1]
+ try:
+ sys.stderr.write(
+ "[%d] exception during tracing: %r\n" % (pid, v))
+ except (IOError,ValueError):
+ pass # nothing we can do anymore
else:
def trace(msg):
pass
@@ -64,7 +69,6 @@ class SocketIO:
except (AttributeError, socket.error):
e = sys.exc_info()[1]
sys.stderr.write("WARNING: cannot set socketoption")
- self.readable = self.writeable = True
def read(self, numbytes):
"Read exactly 'bytes' bytes from the socket."
@@ -81,19 +85,15 @@ class SocketIO:
self.sock.sendall(data)
def close_read(self):
- if self.readable:
- try:
- self.sock.shutdown(0)
- except socket.error:
- pass
- self.readable = None
+ try:
+ self.sock.shutdown(0)
+ except socket.error:
+ pass
def close_write(self):
- if self.writeable:
- try:
- self.sock.shutdown(1)
- except socket.error:
- pass
- self.writeable = None
+ try:
+ self.sock.shutdown(1)
+ except socket.error:
+ pass
class Popen2IO:
error = (IOError, OSError, EOFError)
@@ -105,7 +105,6 @@ class Popen2IO:
import msvcrt
msvcrt.setmode(infile.fileno(), os.O_BINARY)
msvcrt.setmode(outfile.fileno(), os.O_BINARY)
- self.readable = self.writeable = True
def read(self, numbytes):
"""Read exactly 'numbytes' bytes from the pipe. """
@@ -127,16 +126,12 @@ class Popen2IO:
self.outfile.flush()
def close_read(self):
- if self.readable:
- self.infile.close()
- self.readable = None
+ trace("popen-io.close_read()")
+ self.infile.close()
def close_write(self):
- try:
- self.outfile.close()
- except EnvironmentError:
- pass
- self.writeable = None
+ trace("popen-io.close_write()")
+ self.outfile.close()
class Message:
""" encapsulates Messages and their wire protocol. """
@@ -211,7 +206,8 @@ def _setupmessages():
class GATEWAY_TERMINATE(Message):
def received(self, gateway):
- gateway._io.close_read()
+ gateway._trace("putting None to execqueue")
+ gateway._execqueue.put(None)
raise SystemExit(0)
classes = [
@@ -334,7 +330,7 @@ class Channel(object):
Msg = Message.CHANNEL_CLOSE
try:
self.gateway._send(Msg(self.id))
- except IOError: # ignore problems with sending
+ except (IOError, ValueError): # ignore problems with sending
pass
def _getremoteerror(self):
@@ -645,6 +641,7 @@ class BaseGateway(object):
finally:
_receivelock.release()
except sysex:
+ self._io.close_read()
break
except EOFError:
self._error = sys.exc_info()[1]
@@ -654,7 +651,6 @@ class BaseGateway(object):
geterrortext(self.exc_info()), ))
break
finally:
- self._stopexec()
self._channelfactory._finished_receiving()
if threading: # might be None during shutdown/finalization
self._trace('leaving %r' % threading.currentThread())
@@ -664,9 +660,6 @@ class BaseGateway(object):
msg.writeto(self._serializer)
self._trace('sent -> %r' % msg)
- def _stopexec(self):
- pass
-
def _local_schedulexec(self, channel, sourcetask):
channel.close("execution disallowed")
@@ -689,9 +682,6 @@ class BaseGateway(object):
"already finished")
class SlaveGateway(BaseGateway):
- def _stopexec(self):
- self._execqueue.put(None)
-
def _local_schedulexec(self, channel, sourcetask):
self._execqueue.put((channel, sourcetask))
@@ -702,13 +692,13 @@ class SlaveGateway(BaseGateway):
while 1:
item = self._execqueue.get()
if item is None:
- self._io.close_write()
break
try:
self.executetask(item)
except self._StopExecLoop:
break
finally:
+ self._io.close_write()
self._trace("slavegateway.serve finished")
if joining:
self.join()
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -1,8 +1,9 @@
-Creation and termination of of grouped Gateways
+Creating and working with grouped Gateways
------------------------------------------------------
-You can use ``execnet.Group`` to manage creation and
-termination of Gateways. Example usage::
+Use ``execnet.Group`` to manage the lifetime of multiple
+gateways and perform iteration and membership
+checks::
>>> import execnet
>>> group = execnet.Group()
@@ -12,21 +13,25 @@ termination of Gateways. Example usage:
>>> group
- >>> list(group) # list all member gateways
+ >>> list(group)
[, ]
>>> len(group)
2
+ >>> '1' in group and '2' in group
+ True
+ >>> group['1']
+
+
>>> group.terminate() # exit all member gateways
>>> group
-Acessing Gateways on a group by ID
+Assigning Gateway IDs
------------------------------------------------------
All gateways are created as part of a group and receive
a per-group unique ``id`` after successful initialization.
-This identification string can be set via an ``id=...`` part
-in an gateway URL as passed to ``group.makegateway``. Example::
+Pass an ``id=MYNAME`` part to ``group.makegateway``. Example::
>>> import execnet
>>> group = execnet.Group()
@@ -46,3 +51,22 @@ you actually use the ``execnet.default_g
>>> gw = execnet.makegateway("popen")
>>> gw in execnet.default_group
True
+
+Timely termination
+-------------------------
+
+Use ``group.terminate(timeout)`` if you want to terminate
+member gateways and wait a limited time for proper termination
+of all execution and IO activity::
+
+ >>> import execnet
+ >>> mygroup = execnet.Group()
+ >>> gw = mygroup.makegateway("popen")
+ >>> ch = gw.remote_exec("import time ; time.sleep(2.0)")
+ >>> try:
+ ... mygroup.terminate(timeout=1.0)
+ ... except IOError:
+ ... pass # subprocess-execution still sleeping
+ ... else:
+ ... print ("fail")
+ >>>
--- /dev/null
+++ b/testing/test_termination.py
@@ -0,0 +1,111 @@
+
+import sys, os
+import execnet
+import time
+import subprocess
+import py
+from execnet.threadpool import WorkerPool
+queue = py.builtin._tryimport('queue', 'Queue')
+from testing.test_gateway import TESTTIMEOUT
+execnetdir = py.path.local(execnet.__file__).dirpath().dirpath()
+
+def test_exit_blocked_slave_execution_gateway(anypython):
+ group = execnet.Group()
+ gateway = group.makegateway('popen//python=%s' % anypython)
+ channel = gateway.remote_exec("""
+ import time
+ time.sleep(10.0)
+ """)
+ def doit():
+ gateway.exit()
+ return 17
+
+ pool = WorkerPool()
+ reply = pool.dispatch(doit)
+ x = reply.get(timeout=5.0)
+ assert x == 17
+
+def test_endmarker_delivery_on_remote_killterm():
+ gw = execnet.makegateway('popen')
+ q = queue.Queue()
+ channel = gw.remote_exec(source='''
+ import os, time
+ channel.send(os.getpid())
+ time.sleep(100)
+ ''')
+ pid = channel.receive()
+ py.process.kill(pid)
+ channel.setcallback(q.put, endmarker=999)
+ val = q.get(TESTTIMEOUT)
+ assert val == 999
+ err = channel._getremoteerror()
+ assert isinstance(err, EOFError)
+
+def test_termination_on_remote_channel_receive(monkeypatch):
+ if not py.path.local.sysfind('ps'):
+ py.test.skip("need 'ps' command to externally check process status")
+ monkeypatch.setenv('EXECNET_DEBUG', '2')
+ group = execnet.Group()
+ gw = group.makegateway("popen")
+ pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive()
+ gw.remote_exec("channel.receive()")
+ group.terminate()
+ command = ["ps", "-p", str(pid)]
+ popen = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ out, err = popen.communicate()
+ out = py.builtin._totext(out, 'utf8')
+ assert str(pid) not in out, out
+
+def test_close_initiating_remote_no_error(testdir, anypython):
+ p = testdir.makepyfile("""
+ import sys
+ sys.path.insert(0, %r)
+ import execnet
+ gw = execnet.makegateway("popen")
+ gw.remote_init_threads(num=3)
+ ch1 = gw.remote_exec("channel.receive()")
+ ch2 = gw.remote_exec("channel.receive()")
+ ch3 = gw.remote_exec("channel.receive()")
+ execnet.default_group.terminate()
+ """ % str(execnetdir))
+ popen = subprocess.Popen([str(anypython), str(p)],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
+ stdout, stderr = popen.communicate()
+ assert not stderr
+
+def test_double_call_to_terminate(testdir, anypython):
+ triggerfile = testdir.tmpdir.join("trigger")
+ p = testdir.makepyfile("""
+ import sys
+ sys.path.insert(0, %r)
+ triggerfile = %r
+ import execnet
+ gw = execnet.makegateway("popen")
+ gw.remote_exec('''
+ import os, time
+ while 1:
+ if os.path.exists(%%r):
+ break
+ time.sleep(0.2)
+ ''' %% triggerfile)
+ ok = 0
+ try:
+ execnet.default_group.terminate(1.0)
+ except IOError:
+ try:
+ execnet.default_group.terminate(0.1)
+ except IOError:
+ f = open(triggerfile, 'w')
+ f.write("")
+ f.close()
+ execnet.default_group.terminate(5.0)
+ ok = 1
+ if not ok:
+ sys.stderr.write("no-timeout!\\n")
+ """ % (str(execnetdir), str(triggerfile)))
+ popen = subprocess.Popen([str(anypython), str(p)],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
+ stdout, stderr = popen.communicate()
+ assert not stderr
+
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -39,12 +39,24 @@ def getsocketspec(config=None):
def pytest_generate_tests(metafunc):
if 'gw' in metafunc.funcargnames:
+ assert 'anypython' not in metafunc.funcargnames, "need combine?"
if hasattr(metafunc.cls, 'gwtype'):
gwtypes = [metafunc.cls.gwtype]
else:
gwtypes = ['popen', 'socket', 'ssh']
for gwtype in gwtypes:
metafunc.addcall(id=gwtype, param=gwtype)
+ elif 'anypython' in metafunc.funcargnames:
+ for name in ('python3.1', 'python2.4', 'python2.5', 'python2.6',
+ 'pypy-c', 'jython'):
+ metafunc.addcall(id=name, param=name)
+
+def pytest_funcarg__anypython(request):
+ name = request.param
+ executable = py.path.local.sysfind(name)
+ if executable is None:
+ py.test.skip("no %s found" % (name,))
+ return executable
def pytest_funcarg__gw(request):
scope = "session"
@@ -81,3 +93,4 @@ def setup_ssh_gateway(request):
sshhost = request.getfuncargvalue('specssh').ssh
gw = execnet.makegateway("ssh=%s" %(sshhost,))
return gw
+
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -63,17 +63,25 @@ class TestGroup:
group = Group()
assert atexitlist == [group._cleanup_atexit]
exitlist = []
+ joinlist = []
class PseudoGW:
def exit(self):
exitlist.append(self)
+ group._unregister(self)
+ def join(self):
+ joinlist.append(self)
gw = PseudoGW()
group._register(gw)
assert len(exitlist) == 0
+ assert len(joinlist) == 0
group._cleanup_atexit()
assert len(exitlist) == 1
assert exitlist == [gw]
+ assert len(joinlist) == 1
+ assert joinlist == [gw]
group._cleanup_atexit()
assert len(exitlist) == 1
+ assert len(joinlist) == 1
def test_group_PopenGateway(self):
group = Group()
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -50,19 +50,6 @@ def read_write_loop():
except (IOError, EOFError):
break
-def pytest_generate_tests(metafunc):
- if 'anypython' in metafunc.funcargnames:
- for name in ('python3.1', 'python2.4', 'python2.5', 'python2.6',
- 'pypy-c', 'jython'):
- metafunc.addcall(id=name, param=name)
-
-def pytest_funcarg__anypython(request):
- name = request.param
- executable = py.path.local.sysfind(name)
- if executable is None:
- py.test.skip("no %s found" % (name,))
- return executable
-
def test_io_message(anypython, tmpdir):
check = tmpdir.join("check.py")
check.write(py.code.Source(gateway_base, """
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -95,6 +95,26 @@ two asynchronously running programs.
.. automethod:: Channel.waitclose(timeout)
.. autoattribute:: Channel.RemoteError
+.. _Group:
+
+Grouping Gateways and guaranteed termination
+===============================================
+
+.. _`group examples`: examples.html#group
+.. currentmodule:: execnet.multi
+
+All created gateway instances are part of a group. Group objects
+are container-like objects (see `group examples`_) and manage
+the final termination procedure:
+
+.. automethod:: Group.terminate(timeout=None)
+
+This method is implicitely called for each gateway group at
+process-exit, using a small timeout. This is fine
+for interactive sessions or random scripts which
+you rather like to error out than hang. If you start many
+processes then you often want to call ``group.terminate()``
+yourself and specify a larger or not timeout.
remote_status: get low-level execution info
===================================================
@@ -108,23 +128,10 @@ information from the remote side.
Calling this method tells you e.g. how many execution
tasks are queued, how many are executing and how many
-channels are active.
-
-
-.. _Group:
-
-Grouping Gateways
-============================
-
-All created gateway instances are part of a group. Gateways made
-through the global ``execnet.makegateway()`` will become a
-member of the ``default_group``::
-
-A Group instance can terminate its gateways explicitely
-or at process termination (each group registers with Python's
-``atexit`` mechanism). See the `group examples`_ for details.
-
-.. _`group examples`: examples#group
+channels are active. Note that remote_status()
+works even if the other side's execution thread
+is blocked and can thus be used to query status
+in a non-blocking manner.
rsync: synchronise filesystem with remote
===============================================================
From commits-noreply at bitbucket.org Thu Dec 3 03:11:55 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 02:11:55 +0000 (UTC)
Subject: [execnet-commit] execnet commit 57c400924a5a: refine EXECNET_DEBUG
tracing
Message-ID: <20091203021155.904067EF28@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259753970 -3600
# Node ID 57c400924a5a4ebf5bc6b126b0e8829c279b5de5
# Parent 43c47dc35331d81c146c16422b3d25f7b1fa5ffc
refine EXECNET_DEBUG tracing
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -626,13 +626,37 @@ def test_close_initiating_remote_no_erro
stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
stdout, stderr = popen.communicate()
assert not stderr
-
-def test_debug(monkeypatch):
- monkeypatch.setenv('EXECNET_DEBUG', "1")
- source = py.std.inspect.getsource(gateway_base)
- d = {}
- gateway_base.do_exec(source, d)
- assert 'debugfile' in d
+
+class TestTracing:
+ def test_debug(self, monkeypatch):
+ monkeypatch.setenv('EXECNET_DEBUG', "1")
+ source = py.std.inspect.getsource(gateway_base)
+ d = {}
+ gateway_base.do_exec(source, d)
+ assert 'debugfile' in d
+
+ def test_popen_filetracing(self, monkeypatch):
+ monkeypatch.setenv('EXECNET_DEBUG', "1")
+ gw = execnet.makegateway("popen")
+ pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive()
+ tmpdir = py.path.local(py.std.tempfile.gettempdir())
+ slavefile = tmpdir.join("execnet-debug-%s" % pid)
+ slave_line = "creating slavegateway"
+ for line in slavefile.readlines():
+ if slave_line in line:
+ break
+ else:
+ py.test.fail("did not find %r in tracefile" %(slave_line,))
+ gw.exit()
+
+ def test_popen_stderr_tracing(self, capfd, monkeypatch):
+ monkeypatch.setenv('EXECNET_DEBUG', "2")
+ gw = execnet.makegateway("popen")
+ pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive()
+ out, err = capfd.readouterr()
+ slave_line = "[%s] creating slavegateway" % pid
+ assert slave_line in err
+ gw.exit()
def test_nodebug():
from execnet import gateway_base
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -26,7 +26,13 @@ else:
sysex = (KeyboardInterrupt, SystemExit)
-if os.environ.get('EXECNET_DEBUG'):
+DEBUG = os.environ.get('EXECNET_DEBUG')
+if DEBUG == '2':
+ pid = os.getpid()
+ def trace(msg):
+ sys.stderr.write("[%s] %s\n" % (pid, msg))
+ sys.stderr.flush()
+elif DEBUG:
import tempfile, os.path
fn = os.path.join(tempfile.gettempdir(), 'execnet-debug-%d' % os.getpid())
debugfile = open(fn, 'w')
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -150,7 +150,9 @@ And here is API info about the RSync cla
Debugging execnet
===============================================================
-If you set the enviornment variable ``EXECNET_DEBUG`` to any value
-trace-files will be written to ``execnet-debug-PID`` files in
-the system temporary directory. ``NUM`` will be the process id.
+By setting the environment variable ``EXECNET_DEBUG`` you can
+configure a tracing mechanism:
+:EXECNET_DEBUG=1: write per-process trace-files to ``execnet-debug-PID``
+:EXECNET_DEUBG=2: perform tracing to stderr (popen-gateway slaves will send this to their instantiator)
+
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,6 +16,11 @@ 1.0.1 (pending)
and default to periodically internally wake up
to let KeyboardInterrupts pass through.
+- EXECNET_DEBUG=2 will cause tracing to go to stderr,
+ which with popen slave gateways will relay back
+ tracing to the instantiator process.
+
+
1.0.0
--------------------------------
From commits-noreply at bitbucket.org Thu Dec 3 03:11:57 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 02:11:57 +0000 (UTC)
Subject: [execnet-commit] execnet commit 87be32c84857: assume python2.3 and
use queue.get(timeout) instead of hacking our own.
Message-ID: <20091203021157.4D0047EF2E@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259799191 -3600
# Node ID 87be32c84857879b4016ddc78e0f5db3641f6cc9
# Parent 57c400924a5a4ebf5bc6b126b0e8829c279b5de5
assume python2.3 and use queue.get(timeout) instead of hacking our own.
--- a/execnet/threadpool.py
+++ b/execnet/threadpool.py
@@ -36,21 +36,6 @@ class Reply(object):
self._excinfo = excinfo
self._queue.put(ERRORMARKER)
- def _get_with_timeout(self, timeout):
- # taken from python2.3's Queue.get()
- # we want to run on python2.2 here
- delay = 0.0005 # 500 us -> initial delay of 1 ms
- endtime = time.time() + timeout
- while 1:
- try:
- return self._queue.get_nowait()
- except queue.Empty:
- remaining = endtime - time.time()
- if remaining <= 0: #time is over and no element arrived
- raise IOError("timeout waiting for task %r" %(self.task,))
- delay = min(delay * 2, remaining, .05)
- time.sleep(delay) #reduce CPU usage by using a sleep
-
def get(self, timeout=None):
""" get the result object from an asynchronous function execution.
if the function execution raised an exception,
@@ -59,10 +44,10 @@ class Reply(object):
"""
if self._queue is None:
raise EOFError("reply has already been delivered")
- if timeout is not None:
- result = self._get_with_timeout(timeout)
- else:
- result = self._queue.get()
+ try:
+ result = self._queue.get(timeout=timeout)
+ except queue.Empty:
+ raise IOError("timeout waiting for %r" %(self.task, ))
if result is ERRORMARKER:
self._queue = None
excinfo = self._excinfo
--- a/testing/test_threadpool.py
+++ b/testing/test_threadpool.py
@@ -74,6 +74,7 @@ def test_join_timeout():
reply.get(timeout=1.0)
pool.join(timeout=0.1)
+ at py.test.mark.skipif("not hasattr(os, 'dup')")
def test_pool_clean_shutdown():
capture = py.io.StdCaptureFD()
pool = WorkerPool()
From commits-noreply at bitbucket.org Thu Dec 3 17:15:37 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 16:15:37 +0000 (UTC)
Subject: [execnet-commit] execnet commit 26f11eb2bb99: avoid actually trying
to instantiate gateways for deprecation tests
Message-ID: <20091203161537.909D27EF35@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259834868 -3600
# Node ID 26f11eb2bb99bd1ba6e4b4bfe632333aebb148b1
# Parent 81dde61218151c0c0f7000b8ab19fb0289b4bdce
avoid actually trying to instantiate gateways for deprecation tests
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -16,11 +16,13 @@ def test_serialize_error(gw):
excinfo = py.test.raises(ch.RemoteError, ch.receive)
assert "can't serialize" in str(excinfo.value)
-def test_deprecation(recwarn):
+def test_deprecation(recwarn, monkeypatch):
execnet.PopenGateway()
assert recwarn.pop(DeprecationWarning)
- py.test.raises(Exception, 'execnet.SocketGateway("localhost", 8888)')
+ monkeypatch.setattr(py.std.socket, 'socket', lambda *args: 0/0)
+ py.test.raises(Exception, 'execnet.SocketGateway("localhost", 8811)')
assert recwarn.pop(DeprecationWarning)
+ monkeypatch.setattr(py.std.subprocess, 'Popen', lambda *args,**kwargs: 0/0)
py.test.raises(Exception, 'execnet.SshGateway("not-existing")')
assert recwarn.pop(DeprecationWarning)
From commits-noreply at bitbucket.org Thu Dec 3 17:15:39 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 16:15:39 +0000 (UTC)
Subject: [execnet-commit] execnet commit f429a449f16c: fix socket gateway
creation
Message-ID: <20091203161539.5C1EB7EF37@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259855712 -3600
# Node ID f429a449f16cf5e3a9b43f2fd181062ecb2c0e31
# Parent a29ab6d3ac2e74296a96859faffb335913313790
fix socket gateway creation
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -226,7 +226,7 @@ class SocketGateway(Gateway):
# "port=%r, hostname = %r" %(realport, hostname))
if not realhost or realhost=="0.0.0.0":
realhost = "localhost"
- return gateway._group.makegateway("socket=%s:%s" %(realhost, realport))
+ return cls(realhost, realport)
new_remote = classmethod(new_remote)
class HostNotFound(Exception):
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -66,7 +66,7 @@ class Group:
gateway_id = spec.installvia
if gateway_id:
viagw = self._id2gateway[gateway_id]
- return gateway.SocketGateway.new_remote(viagw)
+ gw = gateway.SocketGateway.new_remote(viagw)
else:
hostport = spec.socket.split(":")
gw = gateway.SocketGateway(*hostport)
--- a/testing/test_xspec.py
+++ b/testing/test_xspec.py
@@ -129,8 +129,9 @@ class TestMakegateway:
def test_ssh(self, specssh):
sshhost = specssh.ssh
- gw = execnet.makegateway("ssh=%s" % sshhost)
+ gw = execnet.makegateway("ssh=%s//id=ssh1" % sshhost)
rinfo = gw._rinfo()
+ assert gw.id == 'ssh1'
gw2 = execnet.SshGateway(sshhost)
rinfo2 = gw2._rinfo()
assert rinfo.executable == rinfo2.executable
@@ -138,11 +139,12 @@ class TestMakegateway:
assert rinfo.version_info == rinfo2.version_info
def test_socket(self, specsocket):
- gw = execnet.makegateway("socket=%s" % specsocket.socket)
+ gw = execnet.makegateway("socket=%s//id=sock1" % specsocket.socket)
rinfo = gw._rinfo()
assert rinfo.executable
assert rinfo.cwd
assert rinfo.version_info
+ assert gw.id == "sock1"
# we cannot instantiate a second gateway
#gw2 = execnet.SocketGateway(*specsocket.socket.split(":"))
@@ -150,3 +152,11 @@ class TestMakegateway:
#assert rinfo.executable == rinfo2.executable
#assert rinfo.cwd == rinfo2.cwd
#assert rinfo.version_info == rinfo2.version_info
+
+ def test_socket_installvia(self):
+ group = execnet.Group()
+ group.makegateway("popen//id=p1")
+ gw = group.makegateway("socket//installvia=p1//id=s1")
+ assert gw.id == "s1"
+ assert gw.remote_status()
+ group.terminate()
From commits-noreply at bitbucket.org Thu Dec 3 17:15:39 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 16:15:39 +0000 (UTC)
Subject: [execnet-commit] execnet commit a29ab6d3ac2e: add
remote_status/receiving issue
Message-ID: <20091203161539.4E5277EF36@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259852891 -3600
# Node ID a29ab6d3ac2e74296a96859faffb335913313790
# Parent 26f11eb2bb99bd1ba6e4b4bfe632333aebb148b1
add remote_status/receiving issue
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -46,3 +46,11 @@ This way a gateway can be instantiated t
intermediating gateway which bi-directionallry forwads
Messages through a existing channel.
+cleanup remote_status / receive status
+----------------------------------------
+tags: 1.0.1 feature
+
+remote_status() will only return a result object
+if the other side could receive so the "receiving"
+bit is redundant. Rather a gateway needs a
+gateway.isreceiving() method.
From commits-noreply at bitbucket.org Thu Dec 3 17:15:41 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 16:15:41 +0000 (UTC)
Subject: [execnet-commit] execnet commit 7681be82d2ff: refactor and re-group
tests and test gateway creation
Message-ID: <20091203161541.9E7187EF39@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259856463 -3600
# Node ID 7681be82d2ffb3f8de8253f9ac1278fe89be99d8
# Parent f429a449f16cf5e3a9b43f2fd181062ecb2c0e31
refactor and re-group tests and test gateway creation
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -40,7 +40,9 @@ def getsocketspec(config=None):
def pytest_generate_tests(metafunc):
if 'gw' in metafunc.funcargnames:
assert 'anypython' not in metafunc.funcargnames, "need combine?"
- if hasattr(metafunc.cls, 'gwtype'):
+ if hasattr(metafunc.function, 'gwtypes'):
+ gwtypes = metafunc.function.gwtypes
+ elif hasattr(metafunc.cls, 'gwtype'):
gwtypes = [metafunc.cls.gwtype]
else:
gwtypes = ['popen', 'socket', 'ssh']
@@ -60,37 +62,27 @@ def pytest_funcarg__anypython(request):
def pytest_funcarg__gw(request):
scope = "session"
- if request.param == "popen":
- return request.cached_setup(
- setup=lambda: execnet.makegateway("popen"),
- teardown=lambda gw: gw.exit(),
- extrakey=request.param,
- scope=scope)
- elif request.param == "socket":
- return request.cached_setup(
- setup=setup_socket_gateway,
- teardown=teardown_socket_gateway,
- extrakey=request.param,
- scope=scope)
- elif request.param == "ssh":
- return request.cached_setup(
- setup=lambda: setup_ssh_gateway(request),
- teardown=lambda gw: gw.exit(),
- extrakey=request.param,
- scope=scope)
-
-def setup_socket_gateway():
- proxygw = execnet.makegateway("popen")
- gw = execnet.makegateway("socket//installvia=%s" % proxygw.id)
- gw.proxygw = proxygw
- return gw
-
-def teardown_socket_gateway(gw):
- gw.exit()
- gw.proxygw.exit()
-
-def setup_ssh_gateway(request):
- sshhost = request.getfuncargvalue('specssh').ssh
- gw = execnet.makegateway("ssh=%s" %(sshhost,))
- return gw
-
+ group = request.cached_setup(
+ setup=execnet.Group,
+ teardown=lambda group: group.terminate(timeout=1),
+ extrakey="testgroup",
+ scope=scope,
+ )
+ try:
+ return group[request.param]
+ except KeyError:
+ if request.param == "popen":
+ gw = group.makegateway("popen//id=popen")
+ elif request.param == "socket":
+ pname = 'sproxy1'
+ if pname not in group:
+ proxygw = group.makegateway("popen//id=%s" % pname)
+ #assert group['proxygw'].remote_status().receiving
+ gw = group.makegateway("socket//id=socket//installvia=%s" % pname)
+ gw.proxygw = proxygw
+ assert pname in group
+
+ elif request.param == "ssh":
+ sshhost = request.getfuncargvalue('specssh').ssh
+ gw = group.makegateway("ssh=%s//id=ssh" %(sshhost,))
+ return gw
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -5,17 +5,10 @@ import os, sys, time
import py
import execnet
from execnet import gateway_base, gateway
-queue = py.builtin._tryimport('queue', 'Queue')
TESTTIMEOUT = 10.0 # seconds
-needs_early_gc = py.test.mark.skipif("not hasattr(sys, 'getrefcount')")
needs_osdup = py.test.mark.skipif("not hasattr(os, 'dup')")
-def test_serialize_error(gw):
- ch = gw.remote_exec("channel.send(ValueError(42))")
- excinfo = py.test.raises(ch.RemoteError, ch.receive)
- assert "can't serialize" in str(excinfo.value)
-
def test_deprecation(recwarn, monkeypatch):
execnet.PopenGateway()
assert recwarn.pop(DeprecationWarning)
@@ -26,9 +19,12 @@ def test_deprecation(recwarn, monkeypatc
py.test.raises(Exception, 'execnet.SshGateway("not-existing")')
assert recwarn.pop(DeprecationWarning)
-class TestBasicRemoteExecution:
+class TestBasicGateway:
def test_correct_setup(self, gw):
assert gw._receiverthread.isAlive()
+ assert gw in gw._group
+ assert gw.id in gw._group
+ assert gw.spec
def test_repr_doesnt_crash(self, gw):
assert isinstance(repr(gw), str)
@@ -122,277 +118,6 @@ class TestBasicRemoteExecution:
result = channel.receive()
assert result == 42
-class TestChannelBasicBehaviour:
- def test_channel_close_and_then_receive_error(self, gw):
- channel = gw.remote_exec('raise ValueError')
- py.test.raises(channel.RemoteError, channel.receive)
-
- def test_channel_finish_and_then_EOFError(self, gw):
- channel = gw.remote_exec('channel.send(42)')
- x = channel.receive()
- assert x == 42
- py.test.raises(EOFError, channel.receive)
- py.test.raises(EOFError, channel.receive)
- py.test.raises(EOFError, channel.receive)
-
- def test_waitclose_timeouterror(self, gw):
- channel = gw.remote_exec("channel.receive()")
- py.test.raises(channel.TimeoutError, channel.waitclose, 0.02)
- channel.send(1)
- channel.waitclose(timeout=TESTTIMEOUT)
-
- def test_channel_receive_timeout(self, gw):
- channel = gw.remote_exec('channel.send(channel.receive())')
- py.test.raises(channel.TimeoutError, "channel.receive(timeout=0.2)")
- channel.send(1)
- x = channel.receive(timeout=0.1)
-
- def test_channel_close_and_then_receive_error_multiple(self, gw):
- channel = gw.remote_exec('channel.send(42) ; raise ValueError')
- x = channel.receive()
- assert x == 42
- py.test.raises(channel.RemoteError, channel.receive)
-
- def test_channel__local_close(self, gw):
- channel = gw._channelfactory.new()
- gw._channelfactory._local_close(channel.id)
- channel.waitclose(0.1)
-
- def test_channel__local_close_error(self, gw):
- channel = gw._channelfactory.new()
- gw._channelfactory._local_close(channel.id,
- channel.RemoteError("error"))
- py.test.raises(channel.RemoteError, channel.waitclose, 0.01)
-
- def test_channel_error_reporting(self, gw):
- channel = gw.remote_exec('def foo():\n return foobar()\nfoo()\n')
- try:
- channel.receive()
- except channel.RemoteError:
- e = sys.exc_info()[1]
- assert str(e).startswith('Traceback (most recent call last):')
- assert str(e).find('NameError: global name \'foobar\' '
- 'is not defined') > -1
- else:
- py.test.fail('No exception raised')
-
- def test_channel_syntax_error(self, gw):
- # missing colon
- channel = gw.remote_exec('def foo()\n return 1\nfoo()\n')
- try:
- channel.receive()
- except channel.RemoteError:
- e = sys.exc_info()[1]
- assert str(e).startswith('Traceback (most recent call last):')
- assert str(e).find('SyntaxError') > -1
-
- def test_channel_iter(self, gw):
- channel = gw.remote_exec("""
- for x in range(3):
- channel.send(x)
- """)
- l = list(channel)
- assert l == [0, 1, 2]
-
- def test_channel_passing_over_channel(self, gw):
- channel = gw.remote_exec('''
- c = channel.gateway.newchannel()
- channel.send(c)
- c.send(42)
- ''')
- c = channel.receive()
- x = c.receive()
- assert x == 42
-
- # check that the both sides previous channels are really gone
- channel.waitclose(TESTTIMEOUT)
- #assert c.id not in gw._channelfactory
- newchan = gw.remote_exec('''
- assert %d not in channel.gateway._channelfactory._channels
- ''' % (channel.id))
- newchan.waitclose(TESTTIMEOUT)
- assert channel.id not in gw._channelfactory._channels
-
- def test_channel_receiver_callback(self, gw):
- l = []
- #channel = gw.newchannel(receiver=l.append)
- channel = gw.remote_exec(source='''
- channel.send(42)
- channel.send(13)
- channel.send(channel.gateway.newchannel())
- ''')
- channel.setcallback(callback=l.append)
- py.test.raises(IOError, channel.receive)
- channel.waitclose(TESTTIMEOUT)
- assert len(l) == 3
- assert l[:2] == [42,13]
- assert isinstance(l[2], channel.__class__)
-
- def test_channel_callback_after_receive(self, gw):
- l = []
- channel = gw.remote_exec(source='''
- channel.send(42)
- channel.send(13)
- channel.send(channel.gateway.newchannel())
- ''')
- x = channel.receive()
- assert x == 42
- channel.setcallback(callback=l.append)
- py.test.raises(IOError, channel.receive)
- channel.waitclose(TESTTIMEOUT)
- assert len(l) == 2
- assert l[0] == 13
- assert isinstance(l[1], channel.__class__)
-
- def test_waiting_for_callbacks(self, gw):
- l = []
- def callback(msg):
- import time; time.sleep(0.2)
- l.append(msg)
- channel = gw.remote_exec(source='''
- channel.send(42)
- ''')
- channel.setcallback(callback)
- channel.waitclose(TESTTIMEOUT)
- assert l == [42]
-
- def test_channel_callback_stays_active(self, gw):
- self.check_channel_callback_stays_active(gw, earlyfree=True)
-
- def check_channel_callback_stays_active(self, gw, earlyfree=True):
- # with 'earlyfree==True', this tests the "sendonly" channel state.
- l = []
- channel = gw.remote_exec(source='''
- try:
- import thread
- except ImportError:
- import _thread as thread
- import time
- def producer(subchannel):
- for i in range(5):
- time.sleep(0.15)
- subchannel.send(i*100)
- channel2 = channel.receive()
- thread.start_new_thread(producer, (channel2,))
- del channel2
- ''')
- subchannel = gw.newchannel()
- subchannel.setcallback(l.append)
- channel.send(subchannel)
- if earlyfree:
- subchannel = None
- counter = 100
- while len(l) < 5:
- if subchannel and subchannel.isclosed():
- break
- counter -= 1
- print(counter)
- if not counter:
- py.test.fail("timed out waiting for the answer[%d]" % len(l))
- time.sleep(0.04) # busy-wait
- assert l == [0, 100, 200, 300, 400]
- return subchannel
-
- @needs_early_gc
- def test_channel_callback_remote_freed(self, gw):
- channel = self.check_channel_callback_stays_active(gw, earlyfree=False)
- # freed automatically at the end of producer()
- channel.waitclose(TESTTIMEOUT)
-
- def test_channel_endmarker_callback(self, gw):
- l = []
- channel = gw.remote_exec(source='''
- channel.send(42)
- channel.send(13)
- channel.send(channel.gateway.newchannel())
- ''')
- channel.setcallback(l.append, 999)
- py.test.raises(IOError, channel.receive)
- channel.waitclose(TESTTIMEOUT)
- assert len(l) == 4
- assert l[:2] == [42,13]
- assert isinstance(l[2], channel.__class__)
- assert l[3] == 999
-
- def test_channel_endmarker_callback_error(self, gw):
- q = queue.Queue()
- channel = gw.remote_exec(source='''
- raise ValueError()
- ''')
- channel.setcallback(q.put, endmarker=999)
- val = q.get(TESTTIMEOUT)
- assert val == 999
- err = channel._getremoteerror()
- assert err
- assert str(err).find("ValueError") != -1
-
-class TestChannelFile:
- def test_channel_file_write(self, gw):
- channel = gw.remote_exec("""
- f = channel.makefile()
- f.write("hello world\\n")
- f.close()
- channel.send(42)
- """)
- first = channel.receive()
- assert first.strip() == 'hello world'
- second = channel.receive()
- assert second == 42
-
- def test_channel_file_write_error(self, gw):
- channel = gw.remote_exec("pass")
- f = channel.makefile()
- channel.waitclose(TESTTIMEOUT)
- py.test.raises(IOError, f.write, 'hello')
-
- def test_channel_file_proxyclose(self, gw):
- channel = gw.remote_exec("""
- f = channel.makefile(proxyclose=True)
- f.write("hello world")
- f.close()
- channel.send(42)
- """)
- first = channel.receive()
- assert first.strip() == 'hello world'
- py.test.raises(EOFError, channel.receive)
-
- def test_channel_file_read(self, gw):
- channel = gw.remote_exec("""
- f = channel.makefile(mode='r')
- s = f.read(2)
- channel.send(s)
- s = f.read(5)
- channel.send(s)
- """)
- channel.send("xyabcde")
- s1 = channel.receive()
- s2 = channel.receive()
- assert s1 == "xy"
- assert s2 == "abcde"
-
- def test_channel_file_read_empty(self, gw):
- channel = gw.remote_exec("pass")
- f = channel.makefile(mode="r")
- s = f.read(3)
- assert s == ""
- s = f.read(5)
- assert s == ""
-
- def test_channel_file_readline_remote(self, gw):
- channel = gw.remote_exec("""
- channel.send('123\\n45')
- """)
- channel.waitclose(TESTTIMEOUT)
- f = channel.makefile(mode="r")
- s = f.readline()
- assert s == "123\n"
- s = f.readline()
- assert s == "45"
-
- def test_channel_makefile_incompatmode(self, gw):
- channel = gw.newchannel()
- py.test.raises(ValueError, 'channel.makefile("rw")')
-
@needs_osdup
def test_confusion_from_os_write_stdout(self, gw):
channel = gw.remote_exec("""
--- /dev/null
+++ b/testing/test_channel.py
@@ -0,0 +1,461 @@
+"""
+mostly functional tests of gateways.
+"""
+import os, sys, time
+import py
+import execnet
+from execnet import gateway_base, gateway
+needs_early_gc = py.test.mark.skipif("not hasattr(sys, 'getrefcount')")
+needs_osdup = py.test.mark.skipif("not hasattr(os, 'dup')")
+queue = py.builtin._tryimport('queue', 'Queue')
+
+TESTTIMEOUT = 10.0 # seconds
+
+class TestChannelBasicBehaviour:
+ def test_serialize_error(self, gw):
+ ch = gw.remote_exec("channel.send(ValueError(42))")
+ excinfo = py.test.raises(ch.RemoteError, ch.receive)
+ assert "can't serialize" in str(excinfo.value)
+
+ def test_channel_close_and_then_receive_error(self, gw):
+ channel = gw.remote_exec('raise ValueError')
+ py.test.raises(channel.RemoteError, channel.receive)
+
+ def test_channel_finish_and_then_EOFError(self, gw):
+ channel = gw.remote_exec('channel.send(42)')
+ x = channel.receive()
+ assert x == 42
+ py.test.raises(EOFError, channel.receive)
+ py.test.raises(EOFError, channel.receive)
+ py.test.raises(EOFError, channel.receive)
+
+ def test_waitclose_timeouterror(self, gw):
+ channel = gw.remote_exec("channel.receive()")
+ py.test.raises(channel.TimeoutError, channel.waitclose, 0.02)
+ channel.send(1)
+ channel.waitclose(timeout=TESTTIMEOUT)
+
+ def test_channel_receive_timeout(self, gw):
+ channel = gw.remote_exec('channel.send(channel.receive())')
+ py.test.raises(channel.TimeoutError, "channel.receive(timeout=0.2)")
+ channel.send(1)
+ x = channel.receive(timeout=0.1)
+
+ def test_channel_close_and_then_receive_error_multiple(self, gw):
+ channel = gw.remote_exec('channel.send(42) ; raise ValueError')
+ x = channel.receive()
+ assert x == 42
+ py.test.raises(channel.RemoteError, channel.receive)
+
+ def test_channel__local_close(self, gw):
+ channel = gw._channelfactory.new()
+ gw._channelfactory._local_close(channel.id)
+ channel.waitclose(0.1)
+
+ def test_channel__local_close_error(self, gw):
+ channel = gw._channelfactory.new()
+ gw._channelfactory._local_close(channel.id,
+ channel.RemoteError("error"))
+ py.test.raises(channel.RemoteError, channel.waitclose, 0.01)
+
+ def test_channel_error_reporting(self, gw):
+ channel = gw.remote_exec('def foo():\n return foobar()\nfoo()\n')
+ try:
+ channel.receive()
+ except channel.RemoteError:
+ e = sys.exc_info()[1]
+ assert str(e).startswith('Traceback (most recent call last):')
+ assert str(e).find('NameError: global name \'foobar\' '
+ 'is not defined') > -1
+ else:
+ py.test.fail('No exception raised')
+
+ def test_channel_syntax_error(self, gw):
+ # missing colon
+ channel = gw.remote_exec('def foo()\n return 1\nfoo()\n')
+ try:
+ channel.receive()
+ except channel.RemoteError:
+ e = sys.exc_info()[1]
+ assert str(e).startswith('Traceback (most recent call last):')
+ assert str(e).find('SyntaxError') > -1
+
+ def test_channel_iter(self, gw):
+ channel = gw.remote_exec("""
+ for x in range(3):
+ channel.send(x)
+ """)
+ l = list(channel)
+ assert l == [0, 1, 2]
+
+ def test_channel_passing_over_channel(self, gw):
+ channel = gw.remote_exec('''
+ c = channel.gateway.newchannel()
+ channel.send(c)
+ c.send(42)
+ ''')
+ c = channel.receive()
+ x = c.receive()
+ assert x == 42
+
+ # check that the both sides previous channels are really gone
+ channel.waitclose(TESTTIMEOUT)
+ #assert c.id not in gw._channelfactory
+ newchan = gw.remote_exec('''
+ assert %d not in channel.gateway._channelfactory._channels
+ ''' % (channel.id))
+ newchan.waitclose(TESTTIMEOUT)
+ assert channel.id not in gw._channelfactory._channels
+
+ def test_channel_receiver_callback(self, gw):
+ l = []
+ #channel = gw.newchannel(receiver=l.append)
+ channel = gw.remote_exec(source='''
+ channel.send(42)
+ channel.send(13)
+ channel.send(channel.gateway.newchannel())
+ ''')
+ channel.setcallback(callback=l.append)
+ py.test.raises(IOError, channel.receive)
+ channel.waitclose(TESTTIMEOUT)
+ assert len(l) == 3
+ assert l[:2] == [42,13]
+ assert isinstance(l[2], channel.__class__)
+
+ def test_channel_callback_after_receive(self, gw):
+ l = []
+ channel = gw.remote_exec(source='''
+ channel.send(42)
+ channel.send(13)
+ channel.send(channel.gateway.newchannel())
+ ''')
+ x = channel.receive()
+ assert x == 42
+ channel.setcallback(callback=l.append)
+ py.test.raises(IOError, channel.receive)
+ channel.waitclose(TESTTIMEOUT)
+ assert len(l) == 2
+ assert l[0] == 13
+ assert isinstance(l[1], channel.__class__)
+
+ def test_waiting_for_callbacks(self, gw):
+ l = []
+ def callback(msg):
+ import time; time.sleep(0.2)
+ l.append(msg)
+ channel = gw.remote_exec(source='''
+ channel.send(42)
+ ''')
+ channel.setcallback(callback)
+ channel.waitclose(TESTTIMEOUT)
+ assert l == [42]
+
+ def test_channel_callback_stays_active(self, gw):
+ self.check_channel_callback_stays_active(gw, earlyfree=True)
+
+ def check_channel_callback_stays_active(self, gw, earlyfree=True):
+ # with 'earlyfree==True', this tests the "sendonly" channel state.
+ l = []
+ channel = gw.remote_exec(source='''
+ try:
+ import thread
+ except ImportError:
+ import _thread as thread
+ import time
+ def producer(subchannel):
+ for i in range(5):
+ time.sleep(0.15)
+ subchannel.send(i*100)
+ channel2 = channel.receive()
+ thread.start_new_thread(producer, (channel2,))
+ del channel2
+ ''')
+ subchannel = gw.newchannel()
+ subchannel.setcallback(l.append)
+ channel.send(subchannel)
+ if earlyfree:
+ subchannel = None
+ counter = 100
+ while len(l) < 5:
+ if subchannel and subchannel.isclosed():
+ break
+ counter -= 1
+ print(counter)
+ if not counter:
+ py.test.fail("timed out waiting for the answer[%d]" % len(l))
+ time.sleep(0.04) # busy-wait
+ assert l == [0, 100, 200, 300, 400]
+ return subchannel
+
+ @needs_early_gc
+ def test_channel_callback_remote_freed(self, gw):
+ channel = self.check_channel_callback_stays_active(gw, earlyfree=False)
+ # freed automatically at the end of producer()
+ channel.waitclose(TESTTIMEOUT)
+
+ def test_channel_endmarker_callback(self, gw):
+ l = []
+ channel = gw.remote_exec(source='''
+ channel.send(42)
+ channel.send(13)
+ channel.send(channel.gateway.newchannel())
+ ''')
+ channel.setcallback(l.append, 999)
+ py.test.raises(IOError, channel.receive)
+ channel.waitclose(TESTTIMEOUT)
+ assert len(l) == 4
+ assert l[:2] == [42,13]
+ assert isinstance(l[2], channel.__class__)
+ assert l[3] == 999
+
+ def test_channel_endmarker_callback_error(self, gw):
+ q = queue.Queue()
+ channel = gw.remote_exec(source='''
+ raise ValueError()
+ ''')
+ channel.setcallback(q.put, endmarker=999)
+ val = q.get(TESTTIMEOUT)
+ assert val == 999
+ err = channel._getremoteerror()
+ assert err
+ assert str(err).find("ValueError") != -1
+
+class TestChannelFile:
+ def test_channel_file_write(self, gw):
+ channel = gw.remote_exec("""
+ f = channel.makefile()
+ f.write("hello world\\n")
+ f.close()
+ channel.send(42)
+ """)
+ first = channel.receive()
+ assert first.strip() == 'hello world'
+ second = channel.receive()
+ assert second == 42
+
+ def test_channel_file_write_error(self, gw):
+ channel = gw.remote_exec("pass")
+ f = channel.makefile()
+ channel.waitclose(TESTTIMEOUT)
+ py.test.raises(IOError, f.write, 'hello')
+
+ def test_channel_file_proxyclose(self, gw):
+ channel = gw.remote_exec("""
+ f = channel.makefile(proxyclose=True)
+ f.write("hello world")
+ f.close()
+ channel.send(42)
+ """)
+ first = channel.receive()
+ assert first.strip() == 'hello world'
+ py.test.raises(EOFError, channel.receive)
+
+ def test_channel_file_read(self, gw):
+ channel = gw.remote_exec("""
+ f = channel.makefile(mode='r')
+ s = f.read(2)
+ channel.send(s)
+ s = f.read(5)
+ channel.send(s)
+ """)
+ channel.send("xyabcde")
+ s1 = channel.receive()
+ s2 = channel.receive()
+ assert s1 == "xy"
+ assert s2 == "abcde"
+
+ def test_channel_file_read_empty(self, gw):
+ channel = gw.remote_exec("pass")
+ f = channel.makefile(mode="r")
+ s = f.read(3)
+ assert s == ""
+ s = f.read(5)
+ assert s == ""
+
+ def test_channel_file_readline_remote(self, gw):
+ channel = gw.remote_exec("""
+ channel.send('123\\n45')
+ """)
+ channel.waitclose(TESTTIMEOUT)
+ f = channel.makefile(mode="r")
+ s = f.readline()
+ assert s == "123\n"
+ s = f.readline()
+ assert s == "45"
+
+ def test_channel_makefile_incompatmode(self, gw):
+ channel = gw.newchannel()
+ py.test.raises(ValueError, 'channel.makefile("rw")')
+
+
+class TestPopenGateway:
+ gwtype = 'popen'
+
+ def test_chdir_separation(self, tmpdir):
+ old = tmpdir.chdir()
+ try:
+ gw = execnet.makegateway('popen')
+ finally:
+ waschangedir = old.chdir()
+ c = gw.remote_exec("import os ; channel.send(os.getcwd())")
+ x = c.receive()
+ assert x == str(waschangedir)
+
+ def test_remoteerror_readable_traceback(self, gw):
+ e = py.test.raises(gateway_base.RemoteError,
+ 'gw.remote_exec("x y").waitclose()')
+ assert "gateway_base" in e.value.formatted
+
+ def test_many_popen(self):
+ num = 4
+ l = []
+ for i in range(num):
+ l.append(execnet.makegateway('popen'))
+ channels = []
+ for gw in l:
+ channel = gw.remote_exec("""channel.send(42)""")
+ channels.append(channel)
+## try:
+## while channels:
+## channel = channels.pop()
+## try:
+## ret = channel.receive()
+## assert ret == 42
+## finally:
+## channel.gateway.exit()
+## finally:
+## for x in channels:
+## x.gateway.exit()
+ while channels:
+ channel = channels.pop()
+ ret = channel.receive()
+ assert ret == 42
+
+ def test_rinfo_popen(self, gw):
+ rinfo = gw._rinfo()
+ assert rinfo.executable == py.std.sys.executable
+ assert rinfo.cwd == py.std.os.getcwd()
+ assert rinfo.version_info == py.std.sys.version_info
+
+ def test_waitclose_on_remote_killed(self):
+ gw = execnet.makegateway('popen')
+ channel = gw.remote_exec("""
+ import os
+ import time
+ channel.send(os.getpid())
+ time.sleep(100)
+ """)
+ remotepid = channel.receive()
+ py.process.kill(remotepid)
+ py.test.raises(EOFError, "channel.waitclose(TESTTIMEOUT)")
+ py.test.raises(IOError, channel.send, None)
+ py.test.raises(EOFError, channel.receive)
+
+ def test_receive_on_remote_io_closed(self):
+ gw = execnet.makegateway('popen')
+ channel = gw.remote_exec("""
+ raise SystemExit()
+ """)
+ py.test.raises(EOFError, channel.receive)
+
+def test_socket_gw_host_not_found(gw):
+ py.test.raises(execnet.HostNotFound,
+ 'execnet.makegateway("socket=qwepoipqwe:9000")'
+ )
+
+class TestSshPopenGateway:
+ gwtype = "ssh"
+
+ def test_sshconfig_config_parsing(self, monkeypatch):
+ import subprocess
+ l = []
+ monkeypatch.setattr(subprocess, 'Popen',
+ lambda *args, **kwargs: l.append(args[0]))
+ py.test.raises(AttributeError,
+ """execnet.makegateway("ssh=xyz//ssh_config=qwe")""")
+ assert len(l) == 1
+ popen_args = l[0]
+ i = popen_args.index('-F')
+ assert popen_args[i+1] == "qwe"
+
+ def test_sshaddress(self, gw, specssh):
+ assert gw.remoteaddress == specssh.ssh
+
+ def test_host_not_found(self, gw):
+ py.test.raises(execnet.HostNotFound,
+ "execnet.makegateway('ssh=nowhere.codespeak.net')")
+
+class TestThreads:
+ def test_threads(self):
+ gw = execnet.makegateway('popen')
+ gw.remote_init_threads(3)
+ c1 = gw.remote_exec("channel.send(channel.receive())")
+ c2 = gw.remote_exec("channel.send(channel.receive())")
+ c2.send(1)
+ res = c2.receive()
+ assert res == 1
+ c1.send(42)
+ res = c1.receive()
+ assert res == 42
+
+ def test_status_with_threads(self):
+ gw = execnet.makegateway('popen')
+ gw.remote_init_threads(3)
+ c1 = gw.remote_exec("channel.send(1) ; channel.receive()")
+ c2 = gw.remote_exec("channel.send(2) ; channel.receive()")
+ c1.receive()
+ c2.receive()
+ rstatus = gw.remote_status()
+ assert rstatus.numexecuting == 2 + 1
+ assert rstatus.execqsize == 0
+ c1.send(1)
+ c2.send(1)
+ c1.waitclose()
+ c2.waitclose()
+ rstatus = gw.remote_status()
+ assert rstatus.numexecuting == 0 + 1
+ assert rstatus.execqsize == 0
+
+ def test_threads_twice(self):
+ gw = execnet.makegateway('popen')
+ gw.remote_init_threads(3)
+ py.test.raises(IOError, gw.remote_init_threads, 3)
+
+
+class TestTracing:
+ def test_debug(self, monkeypatch):
+ monkeypatch.setenv('EXECNET_DEBUG', "1")
+ source = py.std.inspect.getsource(gateway_base)
+ d = {}
+ gateway_base.do_exec(source, d)
+ assert 'debugfile' in d
+
+ def test_popen_filetracing(self, monkeypatch):
+ monkeypatch.setenv('EXECNET_DEBUG', "1")
+ gw = execnet.makegateway("popen")
+ pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive()
+ tmpdir = py.path.local(py.std.tempfile.gettempdir())
+ slavefile = tmpdir.join("execnet-debug-%s" % pid)
+ slave_line = "creating slavegateway"
+ for line in slavefile.readlines():
+ if slave_line in line:
+ break
+ else:
+ py.test.fail("did not find %r in tracefile" %(slave_line,))
+ gw.exit()
+
+ @needs_osdup
+ def test_popen_stderr_tracing(self, capfd, monkeypatch):
+ monkeypatch.setenv('EXECNET_DEBUG', "2")
+ gw = execnet.makegateway("popen")
+ pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive()
+ out, err = capfd.readouterr()
+ slave_line = "[%s] creating slavegateway" % pid
+ assert slave_line in err
+ gw.exit()
+
+def test_nodebug():
+ from execnet import gateway_base
+ assert not hasattr(gateway_base, 'debugfile')
+
+
From commits-noreply at bitbucket.org Thu Dec 3 23:31:43 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 22:31:43 +0000 (UTC)
Subject: [execnet-commit] execnet commit 75e22c145464: assign gateway id's
early, cleanup
Message-ID: <20091203223143.271357F099@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259865531 -3600
# Node ID 75e22c14546489cfccef5af042cae853ac05abf3
# Parent 7681be82d2ffb3f8de8253f9ac1278fe89be99d8
assign gateway id's early, cleanup
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -13,8 +13,8 @@ importdir = os.path.dirname(os.path.dirn
class Gateway(gateway_base.BaseGateway):
""" Gateway to a local or remote Python Intepreter. """
- def __init__(self, io):
- super(Gateway, self).__init__(io=io, _startcount=1)
+ def __init__(self, io, id):
+ super(Gateway, self).__init__(io=io, id=id, _startcount=1)
self._remote_bootstrap_gateway(io)
self._initreceive()
@@ -138,25 +138,25 @@ channel.send(dict(
class PopenCmdGateway(Gateway):
_remotesetup = "io = init_popen_io()"
- def __init__(self, args):
+ def __init__(self, args, id):
from subprocess import Popen, PIPE
self._popen = p = Popen(args, stdin=PIPE, stdout=PIPE)
io = Popen2IO(p.stdin, p.stdout)
- super(PopenCmdGateway, self).__init__(io=io)
+ super(PopenCmdGateway, self).__init__(io=io, id=id)
popen_bootstrapline = "import sys ; exec(eval(sys.stdin.readline()))"
class PopenGateway(PopenCmdGateway):
""" This Gateway provides interaction with a newly started
python subprocess.
"""
- def __init__(self, python=None):
+ def __init__(self, id, python=None):
""" instantiate a gateway to a subprocess
started with the given 'python' executable.
"""
if not python:
python = sys.executable
args = [str(python), '-c', popen_bootstrapline]
- super(PopenGateway, self).__init__(args)
+ super(PopenGateway, self).__init__(args, id=id)
def _remote_bootstrap_gateway(self, io):
sendexec(io,
@@ -185,7 +185,7 @@ class SocketGateway(Gateway):
"""
_remotesetup = "io = SocketIO(clientsock)"
- def __init__(self, host, port):
+ def __init__(self, host, port, id):
""" instantiate a gateway to a process accessed
via a host/port specified socket.
"""
@@ -198,9 +198,9 @@ class SocketGateway(Gateway):
except socket.gaierror:
raise HostNotFound(str(sys.exc_info()[1]))
io = SocketIO(sock)
- super(SocketGateway, self).__init__(io=io)
+ super(SocketGateway, self).__init__(io=io, id=id)
- def new_remote(cls, gateway, hostport=None):
+ def new_remote(cls, gateway, id, hostport=None):
""" return a new (connected) socket gateway,
instantiated through the given 'gateway'.
"""
@@ -226,7 +226,7 @@ class SocketGateway(Gateway):
# "port=%r, hostname = %r" %(realport, hostname))
if not realhost or realhost=="0.0.0.0":
realhost = "localhost"
- return cls(realhost, realport)
+ return cls(realhost, realport, id=id)
new_remote = classmethod(new_remote)
class HostNotFound(Exception):
@@ -237,7 +237,7 @@ class SshGateway(PopenCmdGateway):
established via the 'ssh' command line binary.
The remote side needs to have a Python interpreter executable.
"""
- def __init__(self, sshaddress, remotepython=None, ssh_config=None):
+ def __init__(self, sshaddress, id, remotepython=None, ssh_config=None):
""" instantiate a remote ssh process with the
given 'sshaddress' and remotepython version.
you may specify an ssh_config file.
@@ -250,7 +250,7 @@ class SshGateway(PopenCmdGateway):
args.extend(['-F', str(ssh_config)])
remotecmd = '%s -c "%s"' %(remotepython, popen_bootstrapline)
args.extend([sshaddress, remotecmd])
- super(SshGateway, self).__init__(args)
+ super(SshGateway, self).__init__(args, id=id)
def _remote_bootstrap_gateway(self, io):
try:
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -56,24 +56,26 @@ class Group:
"""
if not isinstance(spec, XSpec):
spec = XSpec(spec)
+ id = self._allocate_id(spec.id)
if spec.popen:
- gw = gateway.PopenGateway(python=spec.python)
+ gw = gateway.PopenGateway(python=spec.python, id=id)
elif spec.ssh:
- gw = gateway.SshGateway(spec.ssh, remotepython=spec.python, ssh_config=spec.ssh_config)
+ gw = gateway.SshGateway(spec.ssh, remotepython=spec.python,
+ ssh_config=spec.ssh_config, id=id)
elif spec.socket:
assert not spec.python, (
"socket: specifying python executables not yet supported")
gateway_id = spec.installvia
if gateway_id:
viagw = self._id2gateway[gateway_id]
- gw = gateway.SocketGateway.new_remote(viagw)
+ gw = gateway.SocketGateway.new_remote(viagw, id=id)
else:
- hostport = spec.socket.split(":")
- gw = gateway.SocketGateway(*hostport)
+ host, port = spec.socket.split(":")
+ gw = gateway.SocketGateway(host, port, id=id)
else:
raise ValueError("no gateway type found for %r" % (spec._spec,))
gw.spec = spec
- self._register(gw, id=spec.id)
+ self._register(gw)
if spec.chdir or spec.nice:
channel = gw.remote_exec("""
import os
@@ -90,18 +92,22 @@ class Group:
channel.waitclose()
return gw
- def _register(self, gateway, id=None):
+ def _allocate_id(self, id=None):
+ if id is None:
+ id = str(self._autoidcounter)
+ self._autoidcounter += 1
+ assert not callable(id)
+ assert id not in self._id2gateway
+ return id
+
+ def _register(self, gateway):
assert not hasattr(gateway, '_group')
- if id is None:
- id = self._autoidcounter
- self._autoidcounter += 1
- id = str(id)
+ assert gateway.id
assert id not in self._id2gateway
assert gateway not in self._activegateways
self._activegateways[gateway] = True
- self._id2gateway[id] = gateway
+ self._id2gateway[gateway.id] = gateway
gateway._group = self
- gateway.id = id
def _unregister(self, gateway):
del self._id2gateway[gateway.id]
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -613,8 +613,9 @@ class BaseGateway(object):
class _StopExecLoop(Exception):
pass
- def __init__(self, io, _startcount=2):
+ def __init__(self, io, id, _startcount=2):
self._io = io
+ self.id = id
self._channelfactory = ChannelFactory(self, _startcount)
self._receivelock = threading.RLock()
self._serializer = Serializer(io)
@@ -1064,4 +1065,4 @@ def init_popen_io():
def serve(io):
trace("creating slavegateway on %r" %(io,))
- SlaveGateway(io=io, _startcount=2).serve()
+ SlaveGateway(io=io, id="slave", _startcount=2).serve()
From commits-noreply at bitbucket.org Thu Dec 3 23:31:44 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 22:31:44 +0000 (UTC)
Subject: [execnet-commit] execnet commit 00cb8b7aa529: do nicer,
faster and more informative tracing.
Message-ID: <20091203223144.B5E0C7F0AE@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259866049 -3600
# Node ID 00cb8b7aa5296d69f1312a77dda79835bb1959bd
# Parent 75e22c14546489cfccef5af042cae853ac05abf3
do nicer, faster and more informative tracing.
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -66,7 +66,7 @@ class Gateway(gateway_base.BaseGateway):
inspect.getsource(gateway_base),
self._remotesetup,
"io.write('1'.encode('ascii'))",
- "serve(io)"
+ "serve(io, id='%s-slave')" % self.id,
)
s = io.read(1)
assert s == "1".encode('ascii')
@@ -167,7 +167,7 @@ class PopenGateway(PopenCmdGateway):
sendexec(io,
"import sys ; sys.path.insert(0, %r)" % importdir,
"from execnet.gateway_base import serve, init_popen_io",
- "serve(init_popen_io())",
+ "serve(init_popen_io(), id='%s-slave')" % self.id,
)
s = io.read(1)
assert s == "1".encode('ascii')
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -96,7 +96,6 @@ class Group:
if id is None:
id = str(self._autoidcounter)
self._autoidcounter += 1
- assert not callable(id)
assert id not in self._id2gateway
return id
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -161,7 +161,7 @@ class PseudoChannel:
def test_exectask():
io = py.io.BytesIO()
- gw = gateway_base.SlaveGateway(io)
+ gw = gateway_base.SlaveGateway(io, id="something")
ch = PseudoChannel()
gw.executetask((ch, "raise ValueError()"))
assert "ValueError" in str(ch._closed[0])
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -29,16 +29,18 @@ sysex = (KeyboardInterrupt, SystemExit)
DEBUG = os.environ.get('EXECNET_DEBUG')
pid = os.getpid()
if DEBUG == '2':
- def trace(msg):
- sys.stderr.write("[%s] %s\n" % (pid, msg))
+ def trace(*msg):
+ line = " ".join(map(str, msg))
+ sys.stderr.write("[%s] %s\n" % (pid, line))
sys.stderr.flush()
elif DEBUG:
import tempfile, os.path
fn = os.path.join(tempfile.gettempdir(), 'execnet-debug-%d' % os.getpid())
debugfile = open(fn, 'w')
- def trace(msg):
+ def trace(*msg):
+ line = " ".join(map(str, msg))
try:
- debugfile.write(msg + "\n")
+ debugfile.write(line + "\n")
debugfile.flush()
except sysex:
raise
@@ -50,7 +52,7 @@ elif DEBUG:
except (IOError,ValueError):
pass # nothing we can do anymore
else:
- def trace(msg):
+ def trace(*msg):
pass
@@ -126,11 +128,9 @@ class Popen2IO:
self.outfile.flush()
def close_read(self):
- trace("popen-io.close_read()")
self.infile.close()
def close_write(self):
- trace("popen-io.close_write()")
self.outfile.close()
class Message:
@@ -269,6 +269,9 @@ class Channel(object):
self._receiveclosed = threading.Event()
self._remoteerrors = []
+ def _trace(self, *msg):
+ self.gateway._trace(self.id, *msg)
+
def setcallback(self, callback, endmarker=NO_ENDMARKER_WANTED):
""" set a callback function for receiving items.
@@ -312,7 +315,7 @@ class Channel(object):
def __del__(self):
if self.gateway is None: # can be None in tests
return
- self.gateway._trace("Channel(%d).__del__" % self.id)
+ self._trace("channel.__del__")
# no multithreading issues here, because we have the last ref to 'self'
if self._closed:
# state transition "closed" --> "deleted"
@@ -371,7 +374,7 @@ class Channel(object):
if self._executing:
raise IOError("cannot explicitly close channel within remote_exec")
if self._closed:
- trace("%r redundant call to close(), ignoring" %(self,))
+ self.gateway._trace(self, "ignoring redundant call to close()")
if not self._closed:
# state transition "opened/sendonly" --> "closed"
# threads warning: the channel might be closed under our feet,
@@ -384,7 +387,7 @@ class Channel(object):
put(Message.CHANNEL_CLOSE_ERROR(self.id, error))
else:
put(Message.CHANNEL_CLOSE(self.id))
- trace("%r: sent channel close message" %(self,))
+ self._trace("sent channel close message")
if isinstance(error, RemoteError):
self._remoteerrors.append(error)
self._closed = True # --> "closed"
@@ -609,6 +612,7 @@ class ChannelFileRead(ChannelFile):
class BaseGateway(object):
exc_info = sys.exc_info
+ id = ""
class _StopExecLoop(Exception):
pass
@@ -619,7 +623,10 @@ class BaseGateway(object):
self._channelfactory = ChannelFactory(self, _startcount)
self._receivelock = threading.RLock()
self._serializer = Serializer(io)
- self._trace = trace # globals may be NONE at process-termination
+ self._globaltrace = trace # globals may be NONE at process-termination
+
+ def _trace(self, *msg):
+ self._globaltrace(self.id, *msg)
def _initreceive(self):
self._receiverthread = threading.Thread(name="receiver",
@@ -634,7 +641,7 @@ class BaseGateway(object):
while 1:
try:
msg = Message.readfrom(unserializer)
- self._trace("received <- %r" % msg)
+ self._trace("received", msg)
_receivelock = self._receivelock
_receivelock.acquire()
try:
@@ -642,24 +649,25 @@ class BaseGateway(object):
finally:
_receivelock.release()
except sysex:
+ self._trace("io.close_read()")
self._io.close_read()
break
except EOFError:
self._error = sys.exc_info()[1]
break
except:
- self._trace("RECEIVERTHREAD: %s " %(
- geterrortext(self.exc_info()), ))
+ self._trace("RECEIVERTHREAD",
+ geterrortext(self.exc_info()))
break
finally:
self._channelfactory._finished_receiving()
if threading: # might be None during shutdown/finalization
- self._trace('leaving %r' % threading.currentThread())
+ self._trace('leaving', threading.currentThread())
def _send(self, msg):
assert isinstance(msg, Message)
msg.writeto(self._serializer)
- self._trace('sent -> %r' % msg)
+ self._trace('sent', msg)
def _local_schedulexec(self, channel, sourcetask):
channel.close("execution disallowed")
@@ -699,6 +707,7 @@ class SlaveGateway(BaseGateway):
except self._StopExecLoop:
break
finally:
+ self._trace("io.close_write()")
self._io.close_write()
self._trace("slavegateway.serve finished")
if joining:
@@ -1063,6 +1072,6 @@ def init_popen_io():
sys.stdout = os.fdopen(1, 'w', 1)
return io
-def serve(io):
+def serve(io, id):
trace("creating slavegateway on %r" %(io,))
- SlaveGateway(io=io, id="slave", _startcount=2).serve()
+ SlaveGateway(io=io, id=id, _startcount=2).serve()
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -65,6 +65,7 @@ class TestGroup:
exitlist = []
joinlist = []
class PseudoGW:
+ id = "9999"
def exit(self):
exitlist.append(self)
group._unregister(self)
From commits-noreply at bitbucket.org Fri Dec 4 00:01:04 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 23:01:04 +0000 (UTC)
Subject: [execnet-commit] execnet commit c4646e0593cb: restructuring example
docs
Message-ID: <20091203230104.EE0F97F0B2@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259880974 -3600
# Node ID c4646e0593cb7eff6403b832ce16771047e5c882
# Parent 00cb8b7aa5296d69f1312a77dda79835bb1959bd
restructuring example docs
--- a/doc/example/test_channelexec.txt
+++ b/doc/example/test_channelexec.txt
@@ -1,3 +1,4 @@
+.. _channelexec:
remote execute modules using their __name__
--------------------------------------------------------------
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -2,160 +2,21 @@
execnet examples
==============================================================================
-execnet is under active development. If you have questions
-or ideas or otherwise want to contribute please feel welcome
-to join the `execnet-dev`_ mailing list.
+.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
+.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
-.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
+execnet is under active development. Checkout `execnet-dev`_
+and `execnet-commit`_.
-Connect to Python2/Numpy from Python3
-----------------------------------------
+.. toctree::
+ :maxdepth: 1
-Here we run a Python3 interpreter to connect to a Python2.6 interpreter
-that has numpy installed. We send items to be added to an array and
-receive back the remote "repr" of the array::
-
- import execnet
- gw = execnet.makegateway("popen//python=python2.6")
- channel = gw.remote_exec("""
- import numpy
- array = numpy.array([1,2,3])
- while 1:
- x = channel.receive()
- if x is None:
- break
- array = numpy.append(array, x)
- channel.send(repr(array))
- """)
- for x in range(10):
- channel.send(x)
- channel.send(None)
- print (channel.receive())
-
-will print on the CPython3.1 side::
-
- array([1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
-
-A more refined real-life example of python3/python2 interaction
-is the anyvc_ project which uses version-control bindings in
-a Python2 subprocess in order to offer Python3-based library
-functionality.
-
-.. _anyvc: http://bitbucket.org/RonnyPfannschmidt/anyvc/overview/
-
-Work with Java objects from CPython
-----------------------------------------
-
-Use your CPython interpreter to connect to a Jython_ interpreter
-and work with Java types::
-
- import execnet
- gw = execnet.makegateway("popen//python=jython")
- channel = gw.remote_exec("""
- from java.util import Vector
- v = Vector()
- v.add('aaa')
- v.add('bbb')
- for val in v:
- channel.send(val)
- """)
-
- for item in channel:
- print (item)
-
-will print on the CPython side::
-
- aaa
- bbb
-
-.. _Jython: http://www.jython.org
-
-Work with C# objects from CPython
-----------------------------------------
-
-(Experimental) use your CPython interpreter to connect to a IronPython_ interpreter
-which can work with C# classes. Here is an example for instantiating
-a CLR Array instance and sending back its representation::
-
- import execnet
- gw = execnet.makegateway("popen//python=ipy")
-
- channel = gw.remote_exec("""
- import clr
- clr.AddReference("System")
- from System import Array
- array = Array[float]([1,2])
- channel.send(str(array))
- """)
- print (channel.receive())
-
-using Mono 2.0 and IronPython-1.1 this will print on the CPython side::
-
- System.Double[](1.0, 2.0)
-
-.. note::
- Using IronPython needs more testing, likely newer versions
- will work better. please feedback if you have information.
-
-.. _IronPython: http://www.IronPython.org
-
-
-.. include example/test_cwd.txt
-
-.. _channelexec:
-
-.. include:: example/test_channelexec.txt
-
-.. _command:
-
-.. include:: example/test_remotecmd.txt
-
-.. _group:
-
-.. include:: example/test_group.txt
-
-.. include:: example/test_syncreceive2.txt
-
-.. include:: example/test_asyncreceive2.txt
-
-
-Receive file contents from remote SSH account
------------------------------------------------------
-
-Here is a small program that you can use to retrieve
-contents of remote files::
-
- import execnet
- # open a gateway to a fresh child process
- gw = execnet.makegateway("ssh=codespeak.net")
- channel = gw.remote_exec("""
- for fn in channel:
- f = open(fn, 'rb')
- channel.send(f.read())
- f.close()
- """)
-
- for fn in somefilelist:
- channel.send(fn)
- content = channel.receive()
- # process content
-
- # later you can exit / close down the gateway
- gw.exit()
-
-
-Instantiate a socket server in a new subprocess
------------------------------------------------------
-
-(experimental) The following example opens a PopenGateway, i.e. a python
-child process, and starts a socket server within that process
-and then opens a second gateway to the freshly started
-socketserver::
-
- import execnet
-
- popengw = execnet.PopenGateway()
- socketgw = execnet.SocketGateway.new_remote(popengw, ("127.0.0.1", 0))
-
- print socketgw.remote_status() # print some info about the remote environment
-
+ example/test_connect
+ example/test_cwd.txt
+ example/test_channelexec.txt
+ example/test_remotecmd.txt
+ example/test_group.txt
+ example/test_syncreceive2.txt
+ example/test_asyncreceive2.txt
+ example/test_ssh_fileserver.txt
+ example/test_installvia.txt
--- a/doc/example/test_pid.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-
-Here is an self-contained example for reading the process identifier.
-
- >>> import execnet, os
- >>> gw = execnet.makegateway("popen")
- >>> channel = gw.remote_exec("""
- ... import os
- ... channel.send(os.getpid())
- ... """)
- >>> remote_pid = channel.receive()
- >>> remote_pid != os.getpid()
- True
-
-If you'd like to avoid inlining source strings take a look
-at the channelexec_ and command_ example.
-
-.. _channelexec: examples.html#channelexec
-.. _command: examples.html#command
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -1,3 +1,5 @@
+.. _group:
+
Creating and working with grouped Gateways
------------------------------------------------------
--- /dev/null
+++ b/doc/example/test_connect.txt
@@ -0,0 +1,93 @@
+
+Connect to Python2/Numpy from Python3
+----------------------------------------
+
+Here we run a Python3 interpreter to connect to a Python2.6 interpreter
+that has numpy installed. We send items to be added to an array and
+receive back the remote "repr" of the array::
+
+ import execnet
+ gw = execnet.makegateway("popen//python=python2.6")
+ channel = gw.remote_exec("""
+ import numpy
+ array = numpy.array([1,2,3])
+ while 1:
+ x = channel.receive()
+ if x is None:
+ break
+ array = numpy.append(array, x)
+ channel.send(repr(array))
+ """)
+ for x in range(10):
+ channel.send(x)
+ channel.send(None)
+ print (channel.receive())
+
+will print on the CPython3.1 side::
+
+ array([1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+
+A more refined real-life example of python3/python2 interaction
+is the anyvc_ project which uses version-control bindings in
+a Python2 subprocess in order to offer Python3-based library
+functionality.
+
+.. _anyvc: http://bitbucket.org/RonnyPfannschmidt/anyvc/overview/
+
+Work with Java objects from CPython
+----------------------------------------
+
+Use your CPython interpreter to connect to a `Jython 2.5.1`_ interpreter
+and work with Java types::
+
+ import execnet
+ gw = execnet.makegateway("popen//python=jython")
+ channel = gw.remote_exec("""
+ from java.util import Vector
+ v = Vector()
+ v.add('aaa')
+ v.add('bbb')
+ for val in v:
+ channel.send(val)
+ """)
+
+ for item in channel:
+ print (item)
+
+will print on the CPython side::
+
+ aaa
+ bbb
+
+.. _`Jython 2.5.1`: http://www.jython.org
+
+Work with C# objects from CPython
+----------------------------------------
+
+(Experimental) use your CPython interpreter to connect to a IronPython_ interpreter
+which can work with C# classes. Here is an example for instantiating
+a CLR Array instance and sending back its representation::
+
+ import execnet
+ gw = execnet.makegateway("popen//python=ipy")
+
+ channel = gw.remote_exec("""
+ import clr
+ clr.AddReference("System")
+ from System import Array
+ array = Array[float]([1,2])
+ channel.send(str(array))
+ """)
+ print (channel.receive())
+
+using Mono 2.0 and IronPython-1.1 this will print on the CPython side::
+
+ System.Double[](1.0, 2.0)
+
+.. note::
+ Using IronPython needs more testing, likely newer versions
+ will work better. please feedback if you have information.
+
+.. _IronPython: http://www.IronPython.org
+
+
--- a/doc/example/test_remotecmd.txt
+++ b/doc/example/test_remotecmd.txt
@@ -1,3 +1,5 @@
+.. _command:
+
A simple command pattern
--------------------------------------------------------------
--- /dev/null
+++ b/doc/example/test_installvia.txt
@@ -0,0 +1,16 @@
+
+Instantiate a socket server in a new subprocess
+-----------------------------------------------------
+
+(experimental) The following example opens a PopenGateway, i.e. a python
+child process, and starts a socket server within that process
+and then opens a second gateway to the freshly started
+socketserver::
+
+ import execnet
+
+ popengw = execnet.PopenGateway()
+ socketgw = execnet.SocketGateway.new_remote(popengw, ("127.0.0.1", 0))
+
+ print socketgw.remote_status() # print some info about the remote environment
+
--- /dev/null
+++ b/doc/example/test_ssh_fileserver.txt
@@ -0,0 +1,25 @@
+
+Receive file contents from remote SSH account
+-----------------------------------------------------
+
+Here is a small program that you can use to retrieve
+contents of remote files::
+
+ import execnet
+ # open a gateway to a fresh child process
+ gw = execnet.makegateway("ssh=codespeak.net")
+ channel = gw.remote_exec("""
+ for fn in channel:
+ f = open(fn, 'rb')
+ channel.send(f.read())
+ f.close()
+ """)
+
+ for fn in somefilelist:
+ channel.send(fn)
+ content = channel.receive()
+ # process content
+
+ # later you can exit / close down the gateway
+ gw.exit()
+
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -70,8 +70,6 @@ in the connected interpreter:
.. automethod:: Gateway.remote_exec(source)
-.. include:: example/test_pid.txt
-
.. _`Channel`:
.. _`channel-api`:
--- a/doc/example/test_cwd.txt
+++ b/doc/example/test_cwd.txt
@@ -8,3 +8,25 @@ A PopenGateway has the same working dire
>>> ch = gw.remote_exec("import os; channel.send(os.getcwd())")
>>> res = ch.receive()
>>> assert res == os.getcwd()
+
+
+Getting the remote process ID
+--------------------------------------
+
+Here is an self-contained example for reading a remote process identifier.
+
+ >>> import execnet, os
+ >>> gw = execnet.makegateway("popen")
+ >>> channel = gw.remote_exec("""
+ ... import os
+ ... channel.send(os.getpid())
+ ... """)
+ >>> remote_pid = channel.receive()
+ >>> remote_pid != os.getpid()
+ True
+
+If you'd like to avoid inlining source strings take a look
+at the channelexec_ and command_ example.
+
+.. _channelexec: examples.html#channelexec
+.. _command: examples.html#command
From commits-noreply at bitbucket.org Fri Dec 4 00:01:06 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 23:01:06 +0000 (UTC)
Subject: [execnet-commit] execnet commit d7477e49fdd0: new method:
gateway.hasreceiver() returns True
Message-ID: <20091203230106.BA4E67F0B3@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259881220 -3600
# Node ID d7477e49fdd084039a9ff597d109ebfa3f53c6e3
# Parent c4646e0593cb7eff6403b832ce16771047e5c882
new method: gateway.hasreceiver() returns True
if the gateway is still receive-active. remote_status
now only carries information about remote execution status.
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -78,6 +78,10 @@ class Gateway(gateway_base.BaseGateway):
self._cache_rinfo = RInfo(ch.receive())
return self._cache_rinfo
+ def hasreceiver(self):
+ """ return True if gateway is able to receive data. """
+ return self._receiverthread.isAlive() # approxmimation
+
def remote_status(self):
""" return information object about remote execution status. """
channel = self.newchannel()
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -168,8 +168,7 @@ def _setupmessages():
for ch in active_channels:
if getattr(ch, '_executing', False):
numexec += 1
- d = {'receiving': True,
- 'execqsize': gateway._execqueue.qsize(),
+ d = {'execqsize': gateway._execqueue.qsize(),
'numchannels': len(active_channels),
'numexecuting': numexec
}
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -21,7 +21,7 @@ def test_deprecation(recwarn, monkeypatc
class TestBasicGateway:
def test_correct_setup(self, gw):
- assert gw._receiverthread.isAlive()
+ assert gw.hasreceiver()
assert gw in gw._group
assert gw.id in gw._group
assert gw.spec
@@ -36,7 +36,6 @@ class TestBasicGateway:
def test_gateway_status_simple(self, gw):
status = gw.remote_status()
- assert status.receiving
assert not status.execqsize
assert status.numexecuting == 0
@@ -54,7 +53,6 @@ class TestBasicGateway:
ch2 = gw.remote_exec("channel.receive()")
ch1.receive()
status = gw.remote_status()
- assert status.receiving
assert status.numexecuting == 1 # number of active execution threads
assert status.execqsize == 1 # one more queued
assert status.numchannels == 2
@@ -63,7 +61,6 @@ class TestBasicGateway:
ch1.waitclose()
ch2.waitclose()
status = gw.remote_status()
- assert status.receiving
assert status.execqsize == 0
assert status.numexecuting == 0
assert status.numchannels == 0
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,10 @@
1.0.1 (pending)
--------------------------------
+- new method: gateway.hasreceiver() returns True
+ if the gateway is still receive-active. remote_status
+ now only carries information about remote execution status.
+
- have popen-gateways use imports instead of source-strings,
also improves debugging/tracebacks, as a side effect
popen-gateway startup can be substantially faster (>30%)
From commits-noreply at bitbucket.org Fri Dec 4 00:08:51 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Thu, 3 Dec 2009 23:08:51 +0000 (UTC)
Subject: [execnet-commit] execnet commit 9c5a39c0506e: remove redundant file,
better gateway repr, remove fixed issue
Message-ID: <20091203230851.CBC557F0B1@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259881560 -3600
# Node ID 9c5a39c0506e65266f279dd8ee4356bc9f9c5ce7
# Parent d7477e49fdd084039a9ff597d109ebfa3f53c6e3
remove redundant file, better gateway repr, remove fixed issue
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -46,11 +46,3 @@ This way a gateway can be instantiated t
intermediating gateway which bi-directionallry forwads
Messages through a existing channel.
-cleanup remote_status / receive status
-----------------------------------------
-tags: 1.0.1 feature
-
-remote_status() will only return a result object
-if the other side could receive so the "receiving"
-bit is redundant. Rather a gateway needs a
-gateway.isreceiving() method.
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -20,23 +20,14 @@ class Gateway(gateway_base.BaseGateway):
def __repr__(self):
""" return string representing gateway type and status. """
- if hasattr(self, 'id'):
- id = self.id
- else:
- id = "???"
- if hasattr(self, 'remoteaddress'):
- addr = '[%s]' % (self.remoteaddress,)
- else:
- addr = ''
try:
- r = (self._receiverthread.isAlive() and "receive-live" or
- "not-receiving")
+ r = (self.hasreceiver() and 'receive-live' or 'not-receiving')
i = len(self._channelfactory.channels())
except AttributeError:
r = "uninitialized"
i = "no"
- return "<%s%s id=%r %s, %s active channels>" %(
- self.__class__.__name__, addr, id, r, i)
+ return "<%s id=%r %s, %s active channels>" %(
+ self.__class__.__name__, self.id, r, i)
def exit(self):
""" trigger gateway exit. Defer waiting for finishing
--- a/doc/example/test_socketnewremote.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-Instantiate a Socket Gateway
------------------------------------------------------
-
-Sometimes there is no direct access method towards a
-machine. If you have a trusted network (like a internal
-developer LAN) you can use a simplistic `socketserver script`_
-which listens on a socket ("8888" by default) and executes
-incoming strings. Download it and run it on the target
-machine like this::
-
- python socketserver.py localhost:8888
-
-After which you can initiate a Gateway to the socket-server
-running machine like this::
-
- >>> import execnet
- >>> gw = execnet.makegateway("socket=localhost:8888") # doctest: +SKIP
-
-
-
-.. _`socketserver script`: http://bitbucket.org/hpk42/execnet/raw/493fc337a1f1/execnet/script/socketserver.py
-
-Instantiate a socket server through an existing Gateway
---------------------------------------------------------
-
-.. note::
- Experimental, please send a note if you are using
- the following feature.
-
-The following example shows a special method to open up
-a SocketServer gateway through another existing gateway.
-
- >>> import execnet
- >>> group = execnet.Group()
- >>> gw = group.makegateway("popen")
- >>> gw.id
- '1'
- >>> group.makegateway("socket//installvia=1") # doctest: +SKIP
-
-
-
-This method will instantiate a Socket based Gateway
-whose remote counterpart will be run via the
-given ``installvia`` specified gateway.
From commits-noreply at bitbucket.org Fri Dec 4 15:05:54 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Fri, 4 Dec 2009 14:05:54 +0000 (UTC)
Subject: [execnet-commit] execnet commit 3023e9db5d9e: re-work initial
documentation and web page
Message-ID: <20091204140554.4ECFB7F0B3@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259935536 -3600
# Node ID 3023e9db5d9e030e8c1467a9e132b673c02961b0
# Parent 9c5a39c0506e65266f279dd8ee4356bc9f9c5ce7
re-work initial documentation and web page
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -1,65 +1,62 @@
+
+execnet: connect your execution environments
+========================================================
+
.. image:: _static/pythonring.png
:align: right
-Welcome to execnet!
-=========================
+Execnet allows to ad-hoc connect to Python interpreters across version, platform and network barriers. It provides a minimal, fast and robust API for the following uses:
-The execnet package allows to:
+* distribute tasks to multiple CPUs
+* deploy hybrid applications
+* manage local and remote execution environments
-* instantiate local/remote Python Interpreters
-* send code for execution to one or many Interpreters
-* send and receive data through channels
+Features
+------------------
-execnet performs **zero-install bootstrapping** into other interpreters;
-package installation is only required at the initiating side. execnet enables
-interoperation between CPython 2.4-3.1, Jython 2.5.1, PyPy 1.1 and IronPython
-and works well on Windows, Linux and OSX systems.
+* zero-install bootstrapping: no remote installation required!
-execnet was written and is maintained by `Holger Krekel`_ (blog_) with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
+* flexible communication: send/receive as well as
+ callback/queue mechanisms supported
-.. note::
- Support of Jython and IronPython are experimental at this point.
- Feedback and help with testing welcome.
+* simple serialization of python builtin types (no pickling)
+* grouped creation and robust termination of processes
-Getting Started
-====================
+* well tested between CPython 2.4-3.1, Jython 2.5.1 and PyPy 1.1
+ interpreters.
-Install a public `pypi release`_ via `easy_install`_ or ``pip``::
+* fully interoperable between Windows and Unix-ish systems.
- easy_install -U execnet
+Known users
+-------------------
-To work from the development tree checkout the `execnet mercurial repository`_.
+* `py.test`_ uses it for its `distributed testing`_ mechanism.
-Next checkout the basic api and examples:
+* Jacob Perkins uses it for his `Distributed NTLK with execnet`_
+ project.
-.. toctree::
- :maxdepth: 1
+* Ronny Pfannschmidt uses it for his `anyvc`_ VCS-abstraction project
- basics
- examples
- changelog
+* sysadmins and developers are using it for ad-hoc custom scripting
-Issues / Contact channels
-===========================
+.. _`py.test`: http://pytest.org
+.. _`distributed testing`: http://codespeak.net/py/dist/test/dist.html
+.. _`Distributed NTLK with execnet`: http://streamhacker.com/2009/11/29/distributed-nltk-execnet/
+.. _`anyvc`: http://bitbucket.org/RonnyPfannschmidt/anyvc/
-If you have a questions, issues or suggestions you are welcome to join
-and post to the `execnet-dev`_ mailing list. Alternatively you
-you get see to help on the #pylib IRC channel on Freenode.
+Project status
+--------------------------
-There also is the `execnet-commit`_ mailing list
-which relays commit mails to the `bitbucket repository`_.
+The current 1.0 series aims at `basic API`_ stabilization, improved tracing and robust termination.
+The 1.1 series will target setting up permanent networks and offering unix-shell-like capabilities to spawn processes and applications.
+
+execnet was conceived and is `actively developed`_ by `Holger Krekel`_.
+The package is licensed under the GPL Version 2 or later, at your choice.
+Armin Rigo and Benjamin Peterson have done major contributions which
+are MIT-licensed.
+
+.. _`basic API`: basics.html
+.. _`actively developed`: http://bitbucket.org/hpk42/execnet/changesets
.. _`Holger Krekel`: http://twitter.com/hpk42
-.. _`blog`: http://tetamap.wordpress.com
-.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
-.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
-
-.. _`easy_install`: http://peak.telecommunity.com/DevCenter/EasyInstall
-.. _`bitbucket repository`: http://bitbucket.org/hpk42/execnet/
-.. _`execnet mercurial repository`: http://bitbucket.org/hpk42/execnet/
-.. _`pypi release`: http://pypi.python.org/pypi/execnet
-
-
-
-
--- a/setup.py
+++ b/setup.py
@@ -1,16 +1,32 @@
"""
-the execnet package allows to:
+execnet: connect your execution environments
+========================================================
-* instantiate local/remote Python Interpreters
-* send code for execution to one or many Interpreters
-* send and receive data between codeInterpreters through channels
+.. image:: _static/pythonring.png
+ :align: right
-execnet performs **zero-install bootstrapping** into other interpreters;
-package installation is only required at the initiating side. execnet enables
-interoperation between CPython 2.4-3.1, Jython 2.5 and PyPy 1.1 and works
-well on Windows, Linux and OSX systems.
+Execnet allows to ad-hoc connect to Python interpreters across version, platform and network barriers. It provides a minimal, fast and robust API for the following uses:
-execnet was written and is maintained by Holger Krekel with contributions from many others. The package is licensed under the GPL Version 2 or later, at your choice. Contributions and some parts of the package are licensed under the MIT license.
+* distribute tasks to multiple CPUs
+* deploy hybrid applications
+* manage local and remote execution environments
+
+Features
+------------------
+
+* zero-install bootstrapping: no remote installation required!
+
+* flexible communication: send/receive as well as
+ callback/queue mechanisms supported
+
+* simple serialization of python builtin types (no pickling)
+
+* grouped creation and robust termination of processes
+
+* well tested between CPython 2.4-3.1, Jython 2.5.1 and PyPy 1.1
+ interpreters.
+
+* fully interoperable between Windows and Unix-ish systems.
"""
try:
@@ -23,7 +39,7 @@ from execnet import __version__
def main():
setup(
name='execnet',
- description='execnet: elastic Python deployment',
+ description='execnet: connect your execution environments',
long_description = __doc__,
version= __version__,
url='http://codespeak.net/execnet',
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -3,12 +3,13 @@ execnet API in a nutshell
==============================================================================
execnet ad-hoc instantiates **gateways** to Python
-interpreter processes with which you can **remote execute
+interpreter processes though which you can **remote execute
code** and exchange basic python objects through **channels**.
+All gateways are created as part of a **group** which is responsible
+for managing membership and **proper termination**.
.. image:: _static/basic1.png
-
Gateways: connecting to another Python Interpreter
===================================================
@@ -19,12 +20,12 @@ passing it a gateway specification or UR
.. autofunction:: execnet.makegateway(xspec)
-Here is an example to instantiate a simple Python subprocess::
+Here is an example which instantiates a simple Python subprocess::
>>> gateway = execnet.makegateway("popen")
-You can then use this gateway object to `remote execute code`_ and
-`exchange data`_ bidirectionally.
+You can use a gateway to `remote execute code`_ and
+`exchange data`_ bidirectionally.
examples for valid gateway specifications
-------------------------------------------
@@ -70,6 +71,12 @@ in the connected interpreter:
.. automethod:: Gateway.remote_exec(source)
+It is allowed to pass a module object as source code
+in which case it's source code will be obtained and
+get sent for remote execution. ``remote_exec`` returns
+a channel object whose symmetric counterpart channel
+is available to the remotely executing source.
+
.. _`Channel`:
.. _`channel-api`:
@@ -86,24 +93,27 @@ two asynchronously running programs.
.. class:: Channel
.. automethod:: Channel.send(item)
- .. automethod:: Channel.receive()
+ .. automethod:: Channel.receive(timeout)
.. automethod:: Channel.setcallback(callback, endmarker=_NOENDMARKER)
.. automethod:: Channel.makefile(mode, proxyclose=False)
.. automethod:: Channel.close(error)
.. automethod:: Channel.waitclose(timeout)
.. autoattribute:: Channel.RemoteError
+ .. autoattribute:: Channel.TimeoutError
.. _Group:
-Grouping Gateways and guaranteed termination
+Grouped Gateways and robust termination
===============================================
.. _`group examples`: examples.html#group
.. currentmodule:: execnet.multi
-All created gateway instances are part of a group. Group objects
-are container-like objects (see `group examples`_) and manage
-the final termination procedure:
+All created gateway instances are part of a group. If you
+call ``execnet.makegateway`` it actually is forwarded to
+the ``execnet.default_group``. Group objects are container
+objects (see `group examples`_) and manage the final
+termination procedure:
.. automethod:: Group.terminate(timeout=None)
--- a/doc/_templates/layout.html
+++ b/doc/_templates/layout.html
@@ -2,10 +2,9 @@
{% block rootrellink %}
home |
- basics |
+ basic API | examples |
- mailing-list |
- pypi-home |
+ contact | docindex »
{% endblock %}
From commits-noreply at bitbucket.org Fri Dec 4 15:44:02 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Fri, 4 Dec 2009 14:44:02 +0000 (UTC)
Subject: [execnet-commit] execnet commit bdfe9a4a229e: adding contact and
install pages
Message-ID: <20091204144402.62D587F031@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259937824 -3600
# Node ID bdfe9a4a229e7f0bf6327997ae7bc593b87e6959
# Parent 3023e9db5d9e030e8c1467a9e132b673c02961b0
adding contact and install pages
--- /dev/null
+++ b/doc/install.txt
@@ -0,0 +1,34 @@
+
+Installation
+====================
+
+Install a public `pypi release`_ via `easy_install`_ or ``pip``::
+
+ easy_install -U execnet
+
+ or
+
+ pip install execnet
+
+ or
+
+ hg clone https://hpk42 at bitbucket.org/hpk42/execnet/
+ python setup.py install # or add checkout path to PYTHONPATH directly
+
+Next checkout the basic api and examples:
+
+.. toctree::
+ :maxdepth: 1
+
+ basics
+ examples
+ changelog
+
+.. _`easy_install`: http://peak.telecommunity.com/DevCenter/EasyInstall
+.. _`bitbucket repository`: http://bitbucket.org/hpk42/execnet/
+.. _`execnet mercurial repository`: http://bitbucket.org/hpk42/execnet/
+.. _`pypi release`: http://pypi.python.org/pypi/execnet
+
+
+
+
--- /dev/null
+++ b/doc/contact.txt
@@ -0,0 +1,17 @@
+Contact channels
+------------------------
+
+If you have interest, questions, issues or suggestions you
+are welcome to:
+
+* join `execnet-dev`_ for general discussions
+* join `execnet-commit`_ to be notified of changes
+* clone the `bitbucket repository`_ and submit patches
+* hang out on the irc.freenode.net #pylib channel
+* follow the `tetamap blog`_ or `Holger's twitter presence`_.
+
+.. _`Holger's twitter presence`: http://twitter.com/hpk42
+.. _`tetamap blog`: http://tetamap.wordpress.com
+.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
+.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
+.. _`bitbucket repository`: http://bitbucket.org/hpk42/execnet
From commits-noreply at bitbucket.org Sat Dec 5 12:55:52 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 11:55:52 +0000 (UTC)
Subject: [execnet-commit] execnet commit 318325f5bc20: fix dist-testing setup
Message-ID: <20091205115552.BA8337EF12@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259938630 -3600
# Node ID 318325f5bc20e02a1e45bdfdd538743a0662d7cd
# Parent bdfe9a4a229e7f0bf6327997ae7bc593b87e6959
fix dist-testing setup
--- /dev/null
+++ b/conftest.py
@@ -0,0 +1,90 @@
+import execnet
+import py
+
+collect_ignore = ['build', 'doc/_build']
+
+rsyncdirs = ['conftest.py', 'execnet', 'testing', 'doc']
+
+pytest_plugins = ['pytester', 'doctest']
+# configuration information for tests
+def pytest_addoption(parser):
+ group = parser.getgroup("pylib", "py lib testing options")
+ group.addoption('--gx',
+ action="append", dest="gspecs", default=None,
+ help=("add a global test environment, XSpec-syntax. "))
+
+def pytest_funcarg__specssh(request):
+ return getspecssh(request.config)
+def pytest_funcarg__specsocket(request):
+ return getsocketspec(request.config)
+def getgspecs(config=None):
+ if config is None:
+ config = py.test.config
+ return [execnet.XSpec(spec)
+ for spec in config.getvalueorskip("gspecs")]
+
+def getspecssh(config=None):
+ xspecs = getgspecs(config)
+ for spec in xspecs:
+ if spec.ssh:
+ if not py.path.local.sysfind("ssh"):
+ py.test.skip("command not found: ssh")
+ return spec
+ py.test.skip("need '--gx ssh=...'")
+
+def getsocketspec(config=None):
+ xspecs = getgspecs(config)
+ for spec in xspecs:
+ if spec.socket:
+ return spec
+ py.test.skip("need '--gx socket=...'")
+
+def pytest_generate_tests(metafunc):
+ if 'gw' in metafunc.funcargnames:
+ assert 'anypython' not in metafunc.funcargnames, "need combine?"
+ if hasattr(metafunc.function, 'gwtypes'):
+ gwtypes = metafunc.function.gwtypes
+ elif hasattr(metafunc.cls, 'gwtype'):
+ gwtypes = [metafunc.cls.gwtype]
+ else:
+ gwtypes = ['popen', 'socket', 'ssh']
+ for gwtype in gwtypes:
+ metafunc.addcall(id=gwtype, param=gwtype)
+ elif 'anypython' in metafunc.funcargnames:
+ for name in ('python3.1', 'python2.4', 'python2.5', 'python2.6',
+ 'pypy-c', 'jython'):
+ metafunc.addcall(id=name, param=name)
+
+def pytest_funcarg__anypython(request):
+ name = request.param
+ executable = py.path.local.sysfind(name)
+ if executable is None:
+ py.test.skip("no %s found" % (name,))
+ return executable
+
+def pytest_funcarg__gw(request):
+ scope = "session"
+ group = request.cached_setup(
+ setup=execnet.Group,
+ teardown=lambda group: group.terminate(timeout=1),
+ extrakey="testgroup",
+ scope=scope,
+ )
+ try:
+ return group[request.param]
+ except KeyError:
+ if request.param == "popen":
+ gw = group.makegateway("popen//id=popen")
+ elif request.param == "socket":
+ pname = 'sproxy1'
+ if pname not in group:
+ proxygw = group.makegateway("popen//id=%s" % pname)
+ #assert group['proxygw'].remote_status().receiving
+ gw = group.makegateway("socket//id=socket//installvia=%s" % pname)
+ gw.proxygw = proxygw
+ assert pname in group
+
+ elif request.param == "ssh":
+ sshhost = request.getfuncargvalue('specssh').ssh
+ gw = group.makegateway("ssh=%s//id=ssh" %(sshhost,))
+ return gw
--- a/testing/conftest.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import execnet
-import py
-
-rsyncdirs = ['../execnet', '.']
-
-pytest_plugins = ['pytester', 'doctest']
-# configuration information for tests
-def pytest_addoption(parser):
- group = parser.getgroup("pylib", "py lib testing options")
- group.addoption('--gx',
- action="append", dest="gspecs", default=None,
- help=("add a global test environment, XSpec-syntax. "))
-
-def pytest_funcarg__specssh(request):
- return getspecssh(request.config)
-def pytest_funcarg__specsocket(request):
- return getsocketspec(request.config)
-def getgspecs(config=None):
- if config is None:
- config = py.test.config
- return [execnet.XSpec(spec)
- for spec in config.getvalueorskip("gspecs")]
-
-def getspecssh(config=None):
- xspecs = getgspecs(config)
- for spec in xspecs:
- if spec.ssh:
- if not py.path.local.sysfind("ssh"):
- py.test.skip("command not found: ssh")
- return spec
- py.test.skip("need '--gx ssh=...'")
-
-def getsocketspec(config=None):
- xspecs = getgspecs(config)
- for spec in xspecs:
- if spec.socket:
- return spec
- py.test.skip("need '--gx socket=...'")
-
-def pytest_generate_tests(metafunc):
- if 'gw' in metafunc.funcargnames:
- assert 'anypython' not in metafunc.funcargnames, "need combine?"
- if hasattr(metafunc.function, 'gwtypes'):
- gwtypes = metafunc.function.gwtypes
- elif hasattr(metafunc.cls, 'gwtype'):
- gwtypes = [metafunc.cls.gwtype]
- else:
- gwtypes = ['popen', 'socket', 'ssh']
- for gwtype in gwtypes:
- metafunc.addcall(id=gwtype, param=gwtype)
- elif 'anypython' in metafunc.funcargnames:
- for name in ('python3.1', 'python2.4', 'python2.5', 'python2.6',
- 'pypy-c', 'jython'):
- metafunc.addcall(id=name, param=name)
-
-def pytest_funcarg__anypython(request):
- name = request.param
- executable = py.path.local.sysfind(name)
- if executable is None:
- py.test.skip("no %s found" % (name,))
- return executable
-
-def pytest_funcarg__gw(request):
- scope = "session"
- group = request.cached_setup(
- setup=execnet.Group,
- teardown=lambda group: group.terminate(timeout=1),
- extrakey="testgroup",
- scope=scope,
- )
- try:
- return group[request.param]
- except KeyError:
- if request.param == "popen":
- gw = group.makegateway("popen//id=popen")
- elif request.param == "socket":
- pname = 'sproxy1'
- if pname not in group:
- proxygw = group.makegateway("popen//id=%s" % pname)
- #assert group['proxygw'].remote_status().receiving
- gw = group.makegateway("socket//id=socket//installvia=%s" % pname)
- gw.proxygw = proxygw
- assert pname in group
-
- elif request.param == "ssh":
- sshhost = request.getfuncargvalue('specssh').ssh
- gw = group.makegateway("ssh=%s//id=ssh" %(sshhost,))
- return gw
--- /dev/null
+++ b/doc/__init__.py
@@ -0,0 +1,1 @@
+#
From commits-noreply at bitbucket.org Sat Dec 5 12:55:54 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 11:55:54 +0000 (UTC)
Subject: [execnet-commit] execnet commit 874bca2734d7: try to find python
versions on windows as well
Message-ID: <20091205115554.3D8DF7EF24@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259948127 -3600
# Node ID 874bca2734d712fb10c09a90c3e4cc438445b871
# Parent 318325f5bc20e02a1e45bdfdd538743a0662d7cd
try to find python versions on windows as well
--- a/conftest.py
+++ b/conftest.py
@@ -1,10 +1,18 @@
import execnet
import py
+import sys
collect_ignore = ['build', 'doc/_build']
rsyncdirs = ['conftest.py', 'execnet', 'testing', 'doc']
+winpymap = {
+ 'python2.6': r'C:\Python26\python.exe',
+ 'python2.5': r'C:\Python25\python.exe',
+ 'python2.4': r'C:\Python24\python.exe',
+ 'python3.1': r'C:\Python31\python.exe',
+}
+
pytest_plugins = ['pytester', 'doctest']
# configuration information for tests
def pytest_addoption(parser):
@@ -59,6 +67,12 @@ def pytest_funcarg__anypython(request):
name = request.param
executable = py.path.local.sysfind(name)
if executable is None:
+ if sys.platform == "win32":
+ executable = winpymap.get(name, None)
+ if executable:
+ executable = py.path.local(executable)
+ if executable.check():
+ return executable
py.test.skip("no %s found" % (name,))
return executable
From commits-noreply at bitbucket.org Sat Dec 5 12:55:55 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 11:55:55 +0000 (UTC)
Subject: [execnet-commit] execnet commit b9ae9afeb79d: kill processes after
timeout
Message-ID: <20091205115555.E51F27EF29@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260014112 -3600
# Node ID b9ae9afeb79d210e10b25558dc32c5fafff195b0
# Parent 874bca2734d712fb10c09a90c3e4cc438445b871
kill processes after timeout
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -138,6 +138,10 @@ class PopenCmdGateway(Gateway):
self._popen = p = Popen(args, stdin=PIPE, stdout=PIPE)
io = Popen2IO(p.stdin, p.stdout)
super(PopenCmdGateway, self).__init__(io=io, id=id)
+ # fix for jython 2.5.1
+ if p.pid is None:
+ p.pid = self.remote_exec(
+ "import os; channel.send(os.getpid())").receive()
popen_bootstrapline = "import sys ; exec(eval(sys.stdin.readline()))"
class PopenGateway(PopenCmdGateway):
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -4,11 +4,11 @@ Managing Gateway Groups and interactions
(c) 2008-2009, Holger Krekel and others
"""
-import sys, weakref, atexit, time
+import os, sys, weakref, atexit
import execnet
from execnet import XSpec
from execnet import gateway
-from execnet.gateway_base import queue, reraise, trace
+from execnet.gateway_base import queue, reraise, trace, TimeoutError
NO_ENDMARKER_WANTED = object()
@@ -115,31 +115,43 @@ class Group:
def _cleanup_atexit(self):
trace("=== atexit cleanup %r ===" %(self,))
- self.terminate(timeout=2.0)
+ self.terminate(timeout=1.0)
def terminate(self, timeout=None):
- """ trigger exit of member gateways and and wait for completion
- of receiver-threads of previously exited member gateways and any
- started subprocesses. If after timeout seconds (None for no-timeout)
- waiting does not finish a TimeoutError will be raised.
+ """ trigger exit of member gateways and wait for termination
+ of member gateways and associated subprocesses. After waiting
+ timeout seconds an attempt to kill local sub processes of popen-
+ and ssh-gateways is started. Timeout defaults to None meaning
+ open-ended waiting and no kill attempts.
"""
- endtime = timeout and (time.time() + timeout) or None
for gw in self:
- gw.exit()
+ gw.exit()
def join_receiver_and_wait_for_subprocesses():
for gw in self._gateways_to_join:
gw.join()
while self._gateways_to_join:
- gw = self._gateways_to_join.pop(0)
+ gw = self._gateways_to_join[0]
if hasattr(gw, '_popen'):
gw._popen.wait()
- self._gateways_to_join[:] = []
+ del self._gateways_to_join[0]
from execnet.threadpool import WorkerPool
pool = WorkerPool(1)
reply = pool.dispatch(join_receiver_and_wait_for_subprocesses)
- reply.get(timeout=timeout)
+ try:
+ reply.get(timeout=timeout)
+ except IOError:
+ trace("Gateways did not come down after timeout: %r"
+ %(self._gateways_to_join))
+ while self._gateways_to_join:
+ gw = self._gateways_to_join.pop(0)
+ popen = getattr(gw, '_popen', None)
+ if popen:
+ killpopen(popen)
def remote_exec(self, source):
+ """ remote_exec source on all member gateways and return
+ MultiChannel connecting to all sub processes.
+ """
channels = []
for gw in list(self._activegateways):
channels.append(gw.remote_exec(source))
@@ -231,3 +243,34 @@ def APIWARN(version, msg, stacklevel=3):
import warnings
Warn = DeprecationWarning("(since version %s) %s" %(version, msg))
warnings.warn(Warn, stacklevel=stacklevel)
+
+def killpopen(popen):
+ try:
+ if hasattr(popen, 'kill'):
+ popen.kill()
+ else:
+ killpid(popen.pid)
+ except EnvironmentError:
+ sys.stderr.write("ERROR killing: %s\n" %(sys.exc_info()[1]))
+ sys.stderr.flush()
+
+def killpid(pid):
+ if hasattr(os, 'kill'):
+ os.kill(pid, 15)
+ elif sys.platform == "win32":
+ try:
+ import ctypes
+ except ImportError:
+ # T: treekill, F: Force
+ cmd = ("taskkill /T /F /PID %d" %(pid)).split()
+ ret = subprocess.call(cmd)
+ if ret != 0:
+ raise EnvironmentError("taskkill returned %r" %(ret,))
+ else:
+ PROCESS_TERMINATE = 1
+ handle = ctypes.windll.kernel32.OpenProcess(
+ PROCESS_TERMINATE, False, pid)
+ ctypes.windll.kernel32.TerminateProcess(handle, -1)
+ ctypes.windll.kernel32.CloseHandle(handle)
+ else:
+ raise EnvironmmentError("no method to kill %s" %(pid,))
--- a/testing/test_termination.py
+++ b/testing/test_termination.py
@@ -74,38 +74,23 @@ def test_close_initiating_remote_no_erro
stdout, stderr = popen.communicate()
assert not stderr
-def test_double_call_to_terminate(testdir, anypython):
- triggerfile = testdir.tmpdir.join("trigger")
+def test_terminate_implicit_does_trykill(testdir, anypython, capfd):
p = testdir.makepyfile("""
import sys
sys.path.insert(0, %r)
- triggerfile = %r
import execnet
- gw = execnet.makegateway("popen")
- gw.remote_exec('''
- import os, time
- while 1:
- if os.path.exists(%%r):
- break
- time.sleep(0.2)
- ''' %% triggerfile)
- ok = 0
- try:
- execnet.default_group.terminate(1.0)
- except IOError:
- try:
- execnet.default_group.terminate(0.1)
- except IOError:
- f = open(triggerfile, 'w')
- f.write("")
- f.close()
- execnet.default_group.terminate(5.0)
- ok = 1
- if not ok:
- sys.stderr.write("no-timeout!\\n")
- """ % (str(execnetdir), str(triggerfile)))
- popen = subprocess.Popen([str(anypython), str(p)],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
- stdout, stderr = popen.communicate()
- assert not stderr
-
+ group = execnet.Group()
+ gw = group.makegateway("popen")
+ ch = gw.remote_exec("import time ; channel.send(1) ; time.sleep(100)")
+ ch.receive() # remote execution started
+ sys.stdout.write("1\\n")
+ sys.stdout.flush()
+ # use process at-exit group.terminate call
+ """ % str(execnetdir))
+ popen = subprocess.Popen([str(anypython), str(p)], stdout=subprocess.PIPE)
+ # sync with start-up
+ line = popen.stdout.readline()
+ reply = WorkerPool(1).dispatch(popen.communicate)
+ reply.get(timeout=10)
+ out, err = capfd.readouterr()
+ assert not err or "Killed" in err
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,8 +10,9 @@ 1.0.1 (pending)
popen-gateway startup can be substantially faster (>30%)
- refine internal gateway exit/termination procedure
- and introduce group.terminate(timeout) for timely
- termination.
+ and introduce group.terminate(timeout) which will
+ attempt to kill all subprocesses that did not terminate
+ within time.
- EOFError on channel.receive/waitclose if the other
side unexpectedly went away. When a gateway exits
From commits-noreply at bitbucket.org Sat Dec 5 14:39:16 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 13:39:16 +0000 (UTC)
Subject: [execnet-commit] execnet commit 80baab4140de: refine and rework
documentation and layout for 1.0.1 relase
Message-ID: <20091205133916.3E2037EF0F@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1259582636 -3600
# Node ID 80baab4140de4e421f7c872ea962291d99fd44dd
# Parent b9ae9afeb79d210e10b25558dc32c5fafff195b0
refine and rework documentation and layout for 1.0.1 relase
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -1,5 +1,5 @@
==============================================================================
-execnet examples
+examples
==============================================================================
.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
--- a/doc/install.txt
+++ b/doc/install.txt
@@ -2,7 +2,7 @@
Installation
====================
-Install a public `pypi release`_ via `easy_install`_ or ``pip``::
+Install a public `pypi release`_ via `easy_install`_ or pip_::
easy_install -U execnet
@@ -20,11 +20,12 @@ Next checkout the basic api and examples
.. toctree::
:maxdepth: 1
+ examples
basics
- examples
changelog
.. _`easy_install`: http://peak.telecommunity.com/DevCenter/EasyInstall
+.. _pip: http://pypi.python.org/pypi/pip
.. _`bitbucket repository`: http://bitbucket.org/hpk42/execnet/
.. _`execnet mercurial repository`: http://bitbucket.org/hpk42/execnet/
.. _`pypi release`: http://pypi.python.org/pypi/execnet
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,4 @@
-1.0.1 (pending)
+1.0.1
--------------------------------
- new method: gateway.hasreceiver() returns True
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -1,20 +1,26 @@
-execnet: connect your execution environments
+Welcome to rapid python deployment
========================================================
.. image:: _static/pythonring.png
:align: right
-Execnet allows to ad-hoc connect to Python interpreters across version, platform and network barriers. It provides a minimal, fast and robust API for the following uses:
+Python_ is a mature dynamic language whose interpreters can interact with
+all major computing platforms today. Execnet provides carefully tested
+means to ad-hoc connect to Python interpreters across version, platform
+and network barriers. It provides a minimal, fast and robust API for
+the following uses:
* distribute tasks to multiple CPUs
-* deploy hybrid applications
-* manage local and remote execution environments
+* write and deploy hybrid multi-process applications
+* write scripts to interact a bunch of exec environments
+
+.. _Python: http://www.python.org
Features
------------------
-* zero-install bootstrapping: no remote installation required!
+* zero-install bootstrapping: no manual remote installation!
* flexible communication: send/receive as well as
callback/queue mechanisms supported
@@ -28,15 +34,18 @@ Features
* fully interoperable between Windows and Unix-ish systems.
-Known users
+* many tested :doc:`examples`
+
+Known uses
-------------------
* `py.test`_ uses it for its `distributed testing`_ mechanism.
* Jacob Perkins uses it for his `Distributed NTLK with execnet`_
- project.
+ project to launch computation processes through ssh.
* Ronny Pfannschmidt uses it for his `anyvc`_ VCS-abstraction project
+ to bridge the Python2/Python3 version gap.
* sysadmins and developers are using it for ad-hoc custom scripting
@@ -48,7 +57,7 @@ Known users
Project status
--------------------------
-The current 1.0 series aims at `basic API`_ stabilization, improved tracing and robust termination.
+The current 1.0 series aims at :doc:`basic API ` stabilization, improved tracing and robust termination.
The 1.1 series will target setting up permanent networks and offering unix-shell-like capabilities to spawn processes and applications.
@@ -60,3 +69,12 @@ are MIT-licensed.
.. _`basic API`: basics.html
.. _`actively developed`: http://bitbucket.org/hpk42/execnet/changesets
.. _`Holger Krekel`: http://twitter.com/hpk42
+
+.. toctree::
+ :hidden:
+
+ support
+ implnotes
+ install
+ rel-1.0.0
+ docindex
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -54,21 +54,24 @@ you actually use the ``execnet.default_g
>>> gw in execnet.default_group
True
-Timely termination
--------------------------
+Robust Termination of ssh/popen processes
+-----------------------------------------------
Use ``group.terminate(timeout)`` if you want to terminate
-member gateways and wait a limited time for proper termination
-of all execution and IO activity::
+member gateways and ensure that no local sub processes remain
+you can specify a ``timeout`` after which an attempt at killing
+the related process is made::
>>> import execnet
- >>> mygroup = execnet.Group()
- >>> gw = mygroup.makegateway("popen")
+ >>> group = execnet.Group()
+ >>> gw = group.makegateway("popen//id=sleeper")
>>> ch = gw.remote_exec("import time ; time.sleep(2.0)")
- >>> try:
- ... mygroup.terminate(timeout=1.0)
- ... except IOError:
- ... pass # subprocess-execution still sleeping
- ... else:
- ... print ("fail")
- >>>
+ >>> group
+
+ >>> group.terminate(timeout=1.0)
+ >>> group
+
+
+execnet aims to provide totally robust termination so if
+you have termination issues please report them via :doc:`contact <../contact>`.
+thanks!
--- a/doc/_templates/layout.html
+++ b/doc/_templates/layout.html
@@ -1,16 +1,21 @@
{% extends "!layout.html" %}
{% block rootrellink %}
- home |
- basic API |
- examples |
- contact | docindex »
{% endblock %}
{% block header %}
{% endblock %}
--- a/doc/example/test_remotecmd.txt
+++ b/doc/example/test_remotecmd.txt
@@ -1,4 +1,3 @@
-.. _command:
A simple command pattern
--------------------------------------------------------------
--- /dev/null
+++ b/doc/support.txt
@@ -0,0 +1,19 @@
+Contact and Support channels
+------------------------------
+
+If you have interest, questions, issues or suggestions you
+are welcome to:
+
+* join `execnet-dev`_ for general discussions
+* join `execnet-commit`_ to be notified of changes
+* clone the `bitbucket repository`_ and submit patches
+* hang out on the irc.freenode.net #pylib channel
+* follow the `tetamap blog`_ or `Holger's twitter presence`_.
+* contact merlinux_ if you want to buy teaching or other support.
+
+.. _`Holger's twitter presence`: http://twitter.com/hpk42
+.. _merlinux: http://merlinux.eu
+.. _`tetamap blog`: http://tetamap.wordpress.com
+.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
+.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
+.. _`bitbucket repository`: http://bitbucket.org/hpk42/execnet
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -1,12 +1,12 @@
==============================================================================
-execnet API in a nutshell
+API in a nutshell
==============================================================================
-execnet ad-hoc instantiates **gateways** to Python
-interpreter processes though which you can **remote execute
-code** and exchange basic python objects through **channels**.
-All gateways are created as part of a **group** which is responsible
-for managing membership and **proper termination**.
+execnet ad-hoc instantiates local and remote Python interpreters.
+Each interpreter is accessible through a **Gateway** which manages
+code and data communication. **Channels** allow to exchange
+data between the local and the remote end. **Groups**
+help to manage creation and termination of sub interpreters.
.. image:: _static/basic1.png
@@ -106,14 +106,13 @@ two asynchronously running programs.
Grouped Gateways and robust termination
===============================================
-.. _`group examples`: examples.html#group
.. currentmodule:: execnet.multi
All created gateway instances are part of a group. If you
call ``execnet.makegateway`` it actually is forwarded to
the ``execnet.default_group``. Group objects are container
-objects (see `group examples`_) and manage the final
-termination procedure:
+objects (see :doc:`group examples >`_)
+and manage the final termination procedure:
.. automethod:: Group.terminate(timeout=None)
--- a/doc/example/test_cwd.txt
+++ b/doc/example/test_cwd.txt
@@ -26,7 +26,5 @@ Here is an self-contained example for re
True
If you'd like to avoid inlining source strings take a look
-at the channelexec_ and command_ example.
-
-.. _channelexec: examples.html#channelexec
-.. _command: examples.html#command
+at the :doc:`channelexec `
+and the :doc:`command pattern` examples.
--- a/doc/contact.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-Contact channels
-------------------------
-
-If you have interest, questions, issues or suggestions you
-are welcome to:
-
-* join `execnet-dev`_ for general discussions
-* join `execnet-commit`_ to be notified of changes
-* clone the `bitbucket repository`_ and submit patches
-* hang out on the irc.freenode.net #pylib channel
-* follow the `tetamap blog`_ or `Holger's twitter presence`_.
-
-.. _`Holger's twitter presence`: http://twitter.com/hpk42
-.. _`tetamap blog`: http://tetamap.wordpress.com
-.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
-.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
-.. _`bitbucket repository`: http://bitbucket.org/hpk42/execnet
From commits-noreply at bitbucket.org Sat Dec 5 17:10:11 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 16:10:11 +0000 (UTC)
Subject: [execnet-commit] execnet commit 9ec10766044b: refine
group-membership handling: index by integer, don't use weakref
Message-ID: <20091205161011.39B4C7EF0F@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260027855 -3600
# Node ID 9ec10766044b9b74a4e0712f0abc16829d4e4ae2
# Parent e07cbeaf8e3bca3be4ee1e966e193522a0409f42
refine group-membership handling: index by integer, don't use weakref
because gateways do not work with implicit GC-related termination anyway
(channels do, however).
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -35,11 +35,10 @@ class Gateway(gateway_base.BaseGateway):
group.terminate() is called.
"""
self._trace("gateway.exit() called")
- try:
- self._group._unregister(self)
- except KeyError:
+ if self not in self._group:
self._trace("gateway already unregistered with group")
return
+ self._group._unregister(self)
self._trace("--> sending GATEWAY_TERMINATE")
try:
self._send(Message.GATEWAY_TERMINATE(0, ''))
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -4,7 +4,7 @@ Managing Gateway Groups and interactions
(c) 2008-2009, Holger Krekel and others
"""
-import os, sys, weakref, atexit
+import os, sys, atexit
import execnet
from execnet import XSpec
from execnet import gateway
@@ -17,33 +17,37 @@ class Group:
def __init__(self, xspecs=()):
""" initialize group and make gateways as specified. """
# Gateways may evolve to become GC-collectable
- self._activegateways = weakref.WeakKeyDictionary()
- self._id2gateway = weakref.WeakValueDictionary()
- self._autoidcounter = 1
+ self._gateways = []
+ self._autoidcounter = 0
self._gateways_to_join = []
for xspec in xspecs:
self.makegateway(xspec)
atexit.register(self._cleanup_atexit)
def __repr__(self):
- keys = list(self._id2gateway)
- keys.sort()
- return "" %(keys,)
+ idgateways = [gw.id for gw in self]
+ return "" %(idgateways)
def __getitem__(self, key):
- return self._id2gateway[key]
+ if isinstance(key, int):
+ return self._gateways[key]
+ for gw in self._gateways:
+ if gw == key or gw.id == key:
+ return gw
+ raise KeyError(key)
def __contains__(self, key):
- return key in self._id2gateway or key in self._activegateways
+ try:
+ self[key]
+ return True
+ except KeyError:
+ return False
def __len__(self):
- return len(self._activegateways)
+ return len(self._gateways)
def __iter__(self):
- l = list(self._id2gateway.items())
- l.sort()
- for id, gw in l:
- yield gw
+ return iter(list(self._gateways))
def makegateway(self, spec):
""" create and configure a gateway to a Python interpreter
@@ -67,7 +71,7 @@ class Group:
"socket: specifying python executables not yet supported")
gateway_id = spec.installvia
if gateway_id:
- viagw = self._id2gateway[gateway_id]
+ viagw = self[gateway_id]
gw = gateway.SocketGateway.new_remote(viagw, id=id)
else:
host, port = spec.socket.split(":")
@@ -94,23 +98,21 @@ class Group:
def _allocate_id(self, id=None):
if id is None:
- id = str(self._autoidcounter)
+ id = "gw" + str(self._autoidcounter)
self._autoidcounter += 1
- assert id not in self._id2gateway
+ if id in self:
+ raise ValueError("already have member gateway with id %r" %(id,))
return id
def _register(self, gateway):
assert not hasattr(gateway, '_group')
assert gateway.id
- assert id not in self._id2gateway
- assert gateway not in self._activegateways
- self._activegateways[gateway] = True
- self._id2gateway[gateway.id] = gateway
+ assert id not in self
+ self._gateways.append(gateway)
gateway._group = self
def _unregister(self, gateway):
- del self._id2gateway[gateway.id]
- del self._activegateways[gateway]
+ self._gateways.remove(gateway)
self._gateways_to_join.append(gateway)
def _cleanup_atexit(self):
@@ -153,7 +155,7 @@ class Group:
MultiChannel connecting to all sub processes.
"""
channels = []
- for gw in list(self._activegateways):
+ for gw in self:
channels.append(gw.remote_exec(source))
return MultiChannel(channels)
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -102,8 +102,10 @@ class TestGroup:
group = Group()
gw = group.makegateway("popen")
assert list(group) == [gw]
+ assert group[0] == gw
+ assert len(group) == 1
group._cleanup_atexit()
- assert not group._activegateways
+ assert not group._gateways
def test_group_ordering_and_termination(self):
group = Group()
@@ -113,8 +115,10 @@ class TestGroup:
gwlist = list(group)
assert len(gwlist) == 3
idlist = [x.id for x in gwlist]
- assert idlist == list('235')
+ assert idlist == list('325')
+ print group
group.terminate()
+ print group
assert not group
assert repr(group) == ""
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,8 @@ 1.0.1
- new: execnet.MultiChannel provides basic iteration/contain interface
+- new: execnet.Group can be indexed by integer
+
- have popen-gateways use imports instead of source-strings,
also improves debugging/tracebacks, as a side effect
popen-gateway startup can be substantially faster (>30%)
From commits-noreply at bitbucket.org Sat Dec 5 17:10:12 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 16:10:12 +0000 (UTC)
Subject: [execnet-commit] execnet commit 835e81022d0e: group.makegateway()
uses group.default_spec if no spec is given
Message-ID: <20091205161012.DEE987EF10@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260029386 -3600
# Node ID 835e81022d0e495086b2b7be3edc007eca636f51
# Parent 9ec10766044b9b74a4e0712f0abc16829d4e4ae2
group.makegateway() uses group.default_spec if no spec is given
and the execnet.default_group uses ``popen`` as a default spec.
This makes a simple
execnet.makegateway()
return a subprocess/popen-gateway.
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -14,6 +14,7 @@ NO_ENDMARKER_WANTED = object()
class Group:
""" Gateway Groups. """
+ defaultspec = XSpec("popen")
def __init__(self, xspecs=()):
""" initialize group and make gateways as specified. """
# Gateways may evolve to become GC-collectable
@@ -49,15 +50,26 @@ class Group:
def __iter__(self):
return iter(list(self._gateways))
- def makegateway(self, spec):
- """ create and configure a gateway to a Python interpreter
- specified by a 'execution specification' string.
- The format of the string generally is::
+ def makegateway(self, spec=None):
+ """ create and configure a gateway to a Python interpreter.
+
+ The ``spec`` string encodes the target gateway type
+ and configuration information. The general format is::
key1=value1//key2=value2//...
If you leave out the ``=value`` part a True value is assumed.
+ Valid types: ``popen``, ``ssh=hostname``, ``socket=host:port``.
+ Valid configuration::
+ id= specifies the gateway id
+ python= specifies which python interpreter to execute
+ chdir= specifies to which directory to change
+ nice= specifies process priority of new process
+
+ If no spec is given, self.defaultspec is used.
"""
+ if not spec:
+ spec = self.defaultspec
if not isinstance(spec, XSpec):
spec = XSpec(spec)
id = self._allocate_id(spec.id)
--- a/testing/test_xspec.py
+++ b/testing/test_xspec.py
@@ -60,8 +60,9 @@ class TestMakegateway:
def test_no_type(self):
py.test.raises(ValueError, "execnet.makegateway('hello')")
- def test_popen(self):
- gw = execnet.makegateway("popen")
+ def test_popen_default(self):
+ gw = execnet.makegateway("")
+ assert gw.spec.popen
assert gw.spec.python == None
rinfo = gw._rinfo()
assert rinfo.executable == py.std.sys.executable
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -98,6 +98,11 @@ class TestGroup:
assert len(exitlist) == 1
assert len(joinlist) == 1
+ def test_group_default_spec(self):
+ group = Group()
+ group.defaultspec = "not-existing-type"
+ py.test.raises(ValueError, group.makegateway)
+
def test_group_PopenGateway(self):
group = Group()
gw = group.makegateway("popen")
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,9 @@ 1.0.1
- new: execnet.Group can be indexed by integer
+- new: group.makegateway() uses group.default_spec if no spec is given
+ and the execnet.default_group uses ``popen`` as a default spec.
+
- have popen-gateways use imports instead of source-strings,
also improves debugging/tracebacks, as a side effect
popen-gateway startup can be substantially faster (>30%)
From commits-noreply at bitbucket.org Sat Dec 5 17:10:14 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 16:10:14 +0000 (UTC)
Subject: [execnet-commit] execnet commit e07cbeaf8e3b: introduce basic
container API to multichannels
Message-ID: <20091205161014.7DC817EF11@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260025425 -3600
# Node ID e07cbeaf8e3bca3be4ee1e966e193522a0409f42
# Parent 80baab4140de4e421f7c872ea962291d99fd44dd
introduce basic container API to multichannels
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -161,6 +161,18 @@ class MultiChannel:
def __init__(self, channels):
self._channels = channels
+ def __len__(self):
+ return len(self._channels)
+
+ def __iter__(self):
+ return iter(self._channels)
+
+ def __getitem__(self, key):
+ return self._channels[key]
+
+ def __contains__(self, chan):
+ return chan in self._channels
+
def send_each(self, item):
for ch in self._channels:
ch.send(item)
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -4,8 +4,22 @@
import execnet
import py
+from execnet.gateway_base import Channel
class TestMultiChannelAndGateway:
+ def test_multichannel_container_basics(self):
+ mch = execnet.MultiChannel([Channel(None, i) for i in range(3)])
+ assert len(mch) == 3
+ channels = list(mch)
+ assert len(channels) == 3
+ # ordering
+ for i in range(3):
+ assert channels[i].id == i
+ assert channels[i] == mch[i]
+ assert channels[0] in mch
+ assert channels[1] in mch
+ assert channels[2] in mch
+
def test_multichannel_receive_each(self):
class pseudochannel:
def receive(self):
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,8 @@ 1.0.1
if the gateway is still receive-active. remote_status
now only carries information about remote execution status.
+- new: execnet.MultiChannel provides basic iteration/contain interface
+
- have popen-gateways use imports instead of source-strings,
also improves debugging/tracebacks, as a side effect
popen-gateway startup can be substantially faster (>30%)
From commits-noreply at bitbucket.org Sat Dec 5 18:33:48 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 17:33:48 +0000 (UTC)
Subject: [execnet-commit] execnet commit 2233f21d1d91: revamp and add docs
Message-ID: <20091205173348.1CB6C7EF10@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260034398 -3600
# Node ID 2233f21d1d91e0d52ba6c9fe4896b02285d85aa7
# Parent 835e81022d0e495086b2b7be3edc007eca636f51
revamp and add docs
--- a/doc/example/test_channelexec.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-.. _channelexec:
-
-remote execute modules using their __name__
---------------------------------------------------------------
-
-You can pass a module object to ``remote_exec`` in which case
-its source code will be sent. No dependencies will be transferred
-so the module must be self-contained or only use modules that are
-installed on the "other" side. Module code can detect if it is
-running in a remote_exec situation by checking for the special
-``__name__`` attribute.
-
-.. include:: remote1.py
- :literal:
-
-You can now remote-execute the module like this::
-
- >>> import execnet, remote1
- >>> gw = execnet.PopenGateway()
- >>> ch = gw.remote_exec(remote1)
- >>> print (ch.receive())
- initialization complete
-
-which will print the 'initialization complete' string.
--- /dev/null
+++ b/doc/example/hybridpython.txt
@@ -0,0 +1,95 @@
+Connecting different Python interpreters
+==========================================
+
+Connect to Python2/Numpy from Python3
+----------------------------------------
+
+Here we run a Python3 interpreter to connect to a Python2.6 interpreter
+that has numpy installed. We send items to be added to an array and
+receive back the remote "repr" of the array::
+
+ import execnet
+ gw = execnet.makegateway("popen//python=python2.6")
+ channel = gw.remote_exec("""
+ import numpy
+ array = numpy.array([1,2,3])
+ while 1:
+ x = channel.receive()
+ if x is None:
+ break
+ array = numpy.append(array, x)
+ channel.send(repr(array))
+ """)
+ for x in range(10):
+ channel.send(x)
+ channel.send(None)
+ print (channel.receive())
+
+will print on the CPython3.1 side::
+
+ array([1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+
+A more refined real-life example of python3/python2 interaction
+is the anyvc_ project which uses version-control bindings in
+a Python2 subprocess in order to offer Python3-based library
+functionality.
+
+.. _anyvc: http://bitbucket.org/RonnyPfannschmidt/anyvc/overview/
+
+Work with Java objects from CPython
+----------------------------------------
+
+Use your CPython interpreter to connect to a `Jython 2.5.1`_ interpreter
+and work with Java types::
+
+ import execnet
+ gw = execnet.makegateway("popen//python=jython")
+ channel = gw.remote_exec("""
+ from java.util import Vector
+ v = Vector()
+ v.add('aaa')
+ v.add('bbb')
+ for val in v:
+ channel.send(val)
+ """)
+
+ for item in channel:
+ print (item)
+
+will print on the CPython side::
+
+ aaa
+ bbb
+
+.. _`Jython 2.5.1`: http://www.jython.org
+
+Work with C# objects from CPython
+----------------------------------------
+
+(Experimental) use your CPython interpreter to connect to a IronPython_ interpreter
+which can work with C# classes. Here is an example for instantiating
+a CLR Array instance and sending back its representation::
+
+ import execnet
+ gw = execnet.makegateway("popen//python=ipy")
+
+ channel = gw.remote_exec("""
+ import clr
+ clr.AddReference("System")
+ from System import Array
+ array = Array[float]([1,2])
+ channel.send(str(array))
+ """)
+ print (channel.receive())
+
+using Mono 2.0 and IronPython-1.1 this will print on the CPython side::
+
+ System.Double[](1.0, 2.0)
+
+.. note::
+ Using IronPython needs more testing, likely newer versions
+ will work better. please feedback if you have information.
+
+.. _IronPython: http://www.IronPython.org
+
+
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,8 @@
1.0.1
--------------------------------
+- revamp and better structure documentation
+
- new method: gateway.hasreceiver() returns True
if the gateway is still receive-active. remote_status
now only carries information about remote execution status.
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -1,36 +1,37 @@
-Welcome to rapid python deployment
+Welcome to rapid Python deployment
========================================================
.. image:: _static/pythonring.png
:align: right
Python_ is a mature dynamic language whose interpreters can interact with
-all major computing platforms today. Execnet provides carefully tested
-means to ad-hoc connect to Python interpreters across version, platform
-and network barriers. It provides a minimal, fast and robust API for
-the following uses:
+all major computing platforms today.
-* distribute tasks to multiple CPUs
+**execnet** provides carefully tested means to ad-hoc interact with Python
+interpreters across version, platform and network barriers. It provides
+a minimal and fast API targetting the following uses:
+
+* distribute tasks to local or remote CPUs
* write and deploy hybrid multi-process applications
-* write scripts to interact a bunch of exec environments
+* write scripts to administer a bunch of exec environments
.. _Python: http://www.python.org
Features
------------------
-* zero-install bootstrapping: no manual remote installation!
+* automatic bootstrapping: no manual remote installation.
-* flexible communication: send/receive as well as
+* safe and simple serialization of python builtin types (no pickle)
+
+* flexible communication: synchronous send/receive as well as
callback/queue mechanisms supported
-* simple serialization of python builtin types (no pickling)
+* easy creation, handling and termination of multiple processes
-* grouped creation and robust termination of processes
-
-* well tested between CPython 2.4-3.1, Jython 2.5.1 and PyPy 1.1
- interpreters.
+* well tested interactions between CPython 2.4-3.1, Jython 2.5.1
+ and PyPy 1.1 interpreters.
* fully interoperable between Windows and Unix-ish systems.
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -14,7 +14,7 @@ NO_ENDMARKER_WANTED = object()
class Group:
""" Gateway Groups. """
- defaultspec = XSpec("popen")
+ defaultspec = "popen"
def __init__(self, xspecs=()):
""" initialize group and make gateways as specified. """
# Gateways may evolve to become GC-collectable
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -1,29 +1,26 @@
-.. _group:
-
-Creating and working with grouped Gateways
+Managing multiple gateways and clusters
+==================================================
+
+Usings Groups for managing multiple gateways
------------------------------------------------------
-Use ``execnet.Group`` to manage the lifetime of multiple
-gateways and perform iteration and membership
-checks::
+Use ``execnet.Group`` to manage membership and lifetime of
+of multiple gateways::
>>> import execnet
- >>> group = execnet.Group()
- >>> group.makegateway("popen")
-
- >>> group.makegateway("popen")
-
- >>> group
-
- >>> list(group)
- [, ]
+ >>> group = execnet.Group(['popen'] * 2)
>>> len(group)
2
- >>> '1' in group and '2' in group
+ >>> group
+
+ >>> list(group)
+ [, ]
+ >>> 'gw0' in group and 'gw1' in group
True
- >>> group['1']
-
-
+ >>> group['gw0'] == group[0]
+ True
+ >>> group['gw1'] == group[1]
+ True
>>> group.terminate() # exit all member gateways
>>> group
@@ -43,16 +40,18 @@ Pass an ``id=MYNAME`` part to ``group.ma
-Gateways live in a default_group
+execnet.makegateway uses execnet.default_group
------------------------------------------------------
Each time you create a gateway with ``execnet.makegateway()``
you actually use the ``execnet.default_group``::
>>> import execnet
- >>> gw = execnet.makegateway("popen")
+ >>> gw = execnet.makegateway()
>>> gw in execnet.default_group
True
+ >>> gw.defaultspec # used for empty makegateway() calls
+ "popen"
Robust Termination of ssh/popen processes
-----------------------------------------------
@@ -73,5 +72,5 @@ the related process is made::
execnet aims to provide totally robust termination so if
-you have termination issues please report them via :doc:`contact <../contact>`.
-thanks!
+you have left-over processes or other termination issues
+please :doc:`report them <../support>`. thanks!
--- a/doc/example/test_syncreceive2.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-
-Synchronously receive results from two sub processes
------------------------------------------------------
-
-Use MultiChannels for receiving multiple results from remote code::
-
- >>> import execnet
- >>> ch1 = execnet.PopenGateway().remote_exec("channel.send(1)")
- >>> ch2 = execnet.PopenGateway().remote_exec("channel.send(2)")
- >>> mch = execnet.MultiChannel([ch1, ch2])
- >>> l = mch.receive_each()
- >>> assert len(l) == 2
- >>> assert 1 in l
- >>> assert 2 in l
--- /dev/null
+++ b/doc/example/test_multi.txt
@@ -0,0 +1,75 @@
+advanced (multi) channel communication
+=====================================================
+
+MultiChannel: container for multiple channels
+------------------------------------------------------
+
+Use ``execnet.MultiChannel`` to work with multiple channels::
+
+ >>> import execnet
+ >>> ch1 = execnet.makegateway().remote_exec("channel.send(1)")
+ >>> ch2 = execnet.makegateway().remote_exec("channel.send(2)")
+ >>> mch = execnet.MultiChannel([ch1, ch2])
+ >>> len(mch)
+ 2
+ >>> mch[0] is ch1 and mch[1] is ch2
+ True
+ >>> ch1 in mch and ch2 in mch
+ True
+ >>> sum(mch.receive_each())
+ 3
+
+receive results from sub processes with a Queue
+-----------------------------------------------------
+
+Use ``MultiChannel.make_receive_queue()`` to get a queue
+from which to obtain results::
+
+ >>> ch1 = execnet.makegateway().remote_exec("channel.send(1)")
+ >>> ch2 = execnet.makegateway().remote_exec("channel.send(2)")
+ >>> mch = execnet.MultiChannel([ch1, ch2])
+ >>> queue = mch.make_receive_queue()
+ >>> chan1, res1 = queue.get()
+ >>> chan2, res2 = queue.get(timeout=3)
+ >>> res1 + res2
+ 3
+
+Working asynchronously/event-based with channels
+---------------------------------------------------
+
+Use channel callbacks if you want to process incoming
+data immediately and without blocking execution::
+
+ >>> import execnet
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec("channel.receive() ; channel.send(42)")
+ >>> l = []
+ >>> ch.setcallback(l.append)
+ >>> ch.send(1)
+ >>> ch.waitclose()
+ >>> assert l == [42]
+
+Note that the callback function will be executed in the
+receiver thread and should not block or run for too long.
+
+robustly receive results and termination notification
+-----------------------------------------------------
+
+Use ``MultiChannel.make_receive_queue(endmarker)`` to specify
+an object to be put to the queue when the remote side of a channel
+is closed. The endmarker will also be put to the Queue if the gateway
+is blocked in execution and is terminated/killed::
+
+ >>> group = execnet.Group(['popen'] * 3) # create three gateways
+ >>> mch = group.remote_exec("channel.send(channel.receive()+1)")
+ >>> queue = mch.make_receive_queue(endmarker=42)
+ >>> mch[0].send(1)
+ >>> chan1, res1 = queue.get()
+ >>> res1
+ 2
+ >>> group.terminate(timeout=1) # kill processes waiting on receive
+ >>> for i in range(3):
+ ... chan1, res1 = queue.get()
+ ... assert res1 == 42
+ >>> group
+
--- /dev/null
+++ b/doc/example/test_info.txt
@@ -0,0 +1,104 @@
+basic local and remote communication
+=========================================
+
+Comparing current working directories
+----------------------------------------
+
+A popen gateway has the same working directory as the instantiatior::
+
+ >>> import execnet, os
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec("import os; channel.send(os.getcwd())")
+ >>> res = ch.receive()
+ >>> assert res == os.getcwd()
+
+"ssh" gateways default to the login home directory.
+
+Getting information from remote ssh account
+--------------------------------------------
+
+Use simple execution to obtain information from remote environments::
+
+ >>> import execnet, os
+ >>> gw = execnet.makegateway("ssh=codespeak.net")
+ >>> channel = gw.remote_exec("""
+ ... import sys, os
+ ... channel.send((sys.platform, sys.version_info, os.getpid()))
+ ... """)
+ >>> platform, version_info, remote_pid = channel.receive()
+ >>> platform
+ 'linux2'
+ >>> version_info
+ (2, 4, 2, 'final', 0)
+
+
+Avoid "inlined" source strings with remote_exec
+--------------------------------------------------------------
+
+You can pass a module object to ``remote_exec`` in which case
+its source code will be sent. No dependencies will be transferred
+so the module must be self-contained or only use modules that are
+installed on the "other" side. Module code can detect if it is
+running in a remote_exec situation by checking for the special
+``__name__`` attribute.
+
+.. include:: remote1.py
+ :literal:
+
+You can now remote-execute the module like this::
+
+ >>> import execnet, remote1
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec(remote1)
+ >>> print (ch.receive())
+ initialization complete
+
+which will print the 'initialization complete' string.
+
+a simple command loop pattern
+--------------------------------------------------------------
+
+If you want the remote side to serve a number
+of synchronous function calls into your module
+you can setup a serving loop and write a local protocol.
+
+.. include:: remotecmd.py
+ :literal:
+
+Then on the local side you can do::
+
+ >>> import execnet, remotecmd
+ >>> gw = execnet.PopenGateway()
+ >>> ch = gw.remote_exec(remotecmd)
+ >>> ch.send('simple(10)') # execute func-call remotely
+ >>> ch.receive()
+ 11
+
+Our remotecmd module starts up remote serving
+through the ``for item in channel`` loop which
+will terminate when the channel closes. It evaluates
+all incoming requests in the global name space and
+sends back the results.
+
+
+Instantiate gateways through sockets
+-----------------------------------------------------
+
+.. _`socketserver.py`: http://bitbucket.org/hpk42/execnet/raw/80baab4140de/execnet/script/socketserver.py
+
+In cases where you do not have SSH-access to a machine
+you need to download a small version-independent standalone
+`socketserver.py`_ script to provide a remote bootstrapping-point.
+You do not need to install the execnet package remotely.
+Simply run the script like this::
+
+ python socketserver.py :8888 # bind to all IPs, port 8888
+
+You can then instruct execnet on your local machine to bootstrap
+itself into the remote socket endpoint:
+
+ import execnet
+ gw = execnet.SocketGateway("TARGET-IP:8888")
+
+That's it, you can now use the gateway object just like
+a popen- or ssh-based one.
--- a/doc/example/test_connect.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-
-Connect to Python2/Numpy from Python3
-----------------------------------------
-
-Here we run a Python3 interpreter to connect to a Python2.6 interpreter
-that has numpy installed. We send items to be added to an array and
-receive back the remote "repr" of the array::
-
- import execnet
- gw = execnet.makegateway("popen//python=python2.6")
- channel = gw.remote_exec("""
- import numpy
- array = numpy.array([1,2,3])
- while 1:
- x = channel.receive()
- if x is None:
- break
- array = numpy.append(array, x)
- channel.send(repr(array))
- """)
- for x in range(10):
- channel.send(x)
- channel.send(None)
- print (channel.receive())
-
-will print on the CPython3.1 side::
-
- array([1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
-
-A more refined real-life example of python3/python2 interaction
-is the anyvc_ project which uses version-control bindings in
-a Python2 subprocess in order to offer Python3-based library
-functionality.
-
-.. _anyvc: http://bitbucket.org/RonnyPfannschmidt/anyvc/overview/
-
-Work with Java objects from CPython
-----------------------------------------
-
-Use your CPython interpreter to connect to a `Jython 2.5.1`_ interpreter
-and work with Java types::
-
- import execnet
- gw = execnet.makegateway("popen//python=jython")
- channel = gw.remote_exec("""
- from java.util import Vector
- v = Vector()
- v.add('aaa')
- v.add('bbb')
- for val in v:
- channel.send(val)
- """)
-
- for item in channel:
- print (item)
-
-will print on the CPython side::
-
- aaa
- bbb
-
-.. _`Jython 2.5.1`: http://www.jython.org
-
-Work with C# objects from CPython
-----------------------------------------
-
-(Experimental) use your CPython interpreter to connect to a IronPython_ interpreter
-which can work with C# classes. Here is an example for instantiating
-a CLR Array instance and sending back its representation::
-
- import execnet
- gw = execnet.makegateway("popen//python=ipy")
-
- channel = gw.remote_exec("""
- import clr
- clr.AddReference("System")
- from System import Array
- array = Array[float]([1,2])
- channel.send(str(array))
- """)
- print (channel.receive())
-
-using Mono 2.0 and IronPython-1.1 this will print on the CPython side::
-
- System.Double[](1.0, 2.0)
-
-.. note::
- Using IronPython needs more testing, likely newer versions
- will work better. please feedback if you have information.
-
-.. _IronPython: http://www.IronPython.org
-
-
--- a/doc/examples.txt
+++ b/doc/examples.txt
@@ -5,18 +5,13 @@ examples
.. _`execnet-dev`: http://codespeak.net/mailman/listinfo/execnet-dev
.. _`execnet-commit`: http://codespeak.net/mailman/listinfo/execnet-commit
-execnet is under active development. Checkout `execnet-dev`_
-and `execnet-commit`_.
+Note: all examples with `>>>` prompts are automatically tested.
.. toctree::
- :maxdepth: 1
+ :maxdepth: 2
- example/test_connect
- example/test_cwd.txt
- example/test_channelexec.txt
- example/test_remotecmd.txt
+ example/test_info.txt
example/test_group.txt
- example/test_syncreceive2.txt
- example/test_asyncreceive2.txt
- example/test_ssh_fileserver.txt
- example/test_installvia.txt
+ example/test_multi.txt
+ example/hybridpython.txt
+ example/test_debug.txt
--- a/doc/example/test_remotecmd.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-
-A simple command pattern
---------------------------------------------------------------
-
-If you want the remote side to serve a number
-of synchronous function you can setup a serving
-loop and invent your own local protocol
-
-.. include:: remotecmd.py
- :literal:
-
-Then on the local side you can do::
-
- >>> import execnet, remotecmd
- >>> gw = execnet.PopenGateway()
- >>> ch = gw.remote_exec(remotecmd)
- >>> ch.send('simple(10)') # execute func-call remotely
- >>> ch.receive()
- 11
-
-Our remotecmd module starts up remote serving
-through the ``for item in channel`` loop which
-will terminate when the channel closes. It evaluates
-all incoming requests in the global name space and
-sends back the results.
--- a/doc/example/test_installvia.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-
-Instantiate a socket server in a new subprocess
------------------------------------------------------
-
-(experimental) The following example opens a PopenGateway, i.e. a python
-child process, and starts a socket server within that process
-and then opens a second gateway to the freshly started
-socketserver::
-
- import execnet
-
- popengw = execnet.PopenGateway()
- socketgw = execnet.SocketGateway.new_remote(popengw, ("127.0.0.1", 0))
-
- print socketgw.remote_status() # print some info about the remote environment
-
--- a/doc/example/test_asyncreceive2.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-
-Asynchronously receive results from two sub processes
------------------------------------------------------
-
-Use ``MultiChannel.make_receive_queue()`` for asynchronously receiving
-multiple results from remote code. This standard Queue provides
-``(channel, result)`` tuples which allows to determine where
-a result comes from::
-
- >>> import execnet
- >>> ch1 = execnet.PopenGateway().remote_exec("channel.send(1)")
- >>> ch2 = execnet.PopenGateway().remote_exec("channel.send(2)")
- >>> mch = execnet.MultiChannel([ch1, ch2])
- >>> queue = mch.make_receive_queue()
- >>> chan1, res1 = queue.get() # you may also specify a timeout
- >>> chan2, res2 = queue.get()
- >>> res1 + res2
- 3
- >>> assert chan1 in (ch1, ch2)
- >>> assert chan2 in (ch1, ch2)
- >>> assert chan1 != chan2
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -10,7 +10,7 @@ help to manage creation and termination
.. image:: _static/basic1.png
-Gateways: connecting to another Python Interpreter
+Gateways: bootstrapping Python interpreters
===================================================
.. currentmodule:: execnet
@@ -18,13 +18,15 @@ Gateways: connecting to another Python I
All Gateways are instantiated via a call to ``makegateway()``
passing it a gateway specification or URL.
-.. autofunction:: execnet.makegateway(xspec)
+.. _xspec:
+
+.. autofunction:: execnet.makegateway(spec)
Here is an example which instantiates a simple Python subprocess::
>>> gateway = execnet.makegateway("popen")
-You can use a gateway to `remote execute code`_ and
+gateways allow to `remote execute code`_ and
`exchange data`_ bidirectionally.
examples for valid gateway specifications
@@ -38,26 +40,8 @@ examples for valid gateway specification
subprocess; running with the lowest CPU priority ("nice" level).
By default current dir will be the current dir of the instantiator.
-* ``socket=192.168.1.4:8888`` specifies of a Python Socket server
- process that listens on 192.168.1.4:8888; current dir will be the
- 'pyexecnet-cache' sub directory which is used a default for all remote
- processes.
-
-.. _xspec:
-
-recognized Gateway URL parts
----------------------------------------
-
-The following parameters are recognized as part of a gateway specification::
-
-* ``popen`` for a PopenGateway.
-* ``ssh=host`` for a SshGateway to the given host.
-* ``socket=address:port`` for a SocketGateway at the given address.
-* ``id=NAME`` set the ``id`` of the gateway, must be unique within Group_
-* ``python=executable`` for specifying Python Interpreter executables
-* ``chdir=path`` change remote working dir to given relative or absolute path
-* ``nice=value`` decrease remote nice level if platforms supports it
-
+* ``socket=192.168.1.4:8888`` specifies a Python Socket server
+ process that listens on 192.168.1.4:8888``
.. _`remote execute code`:
@@ -90,8 +74,6 @@ Channels: exchanging data with remote co
A channel object allows to send and receive data between
two asynchronously running programs.
-.. class:: Channel
-
.. automethod:: Channel.send(item)
.. automethod:: Channel.receive(timeout)
.. automethod:: Channel.setcallback(callback, endmarker=_NOENDMARKER)
@@ -111,7 +93,7 @@ Grouped Gateways and robust termination
All created gateway instances are part of a group. If you
call ``execnet.makegateway`` it actually is forwarded to
the ``execnet.default_group``. Group objects are container
-objects (see :doc:`group examples >`_)
+objects (see :doc:`group examples `)
and manage the final termination procedure:
.. automethod:: Group.terminate(timeout=None)
@@ -135,10 +117,9 @@ information from the remote side.
Calling this method tells you e.g. how many execution
tasks are queued, how many are executing and how many
-channels are active. Note that remote_status()
-works even if the other side's execution thread
-is blocked and can thus be used to query status
-in a non-blocking manner.
+channels are active. Note that ``remote_status()``
+works even if the other side is busy executing code
+and can thus be used to query status in a non-blocking manner.
rsync: synchronise filesystem with remote
===============================================================
--- a/doc/example/test_cwd.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-Compare cwd() of Popen Gateways
-----------------------------------------
-
-A PopenGateway has the same working directory as the instantiatior::
-
- >>> import execnet, os
- >>> gw = execnet.PopenGateway()
- >>> ch = gw.remote_exec("import os; channel.send(os.getcwd())")
- >>> res = ch.receive()
- >>> assert res == os.getcwd()
-
-
-Getting the remote process ID
---------------------------------------
-
-Here is an self-contained example for reading a remote process identifier.
-
- >>> import execnet, os
- >>> gw = execnet.makegateway("popen")
- >>> channel = gw.remote_exec("""
- ... import os
- ... channel.send(os.getpid())
- ... """)
- >>> remote_pid = channel.receive()
- >>> remote_pid != os.getpid()
- True
-
-If you'd like to avoid inlining source strings take a look
-at the :doc:`channelexec `
-and the :doc:`command pattern` examples.
From commits-noreply at bitbucket.org Sat Dec 5 19:29:01 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 18:29:01 +0000 (UTC)
Subject: [execnet-commit] execnet commit 66760dc71203: finalizing bits for
1.0.1
Message-ID: <20091205182901.5609D7EF0C@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260036340 -3600
# Node ID 66760dc71203dbceeb08851cf4cf74e2687156d0
# Parent 2233f21d1d91e0d52ba6c9fe4896b02285d85aa7
finalizing bits for 1.0.1
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -30,6 +30,9 @@ help:
clean:
-rm -rf $(BUILDDIR)/*
+install: clean html linkcheck
+ rsync -avz $(BUILDDIR)/html/ code:www-execnet/trunk/
+
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -1,6 +1,4 @@
-Welcome to rapid Python deployment
-========================================================
.. image:: _static/pythonring.png
:align: right
@@ -12,7 +10,7 @@ all major computing platforms today.
interpreters across version, platform and network barriers. It provides
a minimal and fast API targetting the following uses:
-* distribute tasks to local or remote CPUs
+* distribute tasks to local or remote CPUs
* write and deploy hybrid multi-process applications
* write scripts to administer a bunch of exec environments
@@ -78,4 +76,3 @@ are MIT-licensed.
implnotes
install
rel-1.0.0
- docindex
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -105,8 +105,11 @@ class Popen2IO:
self.outfile, self.infile = outfile, infile
if sys.platform == "win32":
import msvcrt
- msvcrt.setmode(infile.fileno(), os.O_BINARY)
- msvcrt.setmode(outfile.fileno(), os.O_BINARY)
+ try:
+ msvcrt.setmode(infile.fileno(), os.O_BINARY)
+ msvcrt.setmode(outfile.fileno(), os.O_BINARY)
+ except (AttributeError, IOError):
+ pass
def read(self, numbytes):
"""Read exactly 'numbytes' bytes from the pipe. """
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -50,8 +50,8 @@ you actually use the ``execnet.default_g
>>> gw = execnet.makegateway()
>>> gw in execnet.default_group
True
- >>> gw.defaultspec # used for empty makegateway() calls
- "popen"
+ >>> execnet.default_group.defaultspec # used for empty makegateway() calls
+ 'popen'
Robust Termination of ssh/popen processes
-----------------------------------------------
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,5 @@
include CHANGELOG
+include conftest.py
include README.txt
include setup.py
include LICENSE
--- a/doc/_templates/layout.html
+++ b/doc/_templates/layout.html
@@ -1,14 +1,14 @@
{% extends "!layout.html" %}
{% block rootrellink %}
- docindex »
+
{% endblock %}
{% block header %}
-
execnet: robust gateways to hybrid execution environments
+
execnet: rapid multi-Python deployment home |
install |
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -4,7 +4,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.1a"
+__version__ = "1.0.1"
import execnet.apipkg
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -8,7 +8,7 @@ def test_subprocess_interaction(anypytho
line = gateway.popen_bootstrapline
compile(line, 'xyz', 'exec')
args = [str(anypython), '-c', line]
- popen = subprocess.Popen(args, bufsize=0,
+ popen = subprocess.Popen(args, bufsize=0, universal_newlines=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def send(line):
popen.stdin.write(line.encode('ascii'))
--- a/doc/docindex.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-
-Documentation Index
-============================
-
-.. toctree::
- :maxdepth: 2
-
- basics
- examples
- changelog
--- a/setup.py
+++ b/setup.py
@@ -1,15 +1,16 @@
"""
-execnet: connect your execution environments
+execnet: rapid multi-Python deployment
========================================================
-.. image:: _static/pythonring.png
- :align: right
+.. _execnet: http://codespeak.net/execnet
-Execnet allows to ad-hoc connect to Python interpreters across version, platform and network barriers. It provides a minimal, fast and robust API for the following uses:
+execnet_ provides carefully tested means to ad-hoc interact with Python
+interpreters across version, platform and network barriers. It provides
+a minimal and fast API targetting the following uses:
-* distribute tasks to multiple CPUs
-* deploy hybrid applications
-* manage local and remote execution environments
+* distribute tasks to local or remote CPUs
+* write and deploy hybrid multi-process applications
+* write scripts to administer a bunch of exec environments
Features
------------------
@@ -39,7 +40,7 @@ from execnet import __version__
def main():
setup(
name='execnet',
- description='execnet: connect your execution environments',
+ description='execnet: rapid multi-Python deployment',
long_description = __doc__,
version= __version__,
url='http://codespeak.net/execnet',
From commits-noreply at bitbucket.org Sat Dec 5 19:29:08 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sat, 5 Dec 2009 18:29:08 +0000 (UTC)
Subject: [execnet-commit] execnet commit e77a2974394b: Added tag 1.0.1 for
changeset 66760dc71203
Message-ID: <20091205182908.784C27EF0D@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260037737 -3600
# Node ID e77a2974394bd0c6bc18f6c2f90f436be51b90ef
# Parent 66760dc71203dbceeb08851cf4cf74e2687156d0
Added tag 1.0.1 for changeset 66760dc71203
--- a/.hgtags
+++ b/.hgtags
@@ -2,3 +2,4 @@ 39e4b3a1397adad17ecfcd9e65a08521d90b2795
7cf86a1811b11aa58112a255e6afba870c8d855e 1.0.0
7cf86a1811b11aa58112a255e6afba870c8d855e 1.0.0
5d1c438e335dc369834207882ec5f3227d365c11 1.0.0
+66760dc71203dbceeb08851cf4cf74e2687156d0 1.0.1
From commits-noreply at bitbucket.org Sun Dec 6 18:56:35 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 6 Dec 2009 17:56:35 +0000 (UTC)
Subject: [execnet-commit] execnet commit de6284add5db: avoid deprecated
PopenGateway, new Group.defaultspec example, some doc tweaks
Message-ID: <20091206175635.422797EE74@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260118830 -3600
# Node ID de6284add5db8329f9b57fc44026b6f031771782
# Parent e77a2974394bd0c6bc18f6c2f90f436be51b90ef
avoid deprecated PopenGateway, new Group.defaultspec example, some doc tweaks
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -31,7 +31,7 @@ clean:
-rm -rf $(BUILDDIR)/*
install: clean html linkcheck
- rsync -avz $(BUILDDIR)/html/ code:www-execnet/trunk/
+ rsync -avz $(BUILDDIR)/html/ code:www-execnet/
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
--- a/doc/install.txt
+++ b/doc/install.txt
@@ -13,7 +13,7 @@ Install a public `pypi release`_ via `ea
or
hg clone https://hpk42 at bitbucket.org/hpk42/execnet/
- python setup.py install # or add checkout path to PYTHONPATH directly
+ python setup.py install # or 'develop' or add checkout path to PYTHONPATH
Next checkout the basic api and examples:
--- a/doc/example/popen_read_multiple.py
+++ b/doc/example/popen_read_multiple.py
@@ -9,7 +9,7 @@ NUM_PROCESSES = 5
channels = []
for i in range(NUM_PROCESSES):
- gw = execnet.PopenGateway() # or use SSH or socket gateways
+ gw = execnet.makegateway() # or use SSH or socket gateways
channel = gw.remote_exec("""
import time
secs = channel.receive()
--- a/doc/example/test_group.txt
+++ b/doc/example/test_group.txt
@@ -74,3 +74,25 @@ the related process is made::
execnet aims to provide totally robust termination so if
you have left-over processes or other termination issues
please :doc:`report them <../support>`. thanks!
+
+
+Using Groups to manage a certain type of gateway
+------------------------------------------------------
+
+Set ``group.defaultspec`` to determine the default gateway
+specification used by ``group.makegateway()``:
+
+ >>> import execnet
+ >>> group = execnet.Group()
+ >>> group.defaultspec = "ssh=localhost//chdir=mytmp//nice=20"
+ >>> gw = group.makegateway()
+ >>> ch = gw.remote_exec("""
+ ... import os.path
+ ... basename = os.path.basename(os.getcwd())
+ ... channel.send(basename)
+ ... """)
+ >>> ch.receive()
+ 'mytmp'
+
+This way a Group object becomes kind of a Gateway factory where
+the factory-caller does not need to know the setup.
--- a/doc/example/test_multi.txt
+++ b/doc/example/test_multi.txt
@@ -41,7 +41,7 @@ Use channel callbacks if you want to pro
data immediately and without blocking execution::
>>> import execnet
- >>> gw = execnet.PopenGateway()
+ >>> gw = execnet.makegateway()
>>> ch = gw.remote_exec("channel.receive() ; channel.send(42)")
>>> l = []
>>> ch.setcallback(l.append)
--- a/doc/example/test_info.txt
+++ b/doc/example/test_info.txt
@@ -4,10 +4,10 @@ basic local and remote communication
Comparing current working directories
----------------------------------------
-A popen gateway has the same working directory as the instantiatior::
+A local popen gateway has the same working directory as the instantiatior::
>>> import execnet, os
- >>> gw = execnet.PopenGateway()
+ >>> gw = execnet.makegateway()
>>> ch = gw.remote_exec("import os; channel.send(os.getcwd())")
>>> res = ch.receive()
>>> assert res == os.getcwd()
@@ -48,7 +48,7 @@ running in a remote_exec situation by ch
You can now remote-execute the module like this::
>>> import execnet, remote1
- >>> gw = execnet.PopenGateway()
+ >>> gw = execnet.makegateway()
>>> ch = gw.remote_exec(remote1)
>>> print (ch.receive())
initialization complete
@@ -68,7 +68,7 @@ you can setup a serving loop and write a
Then on the local side you can do::
>>> import execnet, remotecmd
- >>> gw = execnet.PopenGateway()
+ >>> gw = execnet.makegateway()
>>> ch = gw.remote_exec(remotecmd)
>>> ch.send('simple(10)') # execute func-call remotely
>>> ch.receive()
@@ -95,7 +95,7 @@ Simply run the script like this::
python socketserver.py :8888 # bind to all IPs, port 8888
You can then instruct execnet on your local machine to bootstrap
-itself into the remote socket endpoint:
+itself into the remote socket endpoint::
import execnet
gw = execnet.SocketGateway("TARGET-IP:8888")
--- a/doc/example/redirect_remote_output.py
+++ b/doc/example/redirect_remote_output.py
@@ -10,7 +10,7 @@ showcasing features of the channel objec
import py
-gw = execnet.PopenGateway()
+gw = execnet.makegateway()
outchan = gw.remote_exec("""
import sys
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -24,7 +24,7 @@ passing it a gateway specification or UR
Here is an example which instantiates a simple Python subprocess::
- >>> gateway = execnet.makegateway("popen")
+ >>> gateway = execnet.makegateway()
gateways allow to `remote execute code`_ and
`exchange data`_ bidirectionally.
@@ -131,7 +131,7 @@ rsync: synchronise filesystem with remot
Here is a basic example for using RSync::
rsync = execnet.RSync('/tmp/source')
- gw = execnet.makegateway("popen")
+ gw = execnet.makegateway()
rsync.add_target(gw, '/tmp/dest')
rsync.send()
From commits-noreply at bitbucket.org Sun Dec 6 18:56:37 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 6 Dec 2009 17:56:37 +0000 (UTC)
Subject: [execnet-commit] execnet commit 800841c9b050: skip jython tests if
it is not a 2.5 version
Message-ID: <20091206175637.8D9A67EE74@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260121901 -3600
# Node ID 800841c9b050c49df81bddf3270d74cd5263df69
# Parent de6284add5db8329f9b57fc44026b6f031771782
skip jython tests if it is not a 2.5 version
--- a/conftest.py
+++ b/conftest.py
@@ -1,6 +1,7 @@
import execnet
import py
import sys
+import subprocess
collect_ignore = ['build', 'doc/_build']
@@ -63,9 +64,24 @@ def pytest_generate_tests(metafunc):
'pypy-c', 'jython'):
metafunc.addcall(id=name, param=name)
+def getexecutable(name, cache={}):
+ try:
+ return cache[name]
+ except KeyError:
+ executable = py.path.local.sysfind(name)
+ if executable:
+ if name == "jython":
+ popen = subprocess.Popen([str(executable), "--version"],
+ stderr=subprocess.PIPE)
+ out, err = popen.communicate()
+ if not err or "2.5" not in err:
+ executable = None
+ cache[name] = executable
+ return executable
+
def pytest_funcarg__anypython(request):
name = request.param
- executable = py.path.local.sysfind(name)
+ executable = getexecutable(name)
if executable is None:
if sys.platform == "win32":
executable = winpymap.get(name, None)
From commits-noreply at bitbucket.org Sun Dec 6 20:11:57 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 6 Dec 2009 19:11:57 +0000 (UTC)
Subject: [execnet-commit] execnet commit a15041d7bb5e: work out issues/ideas
a bit
Message-ID: <20091206191157.4755D7EEE5@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260125857 -3600
# Node ID a15041d7bb5e008dc1a4a2976f03a79ca2085841
# Parent 800841c9b050c49df81bddf3270d74cd5263df69
work out issues/ideas a bit
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -19,8 +19,9 @@ path: execnet/rsync.py
currently rsyncing bails out with a strange message when trying to rsync
links. should provide better message or even some support for doing it.
-a process keeping state across invoactions
---------------------------------------------------
+socketservice process for keeping cross-invocation state
+---------------------------------------------------------
+
tags: 1.1 feature newdist
A building block for keeping permanent network
@@ -36,6 +37,16 @@ that keeps state. Considered:
will re-use the already-imported gateway base code
so again avoid parse/compile on arbitrary strings.
+Possible code example:
+
+ # connect to on-demand started port-8888 socket server
+ # similar to popen+socket//installvia=popen
+ gw = group.makegateway("socketservice//port=8888")
+ gw.remote_exec("...") # execute code in a thread in the socket server
+ ...
+ # exit the gateway-connection, but leave socket server running
+ gw.exit()
+
a channel-IO based gateway
--------------------------------------------------
tags: 1.1 feature newdist
@@ -46,3 +57,24 @@ This way a gateway can be instantiated t
intermediating gateway which bi-directionallry forwads
Messages through a existing channel.
+A code example could look like this:
+
+ group.setlocalfactory(port=8888)
+ gw = group.makegateway("ssh=codespeak.net")
+ # local ssh-process would be a child of the factory service
+ # which could have timeouts for killing, or allow resuming.
+ # for instantiating remote it could also install a multiplexer
+ # on the remote place such that the same ssh connection is re-used
+ # for multiple ssh=codespeak.net connections, makes for small latency.
+
+some first simple implemenation might look like this::
+
+ gw = group.makegateway("socketservice//port=8888")
+ # we can rely that the subprocess gw can import execnet
+ ch = gw.remote_exec("""
+ import execnet.service
+ execnet.service.gatewaymaker(channel)
+ """)
+ ch.send(xspec)
+ subchannel = ch.receive()
+ return ChannelGateway(subchannel)
From commits-noreply at bitbucket.org Mon Dec 7 13:34:59 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Mon, 7 Dec 2009 12:34:59 +0000 (UTC)
Subject: [execnet-commit] execnet commit e5cbfedbd84e: internal tweaks: move
deprecated code to its own module,
add python2.7 to python interpreters to look for.
Message-ID: <20091207123459.57DA17EE85@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260189273 -3600
# Node ID e5cbfedbd84e6dc2ee0f62f3b5ea70d865cd67ae
# Parent a15041d7bb5e008dc1a4a2976f03a79ca2085841
internal tweaks: move deprecated code to its own module, add python2.7 to python interpreters to look for.
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -228,48 +228,6 @@ class MultiChannel:
if first:
reraise(*first)
-
-default_group = Group()
-
-makegateway = default_group.makegateway
-
-def PopenGateway(python=None):
- """ instantiate a gateway to a subprocess
- started with the given 'python' executable.
- """
- APIWARN("1.0.0b4", "use makegateway('popen')")
- spec = execnet.XSpec("popen")
- spec.python = python
- return default_group.makegateway(spec)
-
-def SocketGateway(host, port):
- """ This Gateway provides interaction with a remote process
- by connecting to a specified socket. On the remote
- side you need to manually start a small script
- (py/execnet/script/socketserver.py) that accepts
- SocketGateway connections or use the experimental
- new_remote() method on existing gateways.
- """
- APIWARN("1.0.0b4", "use makegateway('socket=host:port')")
- spec = execnet.XSpec("socket=%s:%s" %(host, port))
- return default_group.makegateway(spec)
-
-def SshGateway(sshaddress, remotepython=None, ssh_config=None):
- """ instantiate a remote ssh process with the
- given 'sshaddress' and remotepython version.
- you may specify an ssh_config file.
- """
- APIWARN("1.0.0b4", "use makegateway('ssh=host')")
- spec = execnet.XSpec("ssh=%s" % sshaddress)
- spec.python = remotepython
- spec.ssh_config = ssh_config
- return default_group.makegateway(spec)
-
-def APIWARN(version, msg, stacklevel=3):
- import warnings
- Warn = DeprecationWarning("(since version %s) %s" %(version, msg))
- warnings.warn(Warn, stacklevel=stacklevel)
-
def killpopen(popen):
try:
if hasattr(popen, 'kill'):
@@ -300,3 +258,7 @@ def killpid(pid):
ctypes.windll.kernel32.CloseHandle(handle)
else:
raise EnvironmmentError("no method to kill %s" %(pid,))
+
+default_group = Group()
+makegateway = default_group.makegateway
+
--- a/conftest.py
+++ b/conftest.py
@@ -8,6 +8,7 @@ collect_ignore = ['build', 'doc/_build']
rsyncdirs = ['conftest.py', 'execnet', 'testing', 'doc']
winpymap = {
+ 'python2.7': r'C:\Python27\python.exe',
'python2.6': r'C:\Python26\python.exe',
'python2.5': r'C:\Python25\python.exe',
'python2.4': r'C:\Python24\python.exe',
@@ -61,7 +62,7 @@ def pytest_generate_tests(metafunc):
metafunc.addcall(id=gwtype, param=gwtype)
elif 'anypython' in metafunc.funcargnames:
for name in ('python3.1', 'python2.4', 'python2.5', 'python2.6',
- 'pypy-c', 'jython'):
+ 'python2.7', 'pypy-c', 'jython'):
metafunc.addcall(id=name, param=name)
def getexecutable(name, cache={}):
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -9,9 +9,9 @@ __version__ = "1.0.1"
import execnet.apipkg
execnet.apipkg.initpkg(__name__, {
- 'PopenGateway': '.multi:PopenGateway',
- 'SocketGateway': '.multi:SocketGateway',
- 'SshGateway': '.multi:SshGateway',
+ 'PopenGateway': '.deprecated:PopenGateway',
+ 'SocketGateway': '.deprecated:SocketGateway',
+ 'SshGateway': '.deprecated:SshGateway',
'makegateway': '.multi:makegateway',
'HostNotFound': '.gateway:HostNotFound',
'XSpec': '.xspec:XSpec',
--- /dev/null
+++ b/execnet/deprecated.py
@@ -0,0 +1,43 @@
+"""
+some deprecated calls
+
+(c) 2008-2009, Holger Krekel and others
+"""
+import execnet
+
+def PopenGateway(python=None):
+ """ instantiate a gateway to a subprocess
+ started with the given 'python' executable.
+ """
+ APIWARN("1.0.0b4", "use makegateway('popen')")
+ spec = execnet.XSpec("popen")
+ spec.python = python
+ return execnet.default_group.makegateway(spec)
+
+def SocketGateway(host, port):
+ """ This Gateway provides interaction with a remote process
+ by connecting to a specified socket. On the remote
+ side you need to manually start a small script
+ (py/execnet/script/socketserver.py) that accepts
+ SocketGateway connections or use the experimental
+ new_remote() method on existing gateways.
+ """
+ APIWARN("1.0.0b4", "use makegateway('socket=host:port')")
+ spec = execnet.XSpec("socket=%s:%s" %(host, port))
+ return execnet.default_group.makegateway(spec)
+
+def SshGateway(sshaddress, remotepython=None, ssh_config=None):
+ """ instantiate a remote ssh process with the
+ given 'sshaddress' and remotepython version.
+ you may specify an ssh_config file.
+ """
+ APIWARN("1.0.0b4", "use makegateway('ssh=host')")
+ spec = execnet.XSpec("ssh=%s" % sshaddress)
+ spec.python = remotepython
+ spec.ssh_config = ssh_config
+ return execnet.default_group.makegateway(spec)
+
+def APIWARN(version, msg, stacklevel=3):
+ import warnings
+ Warn = DeprecationWarning("(since version %s) %s" %(version, msg))
+ warnings.warn(Warn, stacklevel=stacklevel)
From commits-noreply at bitbucket.org Fri Dec 11 15:10:29 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Fri, 11 Dec 2009 14:10:29 +0000 (UTC)
Subject: [execnet-commit] execnet commit 3d00bf8eaea4: move
socket-instantiation into own file,
speeds up popen-gateway creation by another 10%.
Message-ID: <20091211141029.DA64D7EF3B@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1260540574 -3600
# Node ID 3d00bf8eaea4e48e9cc621127e936d50f59727cb
# Parent e5cbfedbd84e6dc2ee0f62f3b5ea70d865cd67ae
move socket-instantiation into own file, speeds up popen-gateway creation by another 10%.
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+1.x (pending)
+--------------------------------
+
+- internally split socket gateways, speeds up popen-gateways
+ by 10% (now at 30 milliseconds per-gateway on my 3-year old machine)
+
1.0.1
--------------------------------
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -6,7 +6,7 @@ gateway code for initiating popen, socke
import sys, os, inspect, socket, types
import textwrap
import execnet
-from execnet.gateway_base import Message, Popen2IO, SocketIO
+from execnet.gateway_base import Message, Popen2IO
from execnet import gateway_base
importdir = os.path.dirname(os.path.dirname(execnet.__file__))
@@ -174,59 +174,6 @@ def sendexec(io, *sources):
source = "\n".join(sources)
io.write((repr(source)+ "\n").encode('ascii'))
-class SocketGateway(Gateway):
- """ This Gateway provides interaction with a remote process
- by connecting to a specified socket. On the remote
- side you need to manually start a small script
- (py/execnet/script/socketserver.py) that accepts
- SocketGateway connections.
- """
- _remotesetup = "io = SocketIO(clientsock)"
-
- def __init__(self, host, port, id):
- """ instantiate a gateway to a process accessed
- via a host/port specified socket.
- """
- self.host = host = str(host)
- self.port = port = int(port)
- self.remoteaddress = '%s:%d' % (self.host, self.port)
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- try:
- sock.connect((host, port))
- except socket.gaierror:
- raise HostNotFound(str(sys.exc_info()[1]))
- io = SocketIO(sock)
- super(SocketGateway, self).__init__(io=io, id=id)
-
- def new_remote(cls, gateway, id, hostport=None):
- """ return a new (connected) socket gateway,
- instantiated through the given 'gateway'.
- """
- if hostport is None:
- host, port = ('localhost', 0)
- else:
- host, port = hostport
-
- mydir = os.path.dirname(__file__)
- socketserver = os.path.join(mydir, 'script', 'socketserver.py')
- socketserverbootstrap = "\n".join([
- open(socketserver, 'r').read(), """if 1:
- import socket
- sock = bind_and_listen((%r, %r))
- port = sock.getsockname()
- channel.send(port)
- startserver(sock)
- """ % (host, port)])
- # execute the above socketserverbootstrap on the other side
- channel = gateway.remote_exec(socketserverbootstrap)
- (realhost, realport) = channel.receive()
- #self._trace("new_remote received"
- # "port=%r, hostname = %r" %(realport, hostname))
- if not realhost or realhost=="0.0.0.0":
- realhost = "localhost"
- return cls(realhost, realport, id=id)
- new_remote = classmethod(new_remote)
-
class HostNotFound(Exception):
pass
--- /dev/null
+++ b/execnet/gateway_socket.py
@@ -0,0 +1,92 @@
+import socket
+from execnet.gateway import Gateway, HostNotFound
+import os, sys, inspect
+
+class SocketIO:
+ error = (socket.error, EOFError)
+ def __init__(self, sock):
+ self.sock = sock
+ try:
+ sock.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10)# IPTOS_LOWDELAY
+ sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+ except (AttributeError, socket.error):
+ e = sys.exc_info()[1]
+ sys.stderr.write("WARNING: cannot set socketoption")
+
+ def read(self, numbytes):
+ "Read exactly 'bytes' bytes from the socket."
+ buf = bytes()
+ while len(buf) < numbytes:
+ t = self.sock.recv(numbytes - len(buf))
+ if not t:
+ raise EOFError
+ buf += t
+ return buf
+
+ def write(self, data):
+ assert isinstance(data, bytes)
+ self.sock.sendall(data)
+
+ def close_read(self):
+ try:
+ self.sock.shutdown(0)
+ except socket.error:
+ pass
+ def close_write(self):
+ try:
+ self.sock.shutdown(1)
+ except socket.error:
+ pass
+
+class SocketGateway(Gateway):
+ """ This Gateway provides interaction with a remote process
+ by connecting to a specified socket. On the remote
+ side you need to manually start a small script
+ (py/execnet/script/socketserver.py) that accepts
+ SocketGateway connections.
+ """
+ _remotesetup = "import socket\n%s\nio = SocketIO(clientsock)" % inspect.getsource(SocketIO)
+
+ def __init__(self, host, port, id):
+ """ instantiate a gateway to a process accessed
+ via a host/port specified socket.
+ """
+ self.host = host = str(host)
+ self.port = port = int(port)
+ self.remoteaddress = '%s:%d' % (self.host, self.port)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ sock.connect((host, port))
+ except socket.gaierror:
+ raise HostNotFound(str(sys.exc_info()[1]))
+ io = SocketIO(sock)
+ super(SocketGateway, self).__init__(io=io, id=id)
+
+ def new_remote(cls, gateway, id, hostport=None):
+ """ return a new (connected) socket gateway,
+ instantiated through the given 'gateway'.
+ """
+ if hostport is None:
+ host, port = ('localhost', 0)
+ else:
+ host, port = hostport
+
+ mydir = os.path.dirname(__file__)
+ socketserver = os.path.join(mydir, 'script', 'socketserver.py')
+ socketserverbootstrap = "\n".join([
+ open(socketserver, 'r').read(), """if 1:
+ import socket
+ sock = bind_and_listen((%r, %r))
+ port = sock.getsockname()
+ channel.send(port)
+ startserver(sock)
+ """ % (host, port)])
+ # execute the above socketserverbootstrap on the other side
+ channel = gateway.remote_exec(socketserverbootstrap)
+ (realhost, realport) = channel.receive()
+ #self._trace("new_remote received"
+ # "port=%r, hostname = %r" %(realport, hostname))
+ if not realhost or realhost=="0.0.0.0":
+ realhost = "localhost"
+ return cls(realhost, realport, id=id)
+ new_remote = classmethod(new_remote)
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -6,7 +6,7 @@ NOTE: aims to be compatible to Python 2.
(C) 2004-2009 Holger Krekel, Armin Rigo, Benjamin Peterson, and others
"""
import sys, os, weakref
-import threading, traceback, socket, struct
+import threading, traceback, struct
try:
import queue
except ImportError:
@@ -56,47 +56,6 @@ else:
pass
-# ___________________________________________________________________________
-#
-# input output classes
-# ___________________________________________________________________________
-
-class SocketIO:
- error = (socket.error, EOFError)
- def __init__(self, sock):
- self.sock = sock
- try:
- sock.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10)# IPTOS_LOWDELAY
- sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
- except (AttributeError, socket.error):
- e = sys.exc_info()[1]
- sys.stderr.write("WARNING: cannot set socketoption")
-
- def read(self, numbytes):
- "Read exactly 'bytes' bytes from the socket."
- buf = bytes()
- while len(buf) < numbytes:
- t = self.sock.recv(numbytes - len(buf))
- if not t:
- raise EOFError
- buf += t
- return buf
-
- def write(self, data):
- assert isinstance(data, bytes)
- self.sock.sendall(data)
-
- def close_read(self):
- try:
- self.sock.shutdown(0)
- except socket.error:
- pass
- def close_write(self):
- try:
- self.sock.shutdown(1)
- except socket.error:
- pass
-
class Popen2IO:
error = (IOError, OSError, EOFError)
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -4,7 +4,7 @@ package for connecting to local and remo
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.1"
+__version__ = "1.0.1.post1"
import execnet.apipkg
--- a/execnet/multi.py
+++ b/execnet/multi.py
@@ -81,13 +81,14 @@ class Group:
elif spec.socket:
assert not spec.python, (
"socket: specifying python executables not yet supported")
+ from execnet.gateway_socket import SocketGateway
gateway_id = spec.installvia
if gateway_id:
viagw = self[gateway_id]
- gw = gateway.SocketGateway.new_remote(viagw, id=id)
+ gw = SocketGateway.new_remote(viagw, id=id)
else:
host, port = spec.socket.split(":")
- gw = gateway.SocketGateway(host, port, id=id)
+ gw = SocketGateway(host, port, id=id)
else:
raise ValueError("no gateway type found for %r" % (spec._spec,))
gw.spec = spec
--- a/testing/test_xspec.py
+++ b/testing/test_xspec.py
@@ -65,7 +65,7 @@ class TestMakegateway:
assert gw.spec.popen
assert gw.spec.python == None
rinfo = gw._rinfo()
- assert rinfo.executable == py.std.sys.executable
+ #assert rinfo.executable == py.std.sys.executable
assert rinfo.cwd == py.std.os.getcwd()
assert rinfo.version_info == py.std.sys.version_info
@@ -113,7 +113,7 @@ class TestMakegateway:
py.test.skip("cpython2.6 not found")
gw = execnet.makegateway("popen//python=%s" % cpython26)
rinfo = gw._rinfo()
- assert rinfo.executable == cpython26
+ #assert rinfo.executable == cpython26
assert rinfo.cwd == py.std.os.getcwd()
assert rinfo.version_info[:2] == (2,6)
From commits-noreply at bitbucket.org Sun Dec 20 17:10:08 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 20 Dec 2009 16:10:08 +0000 (UTC)
Subject: [execnet-commit] execnet commit 9068ad88e86a: adding issues,
fixing docs, fixating release-version
Message-ID: <20091220161008.0EC9C7EE7A@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261314055 -3600
# Node ID 9068ad88e86a345d49d29a36fa3d20b28f18cac6
# Parent 3d00bf8eaea4e48e9cc621127e936d50f59727cb
adding issues, fixing docs, fixating release-version
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -78,3 +78,36 @@ some first simple implemenation might lo
ch.send(xspec)
subchannel = ch.receive()
return ChannelGateway(subchannel)
+
+serialize channels everywhere
+-------------------------------------------------
+tags: 1.0.2 feature
+
+currently sending channels only works if they are send
+as a single item. So they cannot be contained in a list or dict.
+By making channel passing a feature of the serializer we should
+be able to lift this limitation, allowing channels in all places.
+
+creating new channels
+-------------------------------------------------
+tags: 1.0.2 feature
+
+document, provide examples and refine support for creating new channels.
+Defining closing semantics - does a channel created from a channel
+automatically close when its parent channel closes? This currently
+depends on whether it is garbage collected.
+
+creating virtualenv environments on the fly
+-----------------------------------------------
+tags: 1.1 wish
+
+there should be a (plugin-provided?) way to create
+a virtual environment:
+ gw = makegateway("ssh=codespeak.net")
+ newgw = xdeploy.create_venv_gateway(gw, "venvname", depstring)
+ newgw # lives in home/ dir of remote virtualenv
+ # maybe have get_temproot default to tmp/ there?
+ sf = xdeploy.SetupFile("..setup.py")
+ sf.sdist_remote_install(newgw)
+
+
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -46,9 +46,9 @@ copyright = '2009, holger krekel and oth
#
# The short X.Y version.
import execnet
-version = execnet.__version__
+version = "1.0.1"
# The full version, including alpha/beta/rc tags.
-release = execnet.__version__
+release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
--- a/doc/example/hybridpython.txt
+++ b/doc/example/hybridpython.txt
@@ -90,6 +90,6 @@ using Mono 2.0 and IronPython-1.1 this w
Using IronPython needs more testing, likely newer versions
will work better. please feedback if you have information.
-.. _IronPython: http://www.IronPython.org
+.. _IronPython: http://ironpython.net
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -41,7 +41,8 @@ Known uses
* `py.test`_ uses it for its `distributed testing`_ mechanism.
* Jacob Perkins uses it for his `Distributed NTLK with execnet`_
- project to launch computation processes through ssh.
+ project to launch computation processes through ssh. He also
+ compares `disco and execnet`_ in a subsequent post.
* Ronny Pfannschmidt uses it for his `anyvc`_ VCS-abstraction project
to bridge the Python2/Python3 version gap.
@@ -51,6 +52,7 @@ Known uses
.. _`py.test`: http://pytest.org
.. _`distributed testing`: http://codespeak.net/py/dist/test/dist.html
.. _`Distributed NTLK with execnet`: http://streamhacker.com/2009/11/29/distributed-nltk-execnet/
+.. _`disco and execnet`: http://streamhacker.com/2009/12/14/execnet-disco-distributed-nltk/
.. _`anyvc`: http://bitbucket.org/RonnyPfannschmidt/anyvc/
Project status
From commits-noreply at bitbucket.org Sun Dec 20 17:10:09 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 20 Dec 2009 16:10:09 +0000 (UTC)
Subject: [execnet-commit] execnet commit 8a4fe45e6508: avoid unneccessary
lower()
Message-ID: <20091220161009.D07437EE8A@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261314724 -3600
# Node ID 8a4fe45e650874536a521f736ada5b93a84b2614
# Parent 9068ad88e86a345d49d29a36fa3d20b28f18cac6
avoid unneccessary lower()
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -896,14 +896,14 @@ class Serializer(object):
try:
dispatch = self.dispatch[tp]
except KeyError:
- methodname = 'save_' + tp.__name__.lower()
+ methodname = 'save_' + tp.__name__
meth = getattr(self, methodname, None)
if meth is None:
raise SerializationError("can't serialize %s" % (tp,))
dispatch = self.dispatch[tp] = meth
dispatch(obj)
- def save_nonetype(self, non):
+ def save_NoneType(self, non):
self._write(opcode.NONE)
def save_bool(self, boolean):
From commits-noreply at bitbucket.org Sun Dec 20 17:10:12 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 20 Dec 2009 16:10:12 +0000 (UTC)
Subject: [execnet-commit] execnet commit f9a82b8fb819: generalize
channel-over-channel sending, add examples.
Message-ID: <20091220161012.06BD37EEE2@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261323576 -3600
# Node ID f9a82b8fb819440a42ffca843ff065e001d29fba
# Parent 8a4fe45e650874536a521f736ada5b93a84b2614
generalize channel-over-channel sending, add examples.
--- a/doc/example/test_info.txt
+++ b/doc/example/test_info.txt
@@ -1,10 +1,23 @@
basic local and remote communication
=========================================
-Comparing current working directories
+Execute code in subprocess, communicate through a channel
+---------------------------------------------------------
+
+You can instantiate a subprocess gateway, execute code
+in it and exchange data dynamically::
+
+ >>> import execnet
+ >>> gw = execnet.makegateway()
+ >>> channel = gw.remote_exec("channel.send(channel.receive()+1)")
+ >>> channel.send(1)
+ >>> channel.receive()
+ 2
+
+Compare current working directories
----------------------------------------
-A local popen gateway has the same working directory as the instantiatior::
+A local subprocess gateway has the same working directory as the instantiatior::
>>> import execnet, os
>>> gw = execnet.makegateway()
@@ -14,7 +27,7 @@ A local popen gateway has the same worki
"ssh" gateways default to the login home directory.
-Getting information from remote ssh account
+Get information from remote ssh account
--------------------------------------------
Use simple execution to obtain information from remote environments::
@@ -31,6 +44,43 @@ Use simple execution to obtain informati
>>> version_info
(2, 4, 2, 'final', 0)
+Use a callback instead of receive() and wait for completion
+-------------------------------------------------------------
+
+Set a channel callback to immediately react on incoming data::
+
+ >>> import execnet
+ >>> gw = execnet.makegateway()
+ >>> channel = gw.remote_exec("for i in range(10): channel.send(i)")
+ >>> l = []
+ >>> channel.setcallback(l.append, endmarker=None)
+ >>> channel.waitclose() # waits for closing, i.e. remote exec finish
+ >>> l
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, None]
+
+Note that the callback function will execute in the receiver thread
+so it should not block on IO or long to execute.
+
+Sending channels over channels
+------------------------------------------------------
+
+You can create and transfer a channel over an existing channel
+and use it to transfer information::
+
+ >>> import execnet
+ >>> gw = execnet.makegateway()
+ >>> channel = gw.remote_exec("""
+ ... ch1, ch2 = channel.receive()
+ ... ch2.send("world")
+ ... ch1.send("hello")
+ ... """)
+ >>> c1 = gw.newchannel() # create new channel
+ >>> c2 = gw.newchannel() # create another channel
+ >>> channel.send((c1, c2)) # send them over
+ >>> c1.receive()
+ 'hello'
+ >>> c2.receive()
+ 'world'
Avoid "inlined" source strings with remote_exec
--------------------------------------------------------------
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -141,13 +141,6 @@ def _setupmessages():
channel = gateway._channelfactory.new(self.channelid)
gateway._local_schedulexec(channel=channel, sourcetask=self.data)
- class CHANNEL_NEW(Message):
- def received(self, gateway):
- """ receive a remotely created new (sub)channel. """
- newid = self.data
- newchannel = gateway._channelfactory.new(newid)
- gateway._channelfactory._local_receive(self.channelid, newchannel)
-
class CHANNEL_DATA(Message):
def received(self, gateway):
gateway._channelfactory._local_receive(self.channelid, self.data)
@@ -172,7 +165,7 @@ def _setupmessages():
raise SystemExit(0)
classes = [
- STATUS, CHANNEL_OPEN, CHANNEL_NEW, CHANNEL_DATA, CHANNEL_CLOSE,
+ STATUS, CHANNEL_OPEN, CHANNEL_DATA, CHANNEL_CLOSE,
CHANNEL_CLOSE_ERROR, CHANNEL_LAST_MESSAGE, GATEWAY_TERMINATE
]
for i, cls in enumerate(classes):
@@ -385,10 +378,7 @@ class Channel(object):
"""
if self.isclosed():
raise IOError("cannot send to %r" %(self,))
- if isinstance(item, Channel):
- data = Message.CHANNEL_NEW(self.id, item.id)
- else:
- data = Message.CHANNEL_DATA(self.id, item)
+ data = Message.CHANNEL_DATA(self.id, item)
self.gateway._send(data)
def receive(self, timeout=-1):
@@ -455,8 +445,10 @@ class ChannelFactory(object):
if id is None:
id = self.count
self.count += 2
- channel = Channel(self.gateway, id)
- self._channels[id] = channel
+ try:
+ channel = self._channels[id]
+ except KeyError:
+ channel = self._channels[id] = Channel(self.gateway, id)
return channel
finally:
self._writelock.release()
@@ -597,7 +589,7 @@ class BaseGateway(object):
def _thread_receiver(self):
self._trace("starting to receive")
- unserializer = Unserializer(self._io)
+ unserializer = Unserializer(self._io, self._channelfactory)
try:
while 1:
try:
@@ -607,6 +599,7 @@ class BaseGateway(object):
_receivelock.acquire()
try:
msg.received(self)
+ del msg
finally:
_receivelock.release()
except sysex:
@@ -639,6 +632,7 @@ class BaseGateway(object):
# _____________________________________________________________________
#
def newchannel(self):
+ """ return a new independent channel. """
return self._channelfactory.new()
def join(self, timeout=None):
@@ -729,8 +723,9 @@ class Unserializer(object):
py2str_as_py3str = True # True
py3str_as_py2str = False # false means py2 will get unicode
- def __init__(self, stream):
+ def __init__(self, stream, channelfactory=None):
self.stream = stream
+ self.channelfactory = channelfactory
def load(self):
self.stack = []
@@ -748,7 +743,7 @@ class Unserializer(object):
except _Stop:
if len(self.stack) != 1:
raise UnserializationError("internal unserialization error")
- return self.stack[0]
+ return self.stack.pop(0)
else:
raise UnserializationError("didn't get STOP")
@@ -851,6 +846,11 @@ class Unserializer(object):
def load_stop(self):
raise _Stop
+ def load_channel(self):
+ id = self._read_int4()
+ newchannel = self.channelfactory.new(id)
+ self.stack.append(newchannel)
+
# automatically build opcodes and byte-encoding
class opcode:
@@ -998,6 +998,10 @@ class Serializer(object):
def save_frozenset(self, s):
self._write_set(s, opcode.FROZENSET)
+ def save_Channel(self, channel):
+ self._write(opcode.CHANNEL)
+ self._write_int4(channel.id)
+
def init_popen_io():
if not hasattr(os, 'dup'): # jython
io = Popen2IO(sys.stdout, sys.stdin)
--- a/doc/basics.txt
+++ b/doc/basics.txt
@@ -61,6 +61,7 @@ get sent for remote execution. ``remote
a channel object whose symmetric counterpart channel
is available to the remotely executing source.
+
.. _`Channel`:
.. _`channel-api`:
@@ -83,6 +84,7 @@ two asynchronously running programs.
.. autoattribute:: Channel.RemoteError
.. autoattribute:: Channel.TimeoutError
+
.. _Group:
Grouped Gateways and robust termination
--- a/testing/test_channel.py
+++ b/testing/test_channel.py
@@ -88,6 +88,30 @@ class TestChannelBasicBehaviour:
l = list(channel)
assert l == [0, 1, 2]
+ def test_channel_pass_in_structure(self, gw):
+ channel = gw.remote_exec('''
+ ch1, ch2 = channel.receive()
+ data = ch1.receive()
+ ch2.send(data+1)
+ ''')
+ newchan1 = gw.newchannel()
+ newchan2 = gw.newchannel()
+ channel.send((newchan1, newchan2))
+ newchan1.send(1)
+ data = newchan2.receive()
+ assert data ==2
+
+ def test_channel_multipass(self, gw):
+ channel = gw.remote_exec('''
+ channel.send(channel)
+ xchan = channel.receive()
+ assert xchan == channel
+ ''')
+ newchan = channel.receive()
+ assert newchan == channel
+ channel.send(newchan)
+ channel.waitclose()
+
def test_channel_passing_over_channel(self, gw):
channel = gw.remote_exec('''
c = channel.gateway.newchannel()
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,10 @@
1.x (pending)
--------------------------------
+- generalize channel-over-channel sending: you can now have channels
+ anywhere in a data structure (i.e. as an item of a container type).
+ Add according examples.
+
- internally split socket gateways, speeds up popen-gateways
by 10% (now at 30 milliseconds per-gateway on my 3-year old machine)
From commits-noreply at bitbucket.org Sun Dec 20 17:10:14 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 20 Dec 2009 16:10:14 +0000 (UTC)
Subject: [execnet-commit] execnet commit 1d9442698ab8: rename CHANNEL_OPEN
to CHANNEL_EXEC and add test_debug.txt to versioning
Message-ID: <20091220161014.621667EEEF@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261324797 -3600
# Node ID 1d9442698ab81618293b3436d304d4808b528aaf
# Parent f9a82b8fb819440a42ffca843ff065e001d29fba
rename CHANNEL_OPEN to CHANNEL_EXEC and add test_debug.txt to versioning
--- /dev/null
+++ b/doc/example/test_debug.txt
@@ -0,0 +1,43 @@
+
+Debugging execnet / Wire messages
+===============================================================
+
+By setting the environment variable ``EXECNET_DEBUG`` you can
+configure the execnet tracing mechanism:
+
+:EXECNET_DEBUG=1: write per-process trace-files to ``${TEMPROOT}/execnet-debug-PID``
+:EXECNET_DEBUG=2: perform tracing to stderr (popen-gateway slaves will send this to their instantiator)
+
+Here is a simple example to see what goes on with a simple execution::
+
+ EXECNET_DEBUG=2 # or "set EXECNET_DEBUG=2" on windows
+
+ python -c 'import execnet ; execnet.makegateway().remote_exec("42")'
+
+which will show PID-prefixed trace entries::
+
+ [2326] gw0 starting to receive
+ [2326] gw0 sent
+ [2327] creating slavegateway on
+ [2327] gw0-slave starting to receive
+ [2327] gw0-slave received
+ [2327] gw0-slave execution starts[1]: '42'
+ [2327] gw0-slave execution finished
+ [2327] gw0-slave sent
+ [2327] gw0-slave 1 sent channel close message
+ [2326] gw0 received
+ [2326] gw0 1 channel.__del__
+ [2326] === atexit cleanup ===
+ [2326] gw0 gateway.exit() called
+ [2326] gw0 --> sending GATEWAY_TERMINATE
+ [2326] gw0 sent
+ [2326] gw0 joining receiver thread
+ [2327] gw0-slave received
+ [2327] gw0-slave putting None to execqueue
+ [2327] gw0-slave io.close_read()
+ [2327] gw0-slave leaving
+ [2327] gw0-slave 1 channel.__del__
+ [2327] gw0-slave io.close_write()
+ [2327] gw0-slave slavegateway.serve finished
+ [2327] gw0-slave gateway.join() called while receiverthread already finished
+ [2326] gw0 leaving
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -93,7 +93,7 @@ class Gateway(gateway_base.BaseGateway):
else:
source = textwrap.dedent(str(source))
channel = self.newchannel()
- self._send(Message.CHANNEL_OPEN(channel.id, source))
+ self._send(Message.CHANNEL_EXEC(channel.id, source))
return channel
def remote_init_threads(self, num=None):
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -136,7 +136,7 @@ def _setupmessages():
}
gateway._send(Message.CHANNEL_DATA(self.channelid, d))
- class CHANNEL_OPEN(Message):
+ class CHANNEL_EXEC(Message):
def received(self, gateway):
channel = gateway._channelfactory.new(self.channelid)
gateway._local_schedulexec(channel=channel, sourcetask=self.data)
@@ -165,7 +165,7 @@ def _setupmessages():
raise SystemExit(0)
classes = [
- STATUS, CHANNEL_OPEN, CHANNEL_DATA, CHANNEL_CLOSE,
+ STATUS, CHANNEL_EXEC, CHANNEL_DATA, CHANNEL_CLOSE,
CHANNEL_CLOSE_ERROR, CHANNEL_LAST_MESSAGE, GATEWAY_TERMINATE
]
for i, cls in enumerate(classes):
From commits-noreply at bitbucket.org Sun Dec 20 17:15:08 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 20 Dec 2009 16:15:08 +0000 (UTC)
Subject: [execnet-commit] execnet commit bbd403fddf54: remove the just
committed issues
Message-ID: <20091220161508.D32C37EEDE@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261325694 -3600
# Node ID bbd403fddf5418af115c8ece205ce688b69052e0
# Parent 1d9442698ab81618293b3436d304d4808b528aaf
remove the just committed issues
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -79,24 +79,6 @@ some first simple implemenation might lo
subchannel = ch.receive()
return ChannelGateway(subchannel)
-serialize channels everywhere
--------------------------------------------------
-tags: 1.0.2 feature
-
-currently sending channels only works if they are send
-as a single item. So they cannot be contained in a list or dict.
-By making channel passing a feature of the serializer we should
-be able to lift this limitation, allowing channels in all places.
-
-creating new channels
--------------------------------------------------
-tags: 1.0.2 feature
-
-document, provide examples and refine support for creating new channels.
-Defining closing semantics - does a channel created from a channel
-automatically close when its parent channel closes? This currently
-depends on whether it is garbage collected.
-
creating virtualenv environments on the fly
-----------------------------------------------
tags: 1.1 wish
From commits-noreply at bitbucket.org Sun Dec 20 19:24:57 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 20 Dec 2009 18:24:57 +0000 (UTC)
Subject: [execnet-commit] execnet commit 7c99c22b2bbb: better handle errors
in callbacks, closing the channel and leaving
Message-ID: <20091220182457.C88097EEE2@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261332944 -3600
# Node ID 7c99c22b2bbb4b217641fff9122588bf8d41ed6e
# Parent bbd403fddf5418af115c8ece205ce688b69052e0
better handle errors in callbacks, closing the channel and leaving
other execution/communication useable
--- a/ISSUES.txt
+++ b/ISSUES.txt
@@ -1,15 +1,3 @@
-report back errors when calling callbacks
----------------------------------------------------
-
-tags: 1.0
-bb: http://bitbucket.org/hpk42/py-trunk/issue/37/
-path: execnet/gateway_base.py
-
-Callbacks are invoked in the receiver-thread
-and any exception will kill the whole receiver-thread.
-This is especially bad if it happens remotely as
-it leaves the other side inaccesssible.
-
rsync links from posix to windows
-----------------------------------------
tags: 1.1 feature
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -502,7 +502,16 @@ class ChannelFactory(object):
else:
queue.put(data)
else:
- callback(data) # even if channel may be already closed
+ try:
+ callback(data) # even if channel may be already closed
+ except KeyboardInterrupt:
+ raise
+ except:
+ excinfo = sys.exc_info()
+ self.gateway._trace("exception during callback: %s" % excinfo[1])
+ errortext = geterrortext(excinfo)
+ self.gateway._send(Message.CHANNEL_CLOSE_ERROR(id, errortext))
+ self._local_close(id, errortext)
def _finished_receiving(self):
self._writelock.acquire()
--- a/testing/test_channel.py
+++ b/testing/test_channel.py
@@ -244,6 +244,24 @@ class TestChannelBasicBehaviour:
assert err
assert str(err).find("ValueError") != -1
+ def test_channel_callback_error(self, gw):
+ channel = gw.remote_exec("""
+ def f(item):
+ raise ValueError(42)
+ ch = channel.gateway.newchannel()
+ ch.setcallback(f)
+ channel.send(ch)
+ channel.receive()
+ assert ch.isclosed()
+ """)
+ subchan = channel.receive()
+ subchan.send(1)
+ excinfo = py.test.raises(subchan.RemoteError,
+ "subchan.waitclose(TESTTIMEOUT)")
+ assert "42" in excinfo.value.formatted
+ channel.send(1)
+ channel.waitclose()
+
class TestChannelFile:
def test_channel_file_write(self, gw):
channel = gw.remote_exec("""
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,11 @@ 1.x (pending)
anywhere in a data structure (i.e. as an item of a container type).
Add according examples.
+- automatically close a channel when a remote callback raises
+ an exception, makes communication more robust because until
+ now failing callbacks rendered the receiverthread unuseable
+ leaving the remote side in-accessible.
+
- internally split socket gateways, speeds up popen-gateways
by 10% (now at 30 milliseconds per-gateway on my 3-year old machine)
From commits-noreply at bitbucket.org Sun Dec 20 19:40:30 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 20 Dec 2009 18:40:30 +0000 (UTC)
Subject: [execnet-commit] execnet commit 755fc3032507: fixing some python3
issues, everything passes
Message-ID: <20091220184030.1E5777EEE2@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261334418 -3600
# Node ID 755fc30325075889d3abc2ea9cf7b4d324bad848
# Parent 7c99c22b2bbb4b217641fff9122588bf8d41ed6e
fixing some python3 issues, everything passes
--- a/testing/test_basics.py
+++ b/testing/test_basics.py
@@ -11,11 +11,11 @@ def test_subprocess_interaction(anypytho
popen = subprocess.Popen(args, bufsize=0, universal_newlines=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def send(line):
- popen.stdin.write(line.encode('ascii'))
+ popen.stdin.write(line)
if sys.version_info > (3,0) or sys.platform.startswith("java"):
popen.stdin.flush()
def receive():
- return popen.stdout.readline().decode('ascii')
+ return popen.stdout.readline()
try:
source = py.code.Source(read_write_loop, "read_write_loop()")
--- a/testing/test_multi.py
+++ b/testing/test_multi.py
@@ -121,9 +121,9 @@ class TestGroup:
assert len(gwlist) == 3
idlist = [x.id for x in gwlist]
assert idlist == list('325')
- print group
+ print (group)
group.terminate()
- print group
+ print (group)
assert not group
assert repr(group) == ""
--- a/conftest.py
+++ b/conftest.py
@@ -73,7 +73,7 @@ def getexecutable(name, cache={}):
if executable:
if name == "jython":
popen = subprocess.Popen([str(executable), "--version"],
- stderr=subprocess.PIPE)
+ universal_newlines=True, stderr=subprocess.PIPE)
out, err = popen.communicate()
if not err or "2.5" not in err:
executable = None
From commits-noreply at bitbucket.org Sun Dec 20 22:43:05 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Sun, 20 Dec 2009 21:43:05 +0000 (UTC)
Subject: [execnet-commit] execnet commit 6007ee61a482: bumping version to
1.0.2, fixing bits, removing unused imports
Message-ID: <20091220214305.05EAE7EE8A@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261345365 -3600
# Node ID 6007ee61a482f19d765917da3d75a25ff2049793
# Parent 755fc30325075889d3abc2ea9cf7b4d324bad848
bumping version to 1.0.2, fixing bits, removing unused imports
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,4 @@
-1.x (pending)
+1.0.2
--------------------------------
- generalize channel-over-channel sending: you can now have channels
@@ -11,7 +11,7 @@ 1.x (pending)
leaving the remote side in-accessible.
- internally split socket gateways, speeds up popen-gateways
- by 10% (now at 30 milliseconds per-gateway on my 3-year old machine)
+ by 10% (now at <50 milliseconds per-gateway on a 1.5 GHZ machine)
1.0.1
--------------------------------
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -6,8 +6,8 @@
Python_ is a mature dynamic language whose interpreters can interact with
all major computing platforms today.
-**execnet** provides carefully tested means to ad-hoc interact with Python
-interpreters across version, platform and network barriers. It provides
+**execnet** provides carefully tested means to easily interact with Python
+interpreters across version, platform and network barriers. It has
a minimal and fast API targetting the following uses:
* distribute tasks to local or remote CPUs
@@ -28,7 +28,7 @@ Features
* easy creation, handling and termination of multiple processes
-* well tested interactions between CPython 2.4-3.1, Jython 2.5.1
+* well tested interactions between CPython 2.4-2.7, CPython3.1, Jython 2.5.1
and PyPy 1.1 interpreters.
* fully interoperable between Windows and Unix-ish systems.
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -46,7 +46,7 @@ copyright = '2009, holger krekel and oth
#
# The short X.Y version.
import execnet
-version = "1.0.1"
+version = "1.0.2"
# The full version, including alpha/beta/rc tags.
release = version
--- a/execnet/gateway.py
+++ b/execnet/gateway.py
@@ -3,7 +3,7 @@ gateway code for initiating popen, socke
(c) 2004-2009, Holger Krekel and others
"""
-import sys, os, inspect, socket, types
+import sys, os, inspect, types
import textwrap
import execnet
from execnet.gateway_base import Message, Popen2IO
--- a/execnet/__init__.py
+++ b/execnet/__init__.py
@@ -1,10 +1,9 @@
"""
-execnet: Elastic Python Deployment.
-package for connecting to local and remote Python Interpreters.
+execnet: pure python lib for connecting to local and remote Python Interpreters.
(c) 2009, Holger Krekel and others
"""
-__version__ = "1.0.1.post1"
+__version__ = "1.0.2"
import execnet.apipkg
--- a/execnet/gateway_socket.py
+++ b/execnet/gateway_socket.py
@@ -2,6 +2,9 @@ import socket
from execnet.gateway import Gateway, HostNotFound
import os, sys, inspect
+try: bytes
+except NameError: bytes = str
+
class SocketIO:
error = (socket.error, EOFError)
def __init__(self, sock):
@@ -10,7 +13,6 @@ class SocketIO:
sock.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10)# IPTOS_LOWDELAY
sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
except (AttributeError, socket.error):
- e = sys.exc_info()[1]
sys.stderr.write("WARNING: cannot set socketoption")
def read(self, numbytes):
@@ -24,7 +26,6 @@ class SocketIO:
return buf
def write(self, data):
- assert isinstance(data, bytes)
self.sock.sendall(data)
def close_read(self):
From commits-noreply at bitbucket.org Wed Dec 23 20:17:40 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 23 Dec 2009 19:17:40 +0000 (UTC)
Subject: [execnet-commit] execnet commit 58aeb70cf80a: fix internal timeout
waiting bug (thanks ronny)
Message-ID: <20091223191740.70A4E7EF2A@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261595843 -3600
# Node ID 58aeb70cf80a127af7ebc8193396c0f5bc2ee248
# Parent 6007ee61a482f19d765917da3d75a25ff2049793
fix internal timeout waiting bug (thanks ronny)
--- a/execnet/gateway_base.py
+++ b/execnet/gateway_base.py
@@ -212,6 +212,7 @@ class Channel(object):
"""Communication channel between two Python Interpreter execution points."""
RemoteError = RemoteError
TimeoutError = TimeoutError
+ _INTERNALWAKEUP = 1000
_executing = False
def __init__(self, gateway, id):
@@ -395,7 +396,7 @@ class Channel(object):
if itemqueue is None:
raise IOError("cannot receive(), channel has receiver callback")
if timeout < 0:
- internal_timeout = 1000
+ internal_timeout = self._INTERNALWAKEUP
else:
internal_timeout = timeout
@@ -404,7 +405,7 @@ class Channel(object):
x = itemqueue.get(timeout=internal_timeout)
break
except queue.Empty:
- if internal_timeout < 0:
+ if timeout < 0:
continue
raise self.TimeoutError("no item after %r seconds" %(timeout))
if x is ENDMARKER:
--- a/testing/test_channel.py
+++ b/testing/test_channel.py
@@ -41,6 +41,15 @@ class TestChannelBasicBehaviour:
channel.send(1)
x = channel.receive(timeout=0.1)
+ def test_channel_receive_internal_timeout(self, gw, monkeypatch):
+ channel = gw.remote_exec("""
+ import time
+ time.sleep(0.5)
+ channel.send(1)
+ """)
+ monkeypatch.setattr(channel.__class__, '_INTERNALWAKEUP', 0.2)
+ x = channel.receive()
+
def test_channel_close_and_then_receive_error_multiple(self, gw):
channel = gw.remote_exec('channel.send(42) ; raise ValueError')
x = channel.receive()
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -13,6 +13,9 @@ 1.0.2
- internally split socket gateways, speeds up popen-gateways
by 10% (now at <50 milliseconds per-gateway on a 1.5 GHZ machine)
+- fix bug in channel.receive() that would wrongly raise a TimeoutError
+ after 1000 seconds (thanks Ronny Pfannschmidt)
+
1.0.1
--------------------------------
From commits-noreply at bitbucket.org Wed Dec 23 21:30:17 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 23 Dec 2009 20:30:17 +0000 (UTC)
Subject: [execnet-commit] execnet commit 330b0032a09e: fixes for jython
Message-ID: <20091223203017.32CF67EF3F@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261600136 -3600
# Node ID 330b0032a09e13e38644c74d4a679d3760cdcdcf
# Parent 58aeb70cf80a127af7ebc8193396c0f5bc2ee248
fixes for jython
--- a/testing/test_termination.py
+++ b/testing/test_termination.py
@@ -74,6 +74,7 @@ def test_close_initiating_remote_no_erro
stdout, stderr = popen.communicate()
assert not stderr
+ at py.test.mark.skipif("not hasattr(os, 'dup')")
def test_terminate_implicit_does_trykill(testdir, anypython, capfd):
p = testdir.makepyfile("""
import sys
--- a/testing/test_gateway.py
+++ b/testing/test_gateway.py
@@ -49,13 +49,14 @@ class TestBasicGateway:
assert numchan2 == numchan
def test_gateway_status_busy(self, gw):
+ numchannels = gw.remote_status().numchannels
ch1 = gw.remote_exec("channel.send(1); channel.receive()")
ch2 = gw.remote_exec("channel.receive()")
ch1.receive()
status = gw.remote_status()
assert status.numexecuting == 1 # number of active execution threads
assert status.execqsize == 1 # one more queued
- assert status.numchannels == 2
+ assert status.numchannels == numchannels + 2
ch1.send(None)
ch2.send(None)
ch1.waitclose()
From commits-noreply at bitbucket.org Wed Dec 23 21:33:54 2009
From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org)
Date: Wed, 23 Dec 2009 20:33:54 +0000 (UTC)
Subject: [execnet-commit] execnet commit 73176c8168f2: Added tag 1.0.2 for
changeset 330b0032a09e
Message-ID: <20091223203354.938F97EF1E@bitbucket.org>
# HG changeset patch -- Bitbucket.org
# Project execnet
# URL http://bitbucket.org/hpk42/execnet/overview/
# User holger krekel
# Date 1261600417 -3600
# Node ID 73176c8168f280a27ee9e2b79ab6b3488a943e23
# Parent 330b0032a09e13e38644c74d4a679d3760cdcdcf
Added tag 1.0.2 for changeset 330b0032a09e
--- a/.hgtags
+++ b/.hgtags
@@ -3,3 +3,4 @@ 7cf86a1811b11aa58112a255e6afba870c8d855e
7cf86a1811b11aa58112a255e6afba870c8d855e 1.0.0
5d1c438e335dc369834207882ec5f3227d365c11 1.0.0
66760dc71203dbceeb08851cf4cf74e2687156d0 1.0.1
+330b0032a09e13e38644c74d4a679d3760cdcdcf 1.0.2