camelot.camelot.view.model_thread

Package camelot :: Package camelot :: Package view :: Module model_thread |
[hide private]
[frames] | no frames]

# Source Code for Module camelot.camelot.view.model_thread

      1  #  ============================================================================
      2  #
      3  #  Copyright (C) 2007-2008 Conceptive Engineering bvba. All rights reserved.
      4  #  www.conceptive.be / project-camelot@conceptive.be
      5  #
      6  #  This file is part of the Camelot Library.
      7  #
      8  #  This file may be used under the terms of the GNU General Public
      9  #  License version 2.0 as published by the Free Software Foundation
     10  #  and appearing in the file LICENSE.GPL included in the packaging of
     11  #  this file.  Please review the following information to ensure GNU
     12  #  General Public Licensing requirements will be met:
     13  #  http://www.trolltech.com/products/qt/opensource.html
     14  #
     15  #  If you are unsure which license is appropriate for your use, please
     16  #  review the following information:
     17  #  http://www.trolltech.com/products/qt/licensing.html or contact
     18  #  project-camelot@conceptive.be.
     19  #
     20  #  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
     21  #  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
     22  #
     23  #  For use of this library in commercial applications, please contact
     24  #  project-camelot@conceptive.be
     25  #
     26  #  ============================================================================
     27
     28  import os
     29  import sys
     30  import logging
     31  import threading
     32  import Queue
     33
     34  import settings
     35
     36  logger = logging.getLogger('camelot.view.model_thread')
     37
     38  _model_thread_ = []
     39
     40 -class ModelThreadException(Exception):
     41    pass
     42
     43 -def model_function(original_function):
     44    """Decorator to ensure a function is only called from within the model
     45    thread.  If this function is called in another thread, an exception will be
     46    thrown
     47    """
     48
     49    def new_function(*args, **kwargs):
     50      if threading.currentThread() != get_model_thread():
     51        logger.error('%s was called outside the model thread' %
     52                     (original_function.__name__))
     53        raise ModelThreadException()
     54      return original_function(*args, **kwargs)
     55
     56    new_function.__name__ = original_function.__name__
     57
     58    return new_function
     59
     60 -def gui_function(original_function):
     61    """Decorator to ensure a function is only called from within the gui thread.
     62    If this function is called in another thread, an exception will be thrown
     63
     64    @todo: now it only checks if the function is not called within the model
     65    thread, this is incomplete
     66    """
     67
     68    def new_function(*args, **kwargs):
     69      if threading.currentThread() == get_model_thread():
     70        logger.error('%s was called outside the gui thread' %
     71                     (original_function.__name__))
     72        raise ModelThreadException()
     73      return original_function(*args, **kwargs)
     74
     75    new_function.__name__ = original_function.__name__
     76
     77    return new_function
     78
     79
     80 -class ModelThread(threading.Thread):
     81    """Thread in which the model runs, all requests to the model should be
     82    posted to the the model thread.
     83
     84    This class ensures the gui thread doesn't block when the model needs
     85    time to complete tasks by providing asynchronous communication between
     86    the model thread and the gui thread
     87    """
     88
     89 -  def __init__(self, response_signaler):
     90      """@param response_signaler: an object with methods called :
     91
     92      responseAvailable() : this method will be called when a response is
     93      available
     94
     95      startProcessingRequest(),
     96
     97      stopProcessingRequest(),
     98      """
     99      threading.Thread.__init__(self)
    100
    101      def setup_thread():
    102  #      from libraries.elixir import setup_all
    103  #      for model in settings.ELIXIR_MODELS:
    104  #        __import__(model, globals(),  locals(), [], -1)
    105  #      setup_all(create_tables=True)
    106  #      from model.base import Project
    107
    108        logger.debug('thread setup finished')
    109
    110      self._request_queue = Queue.Queue()
    111      self._response_queue = Queue.Queue()
    112      self._response_signaler = response_signaler
    113      self.post(setup_thread)
    114      logger.debug('model thread constructed')
    115
    116 -  def run(self):
    117      logger.debug('model thread started')
    118      try:
    119        from settings import setup_model
    120        setup_model()
    121        logger.debug('start handling requests')
    122        while True:
    123          new_event = threading.Event()
    124          try:
    125            (event, request, response, exception) = self._request_queue.get()
    126            #self._response_queue.join()
    127            logger.debug('start handling request')
    128  #          import inspect
    129  #          print inspect.getsource(request)
    130            self._response_signaler.startProcessingRequest()
    131            result = request()
    132            self._response_queue.put((new_event, result, response))
    133            self._request_queue.task_done()
    134            self._response_signaler.responseAvailable()
    135            self._response_signaler.stopProcessingRequest()
    136            logger.debug('finished handling request')
    137            event.set()
    138            #self._response_queue.join()
    139          except Exception, e:
    140            logger.error('exception caught in model thread', exc_info=e)
    141            self._response_queue.put((new_event, e, exception))
    142            self._request_queue.task_done()
    143            self._response_signaler.responseAvailable()
    144            self._response_signaler.stopProcessingRequest()
    145            event.set()
    146          except:
    147            logger.error('unhandled exception in model thread')
    148
    149      except Exception, e:
    150        logger.error('exception caught in model thread', exc_info=e)
    151      except:
    152        logger.error('unhandled exception')
    153
    154 -  def process_responses(self):
    155      """Process all responses that are generated by completed requests
    156      from the ModelThread.  This method should be called from time
    157      to time from within the GUI thread.
    158      """
    159      try:
    160        while True:
    161          (event, result, response) = self._response_queue.get_nowait()
    162          try:
    163            response(result)
    164          except Exception, e:
    165            logger.error('exception in response', exc_info=e)
    166          self._response_queue.task_done()
    167          event.set()
    168      except Queue.Empty, e:
    169        pass
    170
    171 -  def post(self, request, response=lambda result:None,
    172             exception=lambda exc:None):
    173      """Post a request to the model thread, request should be
    174      a function that takes no arguments.  The request function
    175      will be called within the model thread.  When the request
    176      is finished, on first occasion, the response function will be
    177      called within the gui thread.  The response function takes as
    178      arguments, the results of the request function.
    179      @param request: function to be called within the model thread
    180      @param response: function to be called within the gui thread, when
    181      the request function is finished, the response function takes
    182      as its argument the result of the request function
    183      @param exception: function to be called in case of an exception in the
    184      request function
    185      @return a threading Event object which will be set to True when the
    186      request function is finished and the response has been put on the queue
    187      """
    188      event = threading.Event()
    189      self._request_queue.put_nowait((event, request, response, exception))
    190      return event
    191
    192 -  def post_and_block(self, request):
    193      """Post a request tot the model thread, block until it is finished, and
    194      then return it results.  This function only exists for testing purposes,
    195      it should never be used from within the gui thread
    196      """
    197      # make sure there are no responses in the queue
    198      self.process_responses()
    199      results = []
    200
    201      def re_raise(exc):
    202        raise exc
    203
    204      event = self.post(request,
    205                        lambda result:results.append(result),
    206                        exception=re_raise)
    207      event.wait()
    208      self.process_responses()
    209      return results[-1]
    210
    211 -def construct_model_thread(*args, **kwargs):
    212    _model_thread_.append(ModelThread(*args, **kwargs))
    213
    214 -def get_model_thread():
    215    return _model_thread_[0]
    216