| 
					
				 | 
			
			
				@@ -0,0 +1,259 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+Process Pools. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import os 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import errno 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import multiprocessing 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from multiprocessing.pool import Pool, worker 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from operator import isNumberType 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from celery.datastructures import ExceptionInfo 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from celery.utils import noop 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from celery.utils.functional import curry 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from celery.patch import monkeypatch 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def pid_is_dead(pid): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Check if a process is not running by PID. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    :rtype bool: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return os.kill(pid, 0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    except OSError, err: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if err.errno == errno.ESRCH: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return True # No such process. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        elif err.errno == errno.EPERM: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return False # Operation not permitted. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            raise 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def reap_process(pid): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Reap process if the process is a zombie. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    :returns: ``True`` if process was reaped or is not running, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ``False`` otherwise. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if pid_is_dead(pid): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        is_dead, _ = os.waitpid(pid, os.WNOHANG) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    except OSError, err: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if err.errno == errno.ECHILD: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return False # No child processes. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        raise 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return is_dead 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def process_is_dead(process): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Check if process is not running anymore. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    First it finds out if the process is running by sending 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    signal 0. Then if the process is a child process, and is running 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    it finds out if it's a zombie process and reaps it. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    If the process is running and is not a zombie it tries to send 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    a ping through the process pipe. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    :param process: A :class:`multiprocessing.Process` instance. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    :returns: ``True`` if the process is not running, ``False`` otherwise. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    # Only do this if os.kill exists for this platform (e.g. Windows doesn't 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    # support it). 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if callable(getattr(os, "kill", None)) and reap_process(process.pid): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    # Then try to ping the process using its pipe. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        proc_is_alive = process.is_alive() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    except OSError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return not proc_is_alive 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class DynamicPool(Pool): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Version of :class:`multiprocessing.Pool` that can dynamically grow 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    in size.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self, processes=None, initializer=None, initargs=()): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if processes is None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                processes = multiprocessing.cpu_count() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            except NotImplementedError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                processes = 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        super(DynamicPool, self).__init__(processes=processes, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                          initializer=initializer, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                          initargs=initargs) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._initializer = initializer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._initargs = initargs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._size = processes 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.logger = multiprocessing.get_logger() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def _my_cleanup(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        from multiprocessing.process import _current_process 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for p in list(_current_process._children): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            discard = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                status = p._popen.poll() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            except OSError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                discard = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if status is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    discard = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if discard: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                _current_process._children.discard(p) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def add_worker(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Add another worker to the pool.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._my_cleanup() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        w = self.Process(target=worker, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                         args=(self._inqueue, self._outqueue, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                               self._initializer, self._initargs)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        w.name = w.name.replace("Process", "PoolWorker") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        w.daemon = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        w.start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._pool.append(w) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.logger.debug( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            "DynamicPool: Started pool worker %s (PID: %s, Poolsize: %d)" %( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                w.name, w.pid, len(self._pool))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def grow(self, size=1): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Add workers to the pool. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :keyword size: Number of workers to add (default: 1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [self.add_worker() for i in range(size)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def _is_dead(self, process): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Try to find out if the process is dead. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :rtype bool: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if process_is_dead(process): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.logger.info("DynamicPool: Found dead process (PID: %s)" % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                process.pid)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def _bring_out_the_dead(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Sort out dead process from pool. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :returns: Tuple of two lists, the first list with dead processes, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            the second with active processes. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        dead, alive = [], [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for process in self._pool: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if process and process.pid and isNumberType(process.pid): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                dest = dead if self._is_dead(process) else alive 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                dest.append(process) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return dead, alive 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def replace_dead_workers(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Replace dead workers in the pool by spawning new ones. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        :returns: number of dead processes replaced, or ``None`` if all 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            processes are alive and running. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        dead, alive = self._bring_out_the_dead() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if dead: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            dead_count = len(dead) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self._pool = alive 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.grow(self._size if dead_count > self._size else dead_count) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return dead_count 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class TaskPool(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Process Pool for processing tasks in parallel. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    :param limit: see :attr:`limit` attribute. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    :param logger: see :attr:`logger` attribute. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    .. attribute:: limit 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        The number of processes that can run simultaneously. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    .. attribute:: logger 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        The logger used for debugging. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self, limit, logger=None): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.limit = limit 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.logger = logger or multiprocessing.get_logger() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._pool = None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        monkeypatch() # Patch "no processName" logging error. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def start(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Run the task pool. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Will pre-fork all workers so they're ready to accept tasks. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._pool = DynamicPool(processes=self.limit) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def stop(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Terminate the pool.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._pool.terminate() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._pool = None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def replace_dead_workers(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.logger.debug("TaskPool: Finding dead pool processes...") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        dead_count = self._pool.replace_dead_workers() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if dead_count: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.logger.info( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                "TaskPool: Replaced %d dead pool workers..." % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    dead_count)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def apply_async(self, target, args=None, kwargs=None, callbacks=None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            errbacks=None, on_ack=noop): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Equivalent of the :func:``apply`` built-in function. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        All ``callbacks`` and ``errbacks`` should complete immediately since 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        otherwise the thread which handles the result will get blocked. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        args = args or [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        kwargs = kwargs or {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        callbacks = callbacks or [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        errbacks = errbacks or [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        on_ready = curry(self.on_ready, callbacks, errbacks, on_ack) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.logger.debug("TaskPool: Apply %s (args:%s kwargs:%s)" % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            target, args, kwargs)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.replace_dead_workers() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return self._pool.apply_async(target, args, kwargs, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                        callback=on_ready) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def on_ready(self, callbacks, errbacks, on_ack, ret_value): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """What to do when a worker task is ready and its return value has 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        been collected.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # Acknowledge the task as being processed. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        on_ack() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if isinstance(ret_value, ExceptionInfo): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if isinstance(ret_value.exception, ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    SystemExit, KeyboardInterrupt)): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                raise ret_value.exception 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [errback(ret_value) for errback in errbacks] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [callback(ret_value) for callback in callbacks] 
			 |