|
|
@ -26,6 +26,12 @@
|
|
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
This module implements most package functionality, but remains separate from
|
|
|
|
|
|
|
|
non-essential code in order to reduce its size, since it is also serves as the
|
|
|
|
|
|
|
|
bootstrap implementation sent to every new slave context.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import collections
|
|
|
|
import collections
|
|
|
|
import errno
|
|
|
|
import errno
|
|
|
|
import fcntl
|
|
|
|
import fcntl
|
|
|
@ -33,7 +39,6 @@ import imp
|
|
|
|
import itertools
|
|
|
|
import itertools
|
|
|
|
import logging
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import os
|
|
|
|
import select
|
|
|
|
|
|
|
|
import signal
|
|
|
|
import signal
|
|
|
|
import socket
|
|
|
|
import socket
|
|
|
|
import struct
|
|
|
|
import struct
|
|
|
@ -45,6 +50,9 @@ import warnings
|
|
|
|
import weakref
|
|
|
|
import weakref
|
|
|
|
import zlib
|
|
|
|
import zlib
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Absolute imports for <2.5.
|
|
|
|
|
|
|
|
select = __import__('select')
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
import cPickle
|
|
|
|
import cPickle
|
|
|
|
except ImportError:
|
|
|
|
except ImportError:
|
|
|
@ -67,6 +75,10 @@ IOLOG.setLevel(logging.INFO)
|
|
|
|
_v = False
|
|
|
|
_v = False
|
|
|
|
_vv = False
|
|
|
|
_vv = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Also taken by Broker, no blocking work can occur with it held.
|
|
|
|
|
|
|
|
_service_call_lock = threading.Lock()
|
|
|
|
|
|
|
|
_service_calls = []
|
|
|
|
|
|
|
|
|
|
|
|
GET_MODULE = 100
|
|
|
|
GET_MODULE = 100
|
|
|
|
CALL_FUNCTION = 101
|
|
|
|
CALL_FUNCTION = 101
|
|
|
|
FORWARD_LOG = 102
|
|
|
|
FORWARD_LOG = 102
|
|
|
@ -75,9 +87,16 @@ DEL_ROUTE = 104
|
|
|
|
ALLOCATE_ID = 105
|
|
|
|
ALLOCATE_ID = 105
|
|
|
|
SHUTDOWN = 106
|
|
|
|
SHUTDOWN = 106
|
|
|
|
LOAD_MODULE = 107
|
|
|
|
LOAD_MODULE = 107
|
|
|
|
DETACHING = 108
|
|
|
|
FORWARD_MODULE = 108
|
|
|
|
|
|
|
|
DETACHING = 109
|
|
|
|
|
|
|
|
CALL_SERVICE = 110
|
|
|
|
IS_DEAD = 999
|
|
|
|
IS_DEAD = 999
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
BaseException
|
|
|
|
|
|
|
|
except NameError:
|
|
|
|
|
|
|
|
BaseException = Exception
|
|
|
|
|
|
|
|
|
|
|
|
PY3 = sys.version_info > (3,)
|
|
|
|
PY3 = sys.version_info > (3,)
|
|
|
|
if PY3:
|
|
|
|
if PY3:
|
|
|
|
b = lambda s: s.encode('latin-1')
|
|
|
|
b = lambda s: s.encode('latin-1')
|
|
|
@ -134,7 +153,7 @@ class Secret(UnicodeType):
|
|
|
|
|
|
|
|
|
|
|
|
class CallError(Error):
|
|
|
|
class CallError(Error):
|
|
|
|
def __init__(self, fmt=None, *args):
|
|
|
|
def __init__(self, fmt=None, *args):
|
|
|
|
if not isinstance(fmt, Exception):
|
|
|
|
if not isinstance(fmt, BaseException):
|
|
|
|
Error.__init__(self, fmt, *args)
|
|
|
|
Error.__init__(self, fmt, *args)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
e = fmt
|
|
|
|
e = fmt
|
|
|
@ -229,7 +248,7 @@ def io_op(func, *args):
|
|
|
|
while True:
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
return func(*args), False
|
|
|
|
return func(*args), False
|
|
|
|
except (select.error, OSError):
|
|
|
|
except (select.error, OSError, IOError):
|
|
|
|
e = sys.exc_info()[1]
|
|
|
|
e = sys.exc_info()[1]
|
|
|
|
_vv and IOLOG.debug('io_op(%r) -> OSError: %s', func, e)
|
|
|
|
_vv and IOLOG.debug('io_op(%r) -> OSError: %s', func, e)
|
|
|
|
if e[0] == errno.EINTR:
|
|
|
|
if e[0] == errno.EINTR:
|
|
|
@ -267,6 +286,9 @@ class PidfulStreamHandler(logging.StreamHandler):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def enable_debug_logging():
|
|
|
|
def enable_debug_logging():
|
|
|
|
|
|
|
|
global _v, _vv
|
|
|
|
|
|
|
|
_v = True
|
|
|
|
|
|
|
|
_vv = True
|
|
|
|
root = logging.getLogger()
|
|
|
|
root = logging.getLogger()
|
|
|
|
root.setLevel(logging.DEBUG)
|
|
|
|
root.setLevel(logging.DEBUG)
|
|
|
|
IOLOG.setLevel(logging.DEBUG)
|
|
|
|
IOLOG.setLevel(logging.DEBUG)
|
|
|
@ -280,9 +302,11 @@ def enable_debug_logging():
|
|
|
|
|
|
|
|
|
|
|
|
_profile_hook = lambda name, func, *args: func(*args)
|
|
|
|
_profile_hook = lambda name, func, *args: func(*args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def enable_profiling():
|
|
|
|
def enable_profiling():
|
|
|
|
global _profile_hook
|
|
|
|
global _profile_hook
|
|
|
|
import cProfile, pstats
|
|
|
|
import cProfile
|
|
|
|
|
|
|
|
import pstats
|
|
|
|
def _profile_hook(name, func, *args):
|
|
|
|
def _profile_hook(name, func, *args):
|
|
|
|
profiler = cProfile.Profile()
|
|
|
|
profiler = cProfile.Profile()
|
|
|
|
profiler.enable()
|
|
|
|
profiler.enable()
|
|
|
@ -299,6 +323,13 @@ def enable_profiling():
|
|
|
|
fp.close()
|
|
|
|
fp.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def import_module(modname):
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Import `module` and return the attribute named `attr`.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
return __import__(modname, None, None, [''])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Message(object):
|
|
|
|
class Message(object):
|
|
|
|
dst_id = None
|
|
|
|
dst_id = None
|
|
|
|
src_id = None
|
|
|
|
src_id = None
|
|
|
@ -363,7 +394,10 @@ class Message(object):
|
|
|
|
msg.dst_id = self.src_id
|
|
|
|
msg.dst_id = self.src_id
|
|
|
|
msg.handle = self.reply_to
|
|
|
|
msg.handle = self.reply_to
|
|
|
|
vars(msg).update(kwargs)
|
|
|
|
vars(msg).update(kwargs)
|
|
|
|
(self.router or router).route(msg)
|
|
|
|
if msg.handle:
|
|
|
|
|
|
|
|
(self.router or router).route(msg)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
LOG.debug('Message.reply(): discarding due to zero handle: %r', msg)
|
|
|
|
|
|
|
|
|
|
|
|
def unpickle(self, throw=True, throw_dead=True):
|
|
|
|
def unpickle(self, throw=True, throw_dead=True):
|
|
|
|
"""Deserialize `data` into an object."""
|
|
|
|
"""Deserialize `data` into an object."""
|
|
|
@ -522,6 +556,7 @@ class Importer(object):
|
|
|
|
'lxc',
|
|
|
|
'lxc',
|
|
|
|
'master',
|
|
|
|
'master',
|
|
|
|
'parent',
|
|
|
|
'parent',
|
|
|
|
|
|
|
|
'select',
|
|
|
|
'service',
|
|
|
|
'service',
|
|
|
|
'setns',
|
|
|
|
'setns',
|
|
|
|
'ssh',
|
|
|
|
'ssh',
|
|
|
@ -753,6 +788,7 @@ class Side(object):
|
|
|
|
def __init__(self, stream, fd, cloexec=True, keep_alive=True):
|
|
|
|
def __init__(self, stream, fd, cloexec=True, keep_alive=True):
|
|
|
|
self.stream = stream
|
|
|
|
self.stream = stream
|
|
|
|
self.fd = fd
|
|
|
|
self.fd = fd
|
|
|
|
|
|
|
|
self.closed = False
|
|
|
|
self.keep_alive = keep_alive
|
|
|
|
self.keep_alive = keep_alive
|
|
|
|
self._fork_refs[id(self)] = self
|
|
|
|
self._fork_refs[id(self)] = self
|
|
|
|
if cloexec:
|
|
|
|
if cloexec:
|
|
|
@ -762,21 +798,16 @@ class Side(object):
|
|
|
|
def __repr__(self):
|
|
|
|
def __repr__(self):
|
|
|
|
return '<Side of %r fd %s>' % (self.stream, self.fd)
|
|
|
|
return '<Side of %r fd %s>' % (self.stream, self.fd)
|
|
|
|
|
|
|
|
|
|
|
|
def fileno(self):
|
|
|
|
|
|
|
|
if self.fd is None:
|
|
|
|
|
|
|
|
raise StreamError('%r.fileno() called but no FD set', self)
|
|
|
|
|
|
|
|
return self.fd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
@classmethod
|
|
|
|
def _on_fork(cls):
|
|
|
|
def _on_fork(cls):
|
|
|
|
for side in list(cls._fork_refs.values()):
|
|
|
|
for side in list(cls._fork_refs.values()):
|
|
|
|
side.close()
|
|
|
|
side.close()
|
|
|
|
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
def close(self):
|
|
|
|
if self.fd is not None:
|
|
|
|
if not self.closed:
|
|
|
|
_vv and IOLOG.debug('%r.close()', self)
|
|
|
|
_vv and IOLOG.debug('%r.close()', self)
|
|
|
|
os.close(self.fd)
|
|
|
|
os.close(self.fd)
|
|
|
|
self.fd = None
|
|
|
|
self.closed = True
|
|
|
|
|
|
|
|
|
|
|
|
def read(self, n=CHUNK_SIZE):
|
|
|
|
def read(self, n=CHUNK_SIZE):
|
|
|
|
s, disconnected = io_op(os.read, self.fd, n)
|
|
|
|
s, disconnected = io_op(os.read, self.fd, n)
|
|
|
@ -800,11 +831,11 @@ class BasicStream(object):
|
|
|
|
|
|
|
|
|
|
|
|
def on_disconnect(self, broker):
|
|
|
|
def on_disconnect(self, broker):
|
|
|
|
LOG.debug('%r.on_disconnect()', self)
|
|
|
|
LOG.debug('%r.on_disconnect()', self)
|
|
|
|
broker.stop_receive(self)
|
|
|
|
|
|
|
|
broker._stop_transmit(self)
|
|
|
|
|
|
|
|
if self.receive_side:
|
|
|
|
if self.receive_side:
|
|
|
|
|
|
|
|
broker.stop_receive(self)
|
|
|
|
self.receive_side.close()
|
|
|
|
self.receive_side.close()
|
|
|
|
if self.transmit_side:
|
|
|
|
if self.transmit_side:
|
|
|
|
|
|
|
|
broker._stop_transmit(self)
|
|
|
|
self.transmit_side.close()
|
|
|
|
self.transmit_side.close()
|
|
|
|
fire(self, 'disconnect')
|
|
|
|
fire(self, 'disconnect')
|
|
|
|
|
|
|
|
|
|
|
@ -974,12 +1005,6 @@ class Context(object):
|
|
|
|
_v and LOG.debug('%r.on_disconnect()', self)
|
|
|
|
_v and LOG.debug('%r.on_disconnect()', self)
|
|
|
|
fire(self, 'disconnect')
|
|
|
|
fire(self, 'disconnect')
|
|
|
|
|
|
|
|
|
|
|
|
def send(self, msg):
|
|
|
|
|
|
|
|
"""send `obj` to `handle`, and tell the broker we have output. May
|
|
|
|
|
|
|
|
be called from any thread."""
|
|
|
|
|
|
|
|
msg.dst_id = self.context_id
|
|
|
|
|
|
|
|
self.router.route(msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def send_async(self, msg, persist=False):
|
|
|
|
def send_async(self, msg, persist=False):
|
|
|
|
if self.router.broker._thread == threading.currentThread(): # TODO
|
|
|
|
if self.router.broker._thread == threading.currentThread(): # TODO
|
|
|
|
raise SystemError('Cannot making blocking call on broker thread')
|
|
|
|
raise SystemError('Cannot making blocking call on broker thread')
|
|
|
@ -992,6 +1017,25 @@ class Context(object):
|
|
|
|
self.send(msg)
|
|
|
|
self.send(msg)
|
|
|
|
return receiver
|
|
|
|
return receiver
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def call_service_async(self, service_name, method_name, **kwargs):
|
|
|
|
|
|
|
|
_v and LOG.debug('%r.call_service_async(%r, %r, %r)',
|
|
|
|
|
|
|
|
self, service_name, method_name, kwargs)
|
|
|
|
|
|
|
|
if not isinstance(service_name, basestring):
|
|
|
|
|
|
|
|
service_name = service_name.name() # Service.name()
|
|
|
|
|
|
|
|
tup = (service_name, method_name, kwargs)
|
|
|
|
|
|
|
|
msg = Message.pickled(tup, handle=CALL_SERVICE)
|
|
|
|
|
|
|
|
return self.send_async(msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def send(self, msg):
|
|
|
|
|
|
|
|
"""send `obj` to `handle`, and tell the broker we have output. May
|
|
|
|
|
|
|
|
be called from any thread."""
|
|
|
|
|
|
|
|
msg.dst_id = self.context_id
|
|
|
|
|
|
|
|
self.router.route(msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def call_service(self, service_name, method_name, **kwargs):
|
|
|
|
|
|
|
|
recv = self.call_service_async(service_name, method_name, **kwargs)
|
|
|
|
|
|
|
|
return recv.get().unpickle()
|
|
|
|
|
|
|
|
|
|
|
|
def send_await(self, msg, deadline=None):
|
|
|
|
def send_await(self, msg, deadline=None):
|
|
|
|
"""Send `msg` and wait for a response with an optional timeout."""
|
|
|
|
"""Send `msg` and wait for a response with an optional timeout."""
|
|
|
|
receiver = self.send_async(msg)
|
|
|
|
receiver = self.send_async(msg)
|
|
|
@ -1014,7 +1058,56 @@ def _unpickle_context(router, context_id, name):
|
|
|
|
return router.context_class(router, context_id, name)
|
|
|
|
return router.context_class(router, context_id, name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Poller(object):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
self._rfds = {}
|
|
|
|
|
|
|
|
self._wfds = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def readers(self):
|
|
|
|
|
|
|
|
return list(self._rfds.items())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def writers(self):
|
|
|
|
|
|
|
|
return list(self._wfds.items())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
|
|
|
return '%s(%#x)' % (type(self).__name__, id(self))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_receive(self, fd, data=None):
|
|
|
|
|
|
|
|
self._rfds[fd] = data or fd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def stop_receive(self, fd):
|
|
|
|
|
|
|
|
self._rfds.pop(fd, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_transmit(self, fd, data=None):
|
|
|
|
|
|
|
|
self._wfds[fd] = data or fd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def stop_transmit(self, fd):
|
|
|
|
|
|
|
|
self._wfds.pop(fd, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def poll(self, timeout=None):
|
|
|
|
|
|
|
|
_vv and IOLOG.debug('%r.poll(%r)', self, timeout)
|
|
|
|
|
|
|
|
(rfds, wfds, _), _ = io_op(select.select,
|
|
|
|
|
|
|
|
self._rfds,
|
|
|
|
|
|
|
|
self._wfds,
|
|
|
|
|
|
|
|
(), timeout
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for fd in rfds:
|
|
|
|
|
|
|
|
_vv and IOLOG.debug('%r: POLLIN for %r', self, fd)
|
|
|
|
|
|
|
|
yield self._rfds[fd]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for fd in wfds:
|
|
|
|
|
|
|
|
_vv and IOLOG.debug('%r: POLLOUT for %r', self, fd)
|
|
|
|
|
|
|
|
yield self._wfds[fd]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Latch(object):
|
|
|
|
class Latch(object):
|
|
|
|
|
|
|
|
poller_class = Poller
|
|
|
|
closed = False
|
|
|
|
closed = False
|
|
|
|
_waking = 0
|
|
|
|
_waking = 0
|
|
|
|
_sockets = []
|
|
|
|
_sockets = []
|
|
|
@ -1058,7 +1151,6 @@ class Latch(object):
|
|
|
|
def get(self, timeout=None, block=True):
|
|
|
|
def get(self, timeout=None, block=True):
|
|
|
|
_vv and IOLOG.debug('%r.get(timeout=%r, block=%r)',
|
|
|
|
_vv and IOLOG.debug('%r.get(timeout=%r, block=%r)',
|
|
|
|
self, timeout, block)
|
|
|
|
self, timeout, block)
|
|
|
|
|
|
|
|
|
|
|
|
self._lock.acquire()
|
|
|
|
self._lock.acquire()
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
if self.closed:
|
|
|
|
if self.closed:
|
|
|
@ -1074,14 +1166,19 @@ class Latch(object):
|
|
|
|
finally:
|
|
|
|
finally:
|
|
|
|
self._lock.release()
|
|
|
|
self._lock.release()
|
|
|
|
|
|
|
|
|
|
|
|
return self._get_sleep(timeout, block, rsock, wsock)
|
|
|
|
poller = self.poller_class()
|
|
|
|
|
|
|
|
poller.start_receive(rsock.fileno())
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
return self._get_sleep(poller, timeout, block, rsock, wsock)
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
poller.close()
|
|
|
|
|
|
|
|
|
|
|
|
def _get_sleep(self, timeout, block, rsock, wsock):
|
|
|
|
def _get_sleep(self, poller, timeout, block, rsock, wsock):
|
|
|
|
_vv and IOLOG.debug('%r._get_sleep(timeout=%r, block=%r)',
|
|
|
|
_vv and IOLOG.debug('%r._get_sleep(timeout=%r, block=%r)',
|
|
|
|
self, timeout, block)
|
|
|
|
self, timeout, block)
|
|
|
|
e = None
|
|
|
|
e = None
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
io_op(select.select, [rsock], [], [], timeout)
|
|
|
|
list(poller.poll(timeout))
|
|
|
|
except Exception:
|
|
|
|
except Exception:
|
|
|
|
e = sys.exc_info()[1]
|
|
|
|
e = sys.exc_info()[1]
|
|
|
|
|
|
|
|
|
|
|
@ -1091,7 +1188,7 @@ class Latch(object):
|
|
|
|
del self._sleeping[i]
|
|
|
|
del self._sleeping[i]
|
|
|
|
self._sockets.append((rsock, wsock))
|
|
|
|
self._sockets.append((rsock, wsock))
|
|
|
|
if i >= self._waking:
|
|
|
|
if i >= self._waking:
|
|
|
|
raise TimeoutError()
|
|
|
|
raise e or TimeoutError()
|
|
|
|
self._waking -= 1
|
|
|
|
self._waking -= 1
|
|
|
|
if rsock.recv(2) != '\x7f':
|
|
|
|
if rsock.recv(2) != '\x7f':
|
|
|
|
raise LatchError('internal error: received >1 wakeups')
|
|
|
|
raise LatchError('internal error: received >1 wakeups')
|
|
|
@ -1348,7 +1445,7 @@ class Router(object):
|
|
|
|
refused_msg = 'Refused by policy.'
|
|
|
|
refused_msg = 'Refused by policy.'
|
|
|
|
|
|
|
|
|
|
|
|
def _invoke(self, msg, stream):
|
|
|
|
def _invoke(self, msg, stream):
|
|
|
|
#IOLOG.debug('%r._invoke(%r)', self, msg)
|
|
|
|
# IOLOG.debug('%r._invoke(%r)', self, msg)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
persist, fn, policy = self._handle_map[msg.handle]
|
|
|
|
persist, fn, policy = self._handle_map[msg.handle]
|
|
|
|
except KeyError:
|
|
|
|
except KeyError:
|
|
|
@ -1432,16 +1529,20 @@ class Router(object):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Broker(object):
|
|
|
|
class Broker(object):
|
|
|
|
|
|
|
|
poller_class = Poller
|
|
|
|
_waker = None
|
|
|
|
_waker = None
|
|
|
|
_thread = None
|
|
|
|
_thread = None
|
|
|
|
shutdown_timeout = 3.0
|
|
|
|
shutdown_timeout = 3.0
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
def __init__(self, poller_class=None):
|
|
|
|
self._alive = True
|
|
|
|
self._alive = True
|
|
|
|
self._waker = Waker(self)
|
|
|
|
self._waker = Waker(self)
|
|
|
|
self.defer = self._waker.defer
|
|
|
|
self.defer = self._waker.defer
|
|
|
|
self._readers = [self._waker.receive_side]
|
|
|
|
self.poller = self.poller_class()
|
|
|
|
self._writers = []
|
|
|
|
self.poller.start_receive(
|
|
|
|
|
|
|
|
self._waker.receive_side.fd,
|
|
|
|
|
|
|
|
(self._waker.receive_side, self._waker.on_receive)
|
|
|
|
|
|
|
|
)
|
|
|
|
self._thread = threading.Thread(
|
|
|
|
self._thread = threading.Thread(
|
|
|
|
target=_profile_hook,
|
|
|
|
target=_profile_hook,
|
|
|
|
args=('broker', self._broker_main),
|
|
|
|
args=('broker', self._broker_main),
|
|
|
@ -1450,33 +1551,30 @@ class Broker(object):
|
|
|
|
self._thread.start()
|
|
|
|
self._thread.start()
|
|
|
|
self._waker.broker_ident = self._thread.ident
|
|
|
|
self._waker.broker_ident = self._thread.ident
|
|
|
|
|
|
|
|
|
|
|
|
def _list_discard(self, lst, value):
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
lst.remove(value)
|
|
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _list_add(self, lst, value):
|
|
|
|
|
|
|
|
if value not in lst:
|
|
|
|
|
|
|
|
lst.append(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_receive(self, stream):
|
|
|
|
def start_receive(self, stream):
|
|
|
|
_vv and IOLOG.debug('%r.start_receive(%r)', self, stream)
|
|
|
|
_vv and IOLOG.debug('%r.start_receive(%r)', self, stream)
|
|
|
|
assert stream.receive_side and stream.receive_side.fd is not None
|
|
|
|
side = stream.receive_side
|
|
|
|
self.defer(self._list_add, self._readers, stream.receive_side)
|
|
|
|
assert side and side.fd is not None
|
|
|
|
|
|
|
|
self.defer(self.poller.start_receive,
|
|
|
|
|
|
|
|
side.fd, (side, stream.on_receive))
|
|
|
|
|
|
|
|
|
|
|
|
def stop_receive(self, stream):
|
|
|
|
def stop_receive(self, stream):
|
|
|
|
IOLOG.debug('%r.stop_receive(%r)', self, stream)
|
|
|
|
_vv and IOLOG.debug('%r.stop_receive(%r)', self, stream)
|
|
|
|
self.defer(self._list_discard, self._readers, stream.receive_side)
|
|
|
|
self.defer(self.poller.stop_receive, stream.receive_side.fd)
|
|
|
|
|
|
|
|
|
|
|
|
def _start_transmit(self, stream):
|
|
|
|
def _start_transmit(self, stream):
|
|
|
|
IOLOG.debug('%r._start_transmit(%r)', self, stream)
|
|
|
|
_vv and IOLOG.debug('%r._start_transmit(%r)', self, stream)
|
|
|
|
assert stream.transmit_side and stream.transmit_side.fd is not None
|
|
|
|
side = stream.transmit_side
|
|
|
|
self._list_add(self._writers, stream.transmit_side)
|
|
|
|
assert side and side.fd is not None
|
|
|
|
|
|
|
|
self.poller.start_transmit(side.fd, (side, stream.on_transmit))
|
|
|
|
|
|
|
|
|
|
|
|
def _stop_transmit(self, stream):
|
|
|
|
def _stop_transmit(self, stream):
|
|
|
|
IOLOG.debug('%r._stop_transmit(%r)', self, stream)
|
|
|
|
_vv and IOLOG.debug('%r._stop_transmit(%r)', self, stream)
|
|
|
|
self._list_discard(self._writers, stream.transmit_side)
|
|
|
|
self.poller.stop_transmit(stream.transmit_side.fd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def keep_alive(self):
|
|
|
|
|
|
|
|
it = (side.keep_alive for (_, (side, _)) in self.poller.readers)
|
|
|
|
|
|
|
|
return sum(it, 0)
|
|
|
|
|
|
|
|
|
|
|
|
def _call(self, stream, func):
|
|
|
|
def _call(self, stream, func):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
@ -1486,26 +1584,12 @@ class Broker(object):
|
|
|
|
stream.on_disconnect(self)
|
|
|
|
stream.on_disconnect(self)
|
|
|
|
|
|
|
|
|
|
|
|
def _loop_once(self, timeout=None):
|
|
|
|
def _loop_once(self, timeout=None):
|
|
|
|
_vv and IOLOG.debug('%r._loop_once(%r)', self, timeout)
|
|
|
|
_vv and IOLOG.debug('%r._loop_once(%r, %r)',
|
|
|
|
|
|
|
|
self, timeout, self.poller)
|
|
|
|
#IOLOG.debug('readers = %r', self._readers)
|
|
|
|
#IOLOG.debug('readers =\n%s', pformat(self.poller.readers))
|
|
|
|
#IOLOG.debug('writers = %r', self._writers)
|
|
|
|
#IOLOG.debug('writers =\n%s', pformat(self.poller.writers))
|
|
|
|
(rsides, wsides, _), _ = io_op(select.select,
|
|
|
|
for (side, func) in self.poller.poll(timeout):
|
|
|
|
self._readers,
|
|
|
|
self._call(side.stream, func)
|
|
|
|
self._writers,
|
|
|
|
|
|
|
|
(), timeout
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for side in rsides:
|
|
|
|
|
|
|
|
_vv and IOLOG.debug('%r: POLLIN for %r', self, side)
|
|
|
|
|
|
|
|
self._call(side.stream, side.stream.on_receive)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for side in wsides:
|
|
|
|
|
|
|
|
_vv and IOLOG.debug('%r: POLLOUT for %r', self, side)
|
|
|
|
|
|
|
|
self._call(side.stream, side.stream.on_transmit)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def keep_alive(self):
|
|
|
|
|
|
|
|
return sum((side.keep_alive for side in self._readers), 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _broker_main(self):
|
|
|
|
def _broker_main(self):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
@ -1513,8 +1597,7 @@ class Broker(object):
|
|
|
|
self._loop_once()
|
|
|
|
self._loop_once()
|
|
|
|
|
|
|
|
|
|
|
|
fire(self, 'shutdown')
|
|
|
|
fire(self, 'shutdown')
|
|
|
|
|
|
|
|
for _, (side, _) in self.poller.readers + self.poller.writers:
|
|
|
|
for side in set(self._readers).union(self._writers):
|
|
|
|
|
|
|
|
self._call(side.stream, side.stream.on_shutdown)
|
|
|
|
self._call(side.stream, side.stream.on_shutdown)
|
|
|
|
|
|
|
|
|
|
|
|
deadline = time.time() + self.shutdown_timeout
|
|
|
|
deadline = time.time() + self.shutdown_timeout
|
|
|
@ -1527,7 +1610,7 @@ class Broker(object):
|
|
|
|
'more child processes still connected to '
|
|
|
|
'more child processes still connected to '
|
|
|
|
'our stdout/stderr pipes.', self)
|
|
|
|
'our stdout/stderr pipes.', self)
|
|
|
|
|
|
|
|
|
|
|
|
for side in set(self._readers).union(self._writers):
|
|
|
|
for _, (side, _) in self.poller.readers + self.poller.writers:
|
|
|
|
LOG.error('_broker_main() force disconnecting %r', side)
|
|
|
|
LOG.error('_broker_main() force disconnecting %r', side)
|
|
|
|
side.stream.on_disconnect(self)
|
|
|
|
side.stream.on_disconnect(self)
|
|
|
|
except Exception:
|
|
|
|
except Exception:
|
|
|
@ -1551,13 +1634,38 @@ class Broker(object):
|
|
|
|
class ExternalContext(object):
|
|
|
|
class ExternalContext(object):
|
|
|
|
detached = False
|
|
|
|
detached = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, config):
|
|
|
|
|
|
|
|
self.config = config
|
|
|
|
|
|
|
|
|
|
|
|
def _on_broker_shutdown(self):
|
|
|
|
def _on_broker_shutdown(self):
|
|
|
|
self.channel.close()
|
|
|
|
self.recv.close()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_broker_exit(self):
|
|
|
|
def _on_broker_exit(self):
|
|
|
|
if not self.profiling:
|
|
|
|
if not self.config['profiling']:
|
|
|
|
os.kill(os.getpid(), signal.SIGTERM)
|
|
|
|
os.kill(os.getpid(), signal.SIGTERM)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _on_call_service_msg(self, msg):
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Stub CALL_SERVICE handler, push message on temporary queue and invoke
|
|
|
|
|
|
|
|
_on_stub_call() from the main thread.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
if msg.is_dead:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
_service_call_lock.acquire()
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
_service_calls.append(msg)
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
_service_call_lock.release()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.router.route(
|
|
|
|
|
|
|
|
Message.pickled(
|
|
|
|
|
|
|
|
dst_id=mitogen.context_id,
|
|
|
|
|
|
|
|
handle=CALL_FUNCTION,
|
|
|
|
|
|
|
|
obj=('mitogen.service', None, '_on_stub_call', (), {}),
|
|
|
|
|
|
|
|
router=self.router,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _on_shutdown_msg(self, msg):
|
|
|
|
def _on_shutdown_msg(self, msg):
|
|
|
|
_v and LOG.debug('_on_shutdown_msg(%r)', msg)
|
|
|
|
_v and LOG.debug('_on_shutdown_msg(%r)', msg)
|
|
|
|
if not msg.is_dead:
|
|
|
|
if not msg.is_dead:
|
|
|
@ -1593,29 +1701,35 @@ class ExternalContext(object):
|
|
|
|
LOG.error('Stream had %d bytes after 2000ms', pending)
|
|
|
|
LOG.error('Stream had %d bytes after 2000ms', pending)
|
|
|
|
self.broker.defer(stream.on_disconnect, self.broker)
|
|
|
|
self.broker.defer(stream.on_disconnect, self.broker)
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_master(self, max_message_size, profiling, unidirectional,
|
|
|
|
def _setup_master(self):
|
|
|
|
parent_id, context_id, in_fd, out_fd):
|
|
|
|
Router.max_message_size = self.config['max_message_size']
|
|
|
|
Router.max_message_size = max_message_size
|
|
|
|
if self.config['profiling']:
|
|
|
|
self.profiling = profiling
|
|
|
|
|
|
|
|
if profiling:
|
|
|
|
|
|
|
|
enable_profiling()
|
|
|
|
enable_profiling()
|
|
|
|
self.broker = Broker()
|
|
|
|
self.broker = Broker()
|
|
|
|
self.router = Router(self.broker)
|
|
|
|
self.router = Router(self.broker)
|
|
|
|
self.router.undirectional = unidirectional
|
|
|
|
self.router.undirectional = self.config['unidirectional']
|
|
|
|
self.router.add_handler(
|
|
|
|
self.router.add_handler(
|
|
|
|
fn=self._on_shutdown_msg,
|
|
|
|
fn=self._on_shutdown_msg,
|
|
|
|
handle=SHUTDOWN,
|
|
|
|
handle=SHUTDOWN,
|
|
|
|
policy=has_parent_authority,
|
|
|
|
policy=has_parent_authority,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
self.router.add_handler(
|
|
|
|
|
|
|
|
fn=self._on_call_service_msg,
|
|
|
|
|
|
|
|
handle=CALL_SERVICE,
|
|
|
|
|
|
|
|
policy=has_parent_authority,
|
|
|
|
|
|
|
|
)
|
|
|
|
self.master = Context(self.router, 0, 'master')
|
|
|
|
self.master = Context(self.router, 0, 'master')
|
|
|
|
|
|
|
|
parent_id = self.config['parent_ids'][0]
|
|
|
|
if parent_id == 0:
|
|
|
|
if parent_id == 0:
|
|
|
|
self.parent = self.master
|
|
|
|
self.parent = self.master
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
self.parent = Context(self.router, parent_id, 'parent')
|
|
|
|
self.parent = Context(self.router, parent_id, 'parent')
|
|
|
|
|
|
|
|
|
|
|
|
self.channel = Receiver(router=self.router,
|
|
|
|
in_fd = self.config.get('in_fd', 100)
|
|
|
|
handle=CALL_FUNCTION,
|
|
|
|
out_fd = self.config.get('out_fd', 1)
|
|
|
|
policy=has_parent_authority)
|
|
|
|
self.recv = Receiver(router=self.router,
|
|
|
|
|
|
|
|
handle=CALL_FUNCTION,
|
|
|
|
|
|
|
|
policy=has_parent_authority)
|
|
|
|
self.stream = Stream(self.router, parent_id)
|
|
|
|
self.stream = Stream(self.router, parent_id)
|
|
|
|
self.stream.name = 'parent'
|
|
|
|
self.stream.name = 'parent'
|
|
|
|
self.stream.accept(in_fd, out_fd)
|
|
|
|
self.stream.accept(in_fd, out_fd)
|
|
|
@ -1633,20 +1747,22 @@ class ExternalContext(object):
|
|
|
|
except OSError:
|
|
|
|
except OSError:
|
|
|
|
pass # No first stage exists (e.g. fakessh)
|
|
|
|
pass # No first stage exists (e.g. fakessh)
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_logging(self, debug, log_level):
|
|
|
|
def _setup_logging(self):
|
|
|
|
root = logging.getLogger()
|
|
|
|
root = logging.getLogger()
|
|
|
|
root.setLevel(log_level)
|
|
|
|
root.setLevel(self.config['log_level'])
|
|
|
|
root.handlers = [LogHandler(self.master)]
|
|
|
|
root.handlers = [LogHandler(self.master)]
|
|
|
|
if debug:
|
|
|
|
if self.config['debug']:
|
|
|
|
enable_debug_logging()
|
|
|
|
enable_debug_logging()
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_importer(self, importer, core_src_fd, whitelist, blacklist):
|
|
|
|
def _setup_importer(self):
|
|
|
|
|
|
|
|
importer = self.config.get('importer')
|
|
|
|
if importer:
|
|
|
|
if importer:
|
|
|
|
importer._install_handler(self.router)
|
|
|
|
importer._install_handler(self.router)
|
|
|
|
importer._context = self.parent
|
|
|
|
importer._context = self.parent
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
|
|
|
|
core_src_fd = self.config.get('core_src_fd', 101)
|
|
|
|
if core_src_fd:
|
|
|
|
if core_src_fd:
|
|
|
|
fp = os.fdopen(101, 'r', 1)
|
|
|
|
fp = os.fdopen(core_src_fd, 'r', 1)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
core_size = int(fp.readline())
|
|
|
|
core_size = int(fp.readline())
|
|
|
|
core_src = fp.read(core_size)
|
|
|
|
core_src = fp.read(core_size)
|
|
|
@ -1657,8 +1773,13 @@ class ExternalContext(object):
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
core_src = None
|
|
|
|
core_src = None
|
|
|
|
|
|
|
|
|
|
|
|
importer = Importer(self.router, self.parent,
|
|
|
|
importer = Importer(
|
|
|
|
core_src, whitelist, blacklist)
|
|
|
|
self.router,
|
|
|
|
|
|
|
|
self.parent,
|
|
|
|
|
|
|
|
core_src,
|
|
|
|
|
|
|
|
self.config.get('whitelist', ()),
|
|
|
|
|
|
|
|
self.config.get('blacklist', ()),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.importer = importer
|
|
|
|
self.importer = importer
|
|
|
|
self.router.importer = importer
|
|
|
|
self.router.importer = importer
|
|
|
@ -1678,12 +1799,12 @@ class ExternalContext(object):
|
|
|
|
sys.modules['mitogen.core'] = mitogen.core
|
|
|
|
sys.modules['mitogen.core'] = mitogen.core
|
|
|
|
del sys.modules['__main__']
|
|
|
|
del sys.modules['__main__']
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_globals(self, version, context_id, parent_ids):
|
|
|
|
def _setup_globals(self):
|
|
|
|
mitogen.__version__ = version
|
|
|
|
|
|
|
|
mitogen.is_master = False
|
|
|
|
mitogen.is_master = False
|
|
|
|
mitogen.context_id = context_id
|
|
|
|
mitogen.__version__ = self.config['version']
|
|
|
|
mitogen.parent_ids = parent_ids
|
|
|
|
mitogen.context_id = self.config['context_id']
|
|
|
|
mitogen.parent_id = parent_ids[0]
|
|
|
|
mitogen.parent_ids = self.config['parent_ids'][:]
|
|
|
|
|
|
|
|
mitogen.parent_id = mitogen.parent_ids[0]
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_stdio(self):
|
|
|
|
def _setup_stdio(self):
|
|
|
|
# We must open this prior to closing stdout, otherwise it will recycle
|
|
|
|
# We must open this prior to closing stdout, otherwise it will recycle
|
|
|
@ -1718,7 +1839,7 @@ class ExternalContext(object):
|
|
|
|
_v and LOG.debug('_dispatch_calls(%r)', data)
|
|
|
|
_v and LOG.debug('_dispatch_calls(%r)', data)
|
|
|
|
|
|
|
|
|
|
|
|
modname, klass, func, args, kwargs = data
|
|
|
|
modname, klass, func, args, kwargs = data
|
|
|
|
obj = __import__(modname, {}, {}, [''])
|
|
|
|
obj = import_module(modname)
|
|
|
|
if klass:
|
|
|
|
if klass:
|
|
|
|
obj = getattr(obj, klass)
|
|
|
|
obj = getattr(obj, klass)
|
|
|
|
fn = getattr(obj, func)
|
|
|
|
fn = getattr(obj, func)
|
|
|
@ -1729,7 +1850,10 @@ class ExternalContext(object):
|
|
|
|
return fn(*args, **kwargs)
|
|
|
|
return fn(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
def _dispatch_calls(self):
|
|
|
|
def _dispatch_calls(self):
|
|
|
|
for msg in self.channel:
|
|
|
|
if self.config.get('on_start'):
|
|
|
|
|
|
|
|
self.config['on_start'](self)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for msg in self.recv:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
msg.reply(self._dispatch_one(msg))
|
|
|
|
msg.reply(self._dispatch_one(msg))
|
|
|
|
except Exception:
|
|
|
|
except Exception:
|
|
|
@ -1738,28 +1862,24 @@ class ExternalContext(object):
|
|
|
|
msg.reply(CallError(e))
|
|
|
|
msg.reply(CallError(e))
|
|
|
|
self.dispatch_stopped = True
|
|
|
|
self.dispatch_stopped = True
|
|
|
|
|
|
|
|
|
|
|
|
def main(self, parent_ids, context_id, debug, profiling, log_level,
|
|
|
|
def main(self):
|
|
|
|
unidirectional, max_message_size, version, in_fd=100, out_fd=1,
|
|
|
|
self._setup_master()
|
|
|
|
core_src_fd=101, setup_stdio=True, setup_package=True,
|
|
|
|
|
|
|
|
importer=None, whitelist=(), blacklist=()):
|
|
|
|
|
|
|
|
self._setup_master(max_message_size, profiling, unidirectional,
|
|
|
|
|
|
|
|
parent_ids[0], context_id, in_fd, out_fd)
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
self._setup_logging(debug, log_level)
|
|
|
|
self._setup_logging()
|
|
|
|
self._setup_importer(importer, core_src_fd, whitelist, blacklist)
|
|
|
|
self._setup_importer()
|
|
|
|
self._reap_first_stage()
|
|
|
|
self._reap_first_stage()
|
|
|
|
if setup_package:
|
|
|
|
if self.config.get('setup_package', True):
|
|
|
|
self._setup_package()
|
|
|
|
self._setup_package()
|
|
|
|
self._setup_globals(version, context_id, parent_ids)
|
|
|
|
self._setup_globals()
|
|
|
|
if setup_stdio:
|
|
|
|
if self.config.get('setup_stdio', True):
|
|
|
|
self._setup_stdio()
|
|
|
|
self._setup_stdio()
|
|
|
|
|
|
|
|
|
|
|
|
self.router.register(self.parent, self.stream)
|
|
|
|
self.router.register(self.parent, self.stream)
|
|
|
|
|
|
|
|
|
|
|
|
sys.executable = os.environ.pop('ARGV0', sys.executable)
|
|
|
|
sys.executable = os.environ.pop('ARGV0', sys.executable)
|
|
|
|
_v and LOG.debug('Connected to %s; my ID is %r, PID is %r',
|
|
|
|
_v and LOG.debug('Connected to %s; my ID is %r, PID is %r',
|
|
|
|
self.parent, context_id, os.getpid())
|
|
|
|
self.parent, mitogen.context_id, os.getpid())
|
|
|
|
_v and LOG.debug('Recovered sys.executable: %r', sys.executable)
|
|
|
|
_v and LOG.debug('Recovered sys.executable: %r', sys.executable)
|
|
|
|
|
|
|
|
|
|
|
|
_profile_hook('main', self._dispatch_calls)
|
|
|
|
_profile_hook('main', self._dispatch_calls)
|
|
|
|