Commits

Marc-Alexandre Chan committed 4bcd4f7

Scheduler - fixed a couple of typos, proper handling of ResourceError

Comments (0)

Files changed (2)

minibot/__init__.py

         """ Run the minibot. """
         self._populate_queue()
         self._running = True
-        self.scheduler.run()
+        try:
+            self.scheduler.run()
+        except:
+            self._running = False
+            raise
+        self._running = False
 
     def _populate_queue(self):
         """ Populates event scheduler with default/always-running events. """
         now = time.time()
 
         ev_msg = CheckMessageEvent()
-        ev_msg.start_time = time.time() + 5
+        ev_msg.start_time = now + 5
         self.scheduler.queue_event(ev_msg)
 
         ev_post = CheckPostQueueEvent()
-        ev_post.start_time = time.time() + 8
+        ev_post.start_time = now + 8
         self.scheduler.queue_event(ev_post)
 
         ev_sugg = CheckSuggestionQueueEvent()
-        ev_post.start_time = time.time() + 13
+        ev_post.start_time = now + 13
         self.scheduler.queue_event(ev_sugg)
 
         ev_sugg_maint = SuggestionThreadQueueMaintainer()
         ev_sugg_maint.interval =\
             self.THREAD_MAINTAINER_INTERVAL_FACTOR * config.minibot.queue_rate
-        ev_post.start_time = time.time() + 21
+        ev_post.start_time = now + 21
         self.scheduler.queue_event(ev_sugg)
 
     def sigterm_handler(self, signum, frame):

minibot/eventscheduler.py

         for exec_time, priority, event_type, event in self._queue:
             yield event
 
+    # TODO: test me
     def run(self):
         """ Run the event scheduler. The event scheduler will run until an
         exit or stop request is made by an event, or no more events exist in
                 self._next_run = now
 
         if self._exit_flag:
+            self.logger.info("%s: Exiting event scheduler.", classname(self))
             self.exit()
         elif self._stop_flag:
             self.logger.info("%s: Stopped the event scheduler.",classname(self))
             del event
             return False
 
+    # TODO: test me
     def exit(self):
         """ Clean up events and exit the scheduler. This method should be called
         before destroying the EventScheduler or exiting the application. """
             evtime, evpriority, evstatus, event = self._queue.pop(0)
             if evstatus == self.STATUS_RUNNING or\
                evstatus == self.STATUS_ENDING:
-                event.end()
+                event.end() # allow it to do cleanup
                 del event
                 self.logger.debug("%s: Stopped event: %s",
                                   classname(self), repr(event))
                 self.logger.debug("%s: Dropped queued event: %s",
                                   classname(self), repr(event))
 
+    # TODO: test me
     def queue_event(self, event):
         """ Queue a new event object for execution. The event object should
         have been newly constructed; an event object that has already been
         queued or executed may not have a consistent internal state for queueing
-        and starting. """
+        and starting. ResourceError is raised if the event requests an invalid
+        resource in ``required_res``. """
         if hasattr(event, 'owner') and owner is not None:
             raise AttachedEventError(("Cannot queue Event '{}': "
                 "Event is already attached to an EventScheduler. Please only "
             "%s: Queued event: time=%s, priority=%d, status=%s, event=%s",
             classname(self),
             time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(event.start_time)),
-            r_priority, self.STATUS_STRING[r_status], repr(event))
+            event.priority, self.STATUS_STRING[status], repr(event))
 
+    # TODO: test me
     def remove_event(self, event):
         """ Remove a particular event object from the queue. """
         del_index = None
     def _insert_event(self, time, priority, status, event):
         """ Insert an event into the appropriate location in a queue. """
         evtuple = (time, priority, status, event)
-        insert_at = None
+        insert_at = len(self._queue) # insert at end by default
         for q_i, q_evtuple in enumerate(self._queue):
             if q_evtuple > evtuple:
                 insert_at = q_i
 
     def _reschedule_event(self, time, priority, status, event):
         """ Check whether the event needs to be rescheduled and reschedules it.
-        Called during the main loop. """
+        Called during the main loop. If you want to reschedule the SAME run
+        (i.e. not advance the status, if applicable), """
         # using _next_run calibrates to the actual iteration runtime, not the
         # event's expected runtime, and _next_run resets in _execute_iteration()
         # if it lags behind, so no worries about the reschedules lagging behind
         r_priority = event.priority
 
         # refresh resources in the event
-        self.prepare_event(event)
+        res_error = False
+        try:
+            self.prepare_event(event)
+        except ResourceError as e:
+            # error already logged in prepare_event - just take care of cleanup
+            self.logger.warning("%s: Event dropped: %s",
+                classname(self), repr(event))
+            try: # if dbsession assigned, roll it back
+                event.res['dbsession'].rollback()
+            except KeyError, AttributeError:
+                pass
+            res_error = True
 
-        if status == self.STATUS_QUEUED or status == self.STATUS_RUNNING:
+        if status == self.STATUS_QUEUED or status == self.STATUS_RUNNING and\
+                not res_error:
             r_time = now + event.interval + event.delay
             event.delay = None
             end_time = event.start_time + event.duration
         # populate resources
         event.owner = self
 
-        if not hasattr(event, res) or not isinstance(event.res, dict):
+        self._set_timing(event)
+
+        if not hasattr(event, 'res') or not isinstance(event.res, dict):
             event.res = dict()
-
-        for res_name in event.required_res:
-            self._set_resource(res_name, event.res)
-        self._set_timing(event)
+        try:
+            for res_name in event.required_res:
+                self._set_resource(res_name, event.res)
+        except ResourceError as e:
+            self.logger.warning("%s: %s raised in %s: %s",
+                classname(self), classname(e), classname(event), e.args[0])
+            raise
 
     def _set_resource(self, res_name, event):
         """ Set resources to an event's resource dict. If the resource is not
 
     def _set_timing(self, event):
         """ Set default event timing values if necessary. """
-        if not hasattr(event, start_time) or event.start_time < 0 or\
+        if not hasattr(event, 'start_time') or event.start_time < 0 or\
                 event.start_time is None:
             event.start_time = 0 # on next iteration
-        if not hasattr(event, interval) or event.interval < 0 or\
+        if not hasattr(event, 'interval') or event.interval < 0 or\
                 event.interval is None:
             event.interval = 0 # single-run
-        if not hasattr(event, duration) or event.duration < -1 or\
+        if not hasattr(event, 'duration') or event.duration < -1 or\
                 event.duration is None:
             event.duration = -1 # infinite run if interval is nonzero
-        if not hasattr(event, priority) or event.priority is None:
+        if not hasattr(event, 'priority') or event.priority is None:
             event.priority = 100
-        if not hasattr(event, delay) or event.delay is None:
+        if not hasattr(event, 'delay') or event.delay is None:
             event.delay = 0
 
+    # TODO: test me
     def get_events(self, type_=None):
         """ Return a generator of event objects currently in the queue. If
         ``type_`` is specified and is a class, gets only events of the specified
         class. If ``type`` is None, this is equivalent to the ``queue``
         property.
         """
-        if type is None:
+        if type_ is None:
             for event in self.queue:
                 yield event
         else:
                     yield event
         return
 
+    # TODO: test me
     def request_exit(self):
         """ Request that the event scheduler cleans up all events and exits
         on the next iteration. """
         self._exit_flag = True
         self.log.info("%s: Exit requested", classname(self))
 
+    # TODO: test me
     def request_stop(self):
         """ Requests that the event scheduler stop running on the next
         iteration. """