Marc-Alexandre Chan avatar Marc-Alexandre Chan committed 9c69903

Scheduler: Adding support for 'retry' and 'final' return status for handled exceptions;
improved built-in handling of reddit errors

Comments (0)

Files changed (2)

minibot/errors.py

 
 # REDDIT
 from reddit.errors import *
+from urllib2 import HTTPError
+
+# SQL
+from sqlalchemy.exc import SQLAlchemyError, DBAPIError

minibot/eventscheduler.py

 
     """
 
+    # Queued event statuses
     STATUS_QUEUED = 0   # queued, not started
     STATUS_RUNNING = 1  # started, on periodic (interval) runs
     STATUS_ONESHOT = 2  # queued, not started, one-shot event
                      STATUS_ONESHOT : 'Queued (one-shot)',
                      STATUS_ENDING : 'Ending'}
 
+    # Exception handling return values
+    EXC_UNHANDLED = 0 # exception could not be handled
+    EXC_HANDLED_RETRY = 1 # exception handled, safe to requeue
+    EXC_HANDLED_FINAL = 2 # exception handled, no need to requeue
+
     def __init__(self, config, reddit, database, logger, **res):
         """ Initialise the Minibot. ``config`` should be an instance of the
         ``minibot.Config`` class with a loaded configuration file, or an
             try:
                 self._execute_event(*evtuple)
             except Exception as e:
-                self._handle_exception(*evtuple, e)
+                self._handle_exception(*(evtuple + (e,)))
             else: # if no exception occurs in the exec
                 self._reschedule_event(*evtuple)
             evtuple = self._queue[0]
                              classname(self), repr(event))
             event.end()
 
+    DELAY_RATE_ERROR = 2
+    DELAY_HTTPERROR  = 30
     def _handle_exception(self, time, priority, status, event, exc):
         """ Handle an exception that occurred in an event. Returns True if
         successfully handled, False otherwise. """
         # get exception info
         basic, src, trace = log_exc_info()
         self.logger.error("%s: A '%s' was raised by %s",
-            classname(self), classname(e), classname(event))
+            classname(self), classname(exc), classname(event))
         self.logger.warn(*basic)
         self.logger.debug(*src)
         self.logger.debug(*trace)
 
         # pass exception to event exception handler
         try: # just in case
-            is_handled = event.handle_exception(e)
+            handler_status = event.handle_exception(exc)
         except Exception as ehe: # UGH WHYYYYYY
-            is_handled = False
+            handler_status = self.EXC_UNHANDLED
             basic2, src2, trace2 = log_exc_info()
             self.logger.error(
                 "%s: %s was raised in the exception handler for %s",
             self.logger.debug(*trace2)
 
         # original exception
-        if is_handled: # event recovered from error
+        if handler_status > self.EXC_UNHANDLED: # event recovered from error
             self.logger.warn(
                 "%s: '%s' was raised and handled by %s",
-                classname(self), classname(e), classname(event))
-            self._retry_event(time, priority, status, event)
+                classname(self), classname(exc), classname(event))
+            if handler_status == self.EXC_HANDLED_RETRY:
+                self._retry_event(time, priority, status, event)
             return True
 
         # TODO: any other recoverable cases?
         # errors that can be handled by the scheduler
-        elif isinstance(e, InvalidUserPass) or\
-             isinstance(e, LoginRequired) or\
-             isinstance(e, NotLoggedIn):
+        elif isinstance(exc, InvalidUserPass) or\
+             isinstance(exc, LoginRequired) or\
+             isinstance(exc, NotLoggedIn):
             self.__init_validate() # recheck Reddit state
             self._retry_event(time, priority, status, event)
             return True
 
-        elif isinstance(e, RateLimitExceeded):
+        elif isinstance(exc, RateLimitExceeded):
             # give it a bit of breathing room
-            if not hasattr(event, 'delay') or event.delay < 2:
-                event.delay = 2
+            if not hasattr(event, 'delay') or event.delay<self.DELAY_RATE_ERROR:
+                event.delay = self.DELAY_RATE_ERROR
+            self._retry_event(time, priority, status, event)
+            return True
+
+        elif isinstance(exc, HTTPError) or\
+                (isinstance(exc, JSONError) and "No JSON object" in exc):
+            # oh, is Reddit having some issues?
+            if not hasattr(event, 'delay') or event.delay<self.DELAY_HTTPERROR:
+                event.delay = self.DELAY_HTTPERROR
             self._retry_event(time, priority, status, event)
             return True
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.