# -*- coding: utf-8 -*-
#---Header---------------------------------------------------------------------
# This file is part of Message For You Sir (m4us).
# Copyright © 2009-2012 Krys Lawrence
#
# Message For You Sir is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# Message For You Sir is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
# for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Message For You Sir. If not, see <http://www.gnu.org/licenses/>.
"""Provides support for concurrent execution of `coroutines`."""
#---Imports--------------------------------------------------------------------
#--- Standard library imports
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
## pylint: disable=W0622, W0611
from future_builtins import ascii, filter, hex, map, oct, zip ## NOQA
## pylint: enable=W0622, W0611
import threading
import Queue
#--- Third-party imports
## pylint: disable=F0401
from zope import interface
## pylint: enable=F0401
#--- Project imports
from m4us.core import api as core
from m4us import interfaces
#---Globals--------------------------------------------------------------------
#---Functions------------------------------------------------------------------
#---Classes--------------------------------------------------------------------
class _CoroutineThread(threading.Thread):
"""Thread that can run a `coroutine` using `message` queues.
This class' :meth:`run` method is designed to run a `coroutine`, delivering
`messages` between the `coroutine` and the `message` queues.
:param m4us.core.interfaces.ICoroutine coroutine: The `coroutine` to run in
it's own thread.
:param Queue.Queue in_queue: The input queue of `messages` to send to the
`coroutine`.
:param Queue.Queue out_queue: The output queue of `message` sent from the
`coroutine`.
"""
def __init__(self, coroutine, in_queue, out_queue):
"""See class docstring for this method's documentation."""
threading.Thread.__init__(self)
self._coroutine = coroutine
self._in_queue, self._out_queue = in_queue, out_queue
def run(self):
"""Send and receive `coroutine` `messages`.
This is the main loop inside the thread. `Messages` put on the input
queue will be sent to the `coroutine` and it's responses will be put on
the output queue.
All incoming `messages` are expected to be in the standard form of
:samp:`({inbox}, {message})`.
If an :class:`~m4us.core.interfaces.IShutdown` message is sent in on
the ``_thread_control`` `inbox`, the `coroutine` will be closed and the
thread will be shut down.
To have an exception thrown into the `coroutine`, send it as a
`message` on the ``_thread_exception`` inbox.
Any exceptions raised by the `coroutine` will be put on the output
queue in the ``_thread_exception`` `outbox` and the thread will shut
down.
.. note::
The thread only runs if there are `messages` in the input queue,
regardless of whether or not the `coroutine` is `lazy`. It is
expected that ``('control', None)`` `messages` will be sent to non-
`lazy` `coroutines` in order to make them run every cycle.
.. seealso::
The :class:`~m4us.core.interfaces.INotLazy` `interface` for details
on non-`lazy` `coroutines`.
"""
while True:
inbox, message = self._in_queue.get()
try:
if core.is_shutdown(inbox, message, '_thread_control'):
self._coroutine.close()
break
try:
if inbox == '_thread_exception':
outbox_message = self._coroutine.throw(message)
else:
outbox_message = self._coroutine.send((inbox, message))
## pylint: disable=W0703
except Exception as error:
## pylint: enable=W0703
self._out_queue.put(('_thread_exception', error))
break
self._out_queue.put(outbox_message)
finally:
self._in_queue.task_done()
@interface.implementer(interfaces.IThreadedCoroutine, core.INotLazy)
@interface.provider(core.ICoroutineFactory)
[docs]class ThreadedCoroutine(object):
"""Wrapper that runs a `coroutine` in a separate thread.
This class is an analog to Kamaelia_'s :class:`!ThreadedComponent` class
but is meant to be used on any `coroutine` or `component`.
This class uses thread-safe queues for `message` and exception delivery.
Method calls on this class will always return immediately, merely queueing
up `messages` to be delivered to and from the given `coroutine`. The
exception is when either of the queue sizes are set and the queue is full.
In that case, a method will hang until room becomes available in the queue.
:param m4us.core.interfaces.ICoroutine coroutine: The `coroutine` to run in
it's own thread.
:param int max_in_size: The maximum input queue size. ``0`` means
unlimited.
:param int max_out_size: The maximum output queue size. ``0`` means
unlimited.
:param bool start: Whether or not to automatically start the thread. If
disabled, the :meth:`start` method must be called explicitly.
:implements: :class:`m4us.interfaces.IThreadedCoroutine` and
:class:`m4us.core.interfaces.INotLazy`
:provides: :class:`m4us.core.interfaces.ICoroutineFactory`
Example usage:
>>> scheduler.register(ThreadedCoroutine(my_coroutine()))
.. seealso::
The Python_ :class:`Queue.Queue` class for more information on limiting
queue sizes.
.. _Kamaelia: http://kamaelia.org
.. _Python: http://python.org
"""
## pylint: disable=W0105
_closed = False
"""Indicates whether or not instances of this class have been closed.
:type: :obj:`bool`
.. seealso::
The :meth:`close` method for details.
"""
## pylint: enable=W0105
def __init__(self, coroutine, max_in_size=0, max_out_size=0, start=True):
"""See class docstring for this method's documentation."""
self._coroutine = core.ICoroutine(coroutine)
self._in_queue = Queue.Queue(max_in_size)
self._out_queue = Queue.Queue(max_out_size)
self._thread = _CoroutineThread(coroutine, self._in_queue,
self._out_queue)
if start:
self.start()
def _get_queued_message(self):
"""Try to return a queued output `message`.
This method tries to retrieve and return a `message` from the output
queue without blocking. If there is no `message` waiting, :obj:`None`
is returned.
If an exception is received on the ``_thread_exception`` `outbox`, the
exception is raised.
:returns: A queued up output `message`, if one exists.
:rtype: :obj:`tuple` or :obj:`None`
:raises: Any queued up exceptions coming from the `coroutine`.
"""
try:
message = self._out_queue.get_nowait()
except Queue.Empty:
return None
if message is not None:
inbox, error = message
if inbox == '_thread_exception':
raise error
self._out_queue.task_done()
return message
[docs] def start(self):
"""Start the `coroutine` thread if it needs explicit starting.
.. seealso::
:class:`~m4us.interfaces.IThreadedCoroutine` for details about this
method.
.. seealso::
This class's docstring for details about the implicit and explicit
starting of the `coroutine` thread.
"""
self._thread.start()
[docs] def send(self, message):
"""Send and receive `messages` to and from the `coroutine`.
.. note::
``('control', None)`` `inbox` `messages` will only be sent to the
`coroutine` if it is not `lazy` (i.e. it provides the
:class:`~m4us.core.interfaces.INotLazy` marker `interface`).
.. note::
Any `message` returned will be the next one in the output queue, and
not necessarily the immediate response to the given `message` when
sent.
.. seealso::
:class:`~m4us.core.interfaces.ICoroutine` for details about this
method.
"""
if self._closed:
raise StopIteration()
if message != ('control', None) or core.INotLazy(self._coroutine,
False):
self._in_queue.put(message)
return self._get_queued_message()
[docs] def throw(self, exception):
"""Throw an exception inside the threaded `coroutine`.
.. note::
The thrown exception is queued up, in order, with other sent
`messages` and so the threaded `coroutine` may not react to it
immediately.
.. note::
Like the :meth:`send` method, any `message` returned will be the next
one in the output queue, and not necessarily the immediate response
given exception. This includes the raising of any handled
exceptions.
.. seealso::
:class:`~m4us.core.interfaces.ICoroutine` for details about this
method.
"""
if self._closed:
raise exception
self._in_queue.put(('_thread_exception', exception))
return self._get_queued_message()
[docs] def close(self):
"""Close the `coroutine` and shutdown it's thread.
.. seealso::
:class:`~m4us.core.interfaces.ICoroutine` for details about this
method.
"""
if self._closed:
return
self._in_queue.put(('_thread_control', core.Shutdown()))
self._thread.join()
self._closed = True
#---Module initialization------------------------------------------------------
#---Late Imports---------------------------------------------------------------
#---Late Globals---------------------------------------------------------------
#---Late Functions-------------------------------------------------------------
#---Late Classes---------------------------------------------------------------
#---Late Module initialization-------------------------------------------------