示例#1
0
 def test_example_requester_(self):
     """ Initialize ROCON scheduler node for example requester. """
     self.number_of_requests = 3     # number of requests desired
     self.queued_request = None
     rospy.init_node("test_example_requester")
     self.sch = Scheduler(self.callback)
     rospy.spin()                    # spin in the main thread
示例#2
0
class TestExampleRequester(unittest.TestCase):

    def test_example_requester_(self):
        """ Initialize ROCON scheduler node for example requester. """
        self.number_of_requests = 3     # number of requests desired
        self.queued_request = None
        rospy.init_node("test_example_requester")
        self.sch = Scheduler(self.callback)
        rospy.spin()                    # spin in the main thread

    def allocate(self, event):
        """ Timer handler: simulated resource availability event.

        This method runs in a different thread from the scheduler
        callback, so acquire the Big Scheduler Lock before doing
        anything.
        """
        with self.sch.lock:
            if self.queued_request:
                self.queued_request.grant([TEST_RESOURCE])
                rospy.loginfo('Request granted: '
                              + str(self.queued_request.uuid))
                if self.number_of_requests == 2:
                    # preempt this request immediately
                    self.queued_request.preempt()
                    rospy.loginfo('Request preempted: '
                                  + str(self.queued_request.uuid))
                self.queued_request = None
                self.sch.notify(self.queued_requester)

    def queue(self, requester_id, request):
        """
        Queue timer request to simulate delayed resource availability.
        """
        # this test case only makes one request at a time:
        self.assertIsNone(self.queued_request)
        self.queued_requester = requester_id
        self.queued_request = request
        self.timer = rospy.Timer(rospy.Duration(1.0),
                                 self.allocate, oneshot=True)
        rospy.loginfo('Request queued: ' + str(request.uuid))

    def callback(self, rset):
        """ Scheduler request callback.

        Makes new requests wait, they will be granted after a second.

        """
        for rq in rset.values():
            if rq.msg.status == Request.NEW:
                rq.wait(reason=Request.BUSY)
                self.queue(rset.requester_id, rq)
            elif rq.msg.status == Request.CANCELING:
                rq.close()
                rospy.loginfo('Request canceled: ' + str(rq.uuid))
                self.number_of_requests -= 1
                if self.number_of_requests <= 0:
                    rospy.signal_shutdown('test completed.')
class ExampleScheduler:

    def __init__(self):
        rospy.init_node("example_scheduler")
        # simplifying assumptions: all requests want a single robot,
        # and any of these will do:
        self.avail = deque([            # FIFO queue of available robots
            Resource(rapp='example_rapp', uri='rocon:/turtlebot/roberto'),
            Resource(rapp='example_rapp', uri='rocon:/turtlebot/marvin')])
        self.ready_queue = deque()      # FIFO queue of waiting requests
        self.sch = Scheduler(self.callback)
        rospy.spin()

    def callback(self, rset):
        """ Scheduler request callback. """
        rospy.logdebug('scheduler callback:')
        for rq in rset.values():
            rospy.logdebug('  ' + str(rq))
            if rq.msg.status == Request.NEW:
                self.queue(rset.requester_id, rq)
            elif rq.msg.status == Request.CANCELING:
                self.free(rset.requester_id, rq)

    def dispatch(self):
        """ Grant any available resources to waiting requests. """
        while len(self.ready_queue) > 0:
            if len(self.avail) == 0:    # no resources available?
                return
            resource = self.avail.popleft()
            requester_id, rq = self.ready_queue.popleft()
            try:                        # grant request & notify requester
                rq.grant([resource])
                self.sch.notify(requester_id)
                rospy.loginfo('Request granted: ' + str(rq.uuid))
            except (TransitionError, KeyError):
                # request no longer active or requester missing?
                # Put resource back at the front of the queue.
                self.avail.appendleft(resource)

    def free(self, requester_id, rq):
        """ Free all resources allocated for this request. """
        self.avail.extend(rq.allocations)
        rospy.loginfo('Request canceled: ' + str(rq.uuid))
        rq.close()
        self.dispatch()                 # grant waiting requests

    def queue(self, requester_id, rq):
        """ Add request to ready queue, making it wait. """
        try:
            rq.wait(reason=Request.BUSY)
        except TransitionError:         # request no longer active?
            return
        self.ready_queue.append((requester_id, rq))
        rospy.loginfo('Request queued: ' + str(rq.uuid))
        self.dispatch()
class ExampleScheduler:
    def __init__(self):
        rospy.init_node("example_scheduler")
        # simplifying assumptions: all requests want a single robot,
        # and any of these will do:
        self.avail = deque([  # FIFO queue of available robots
            Resource(rapp='example_rapp', uri='rocon:/turtlebot/roberto'),
            Resource(rapp='example_rapp', uri='rocon:/turtlebot/marvin')
        ])
        self.ready_queue = deque()  # FIFO queue of waiting requests
        self.sch = Scheduler(self.callback)
        rospy.spin()

    def callback(self, rset):
        """ Scheduler request callback. """
        rospy.logdebug('scheduler callback:')
        for rq in rset.values():
            rospy.logdebug('  ' + str(rq))
            if rq.msg.status == Request.NEW:
                self.queue(rset.requester_id, rq)
            elif rq.msg.status == Request.CANCELING:
                self.free(rset.requester_id, rq)

    def dispatch(self):
        """ Grant any available resources to waiting requests. """
        while len(self.ready_queue) > 0:
            if len(self.avail) == 0:  # no resources available?
                return
            resource = self.avail.popleft()
            requester_id, rq = self.ready_queue.popleft()
            try:  # grant request & notify requester
                rq.grant([resource])
                self.sch.notify(requester_id)
                rospy.loginfo('Request granted: ' + str(rq.uuid))
            except (TransitionError, KeyError):
                # request no longer active or requester missing?
                # Put resource back at the front of the queue.
                self.avail.appendleft(resource)

    def free(self, requester_id, rq):
        """ Free all resources allocated for this request. """
        self.avail.extend(rq.allocations)
        rospy.loginfo('Request canceled: ' + str(rq.uuid))
        rq.close()
        self.dispatch()  # grant waiting requests

    def queue(self, requester_id, rq):
        """ Add request to ready queue, making it wait. """
        try:
            rq.wait(reason=Request.BUSY)
        except TransitionError:  # request no longer active?
            return
        self.ready_queue.append((requester_id, rq))
        rospy.loginfo('Request queued: ' + str(rq.uuid))
        self.dispatch()
 def __init__(self):
     rospy.init_node("example_scheduler")
     # simplifying assumptions: all requests want a single robot,
     # and any of these will do:
     self.avail = deque([  # FIFO queue of available robots
         Resource(rapp='example_rapp', uri='rocon:/turtlebot/roberto'),
         Resource(rapp='example_rapp', uri='rocon:/turtlebot/marvin')
     ])
     self.ready_queue = deque()  # FIFO queue of waiting requests
     self.sch = Scheduler(self.callback)
     rospy.spin()
 def test_timeout_scheduler(self):
     """ Initialize ROCON scheduler node for timeout test. """
     rospy.init_node("timeout_scheduler")
     # simplifying assumptions: all requests want a single robot,
     # and any of these will do:
     self.avail = deque(  # FIFO queue of available robots
         [
             Resource(rapp='example_rapp', uri='rocon:/turtlebot/roberto'),
             Resource(rapp='example_rapp', uri='rocon:/turtlebot/marvin')
         ])
     self.ready_queue = deque()  # FIFO queue of waiting requests
     self.seen_requester = False
     self.timer = rospy.Timer(rospy.Duration(2.0), self.check_finished)
     self.sch = Scheduler(self.callback, frequency=1.0)
     rospy.spin()
 def __init__(self):
     rospy.init_node("example_scheduler")
     # simplifying assumptions: all requests want a single robot,
     # and any of these will do:
     self.avail = deque([            # FIFO queue of available robots
         Resource(rapp='example_rapp', uri='rocon:/turtlebot/roberto'),
         Resource(rapp='example_rapp', uri='rocon:/turtlebot/marvin')])
     self.ready_queue = deque()      # FIFO queue of waiting requests
     self.sch = Scheduler(self.callback)
     rospy.spin()
示例#8
0
    def __init__(self,
                 node_name='simple_scheduler',
                 period=rospy.Duration(1.0)):
        """ Constructor. """
        rospy.init_node(node_name)
        self.ready_queue = PriorityQueue()
        """ Queue of waiting requests. """
        self.blocked_queue = PriorityQueue()
        """ Queue of blocked requests. """
        self.period = period
        """ Time duration between periodic rescheduling. """
        self.notification_set = set()
        """ Set of requester identifiers to notify. """
        self.timer = rospy.Timer(self.period, self.reschedule)
        self.lock = threading.RLock()
        """ Big Scheduler Lock. """
        self.pool = SchedulerClients(lock=self.lock)
        """ Resource pool of known ROCON clients. """
        self.sch = Scheduler(self.callback, lock=self.lock)
        """ Scheduler request handler. """

        # Handle messages until canceled.
        rospy.spin()
 def test_timeout_scheduler(self):
     """ Initialize ROCON scheduler node for timeout test. """
     rospy.init_node("timeout_scheduler")
     # simplifying assumptions: all requests want a single robot,
     # and any of these will do:
     self.avail = deque(           # FIFO queue of available robots
         [Resource(
             rapp='example_rapp', uri='rocon:/turtlebot/roberto'),
          Resource(
             rapp='example_rapp', uri='rocon:/turtlebot/marvin')])
     self.ready_queue = deque()      # FIFO queue of waiting requests
     self.seen_requester = False
     self.timer = rospy.Timer(rospy.Duration(2.0), self.check_finished)
     self.sch = Scheduler(self.callback, frequency=1.0)
     rospy.spin()
    def __init__(self, node_name='simple_scheduler',
                 period=rospy.Duration(1.0)):
        """ Constructor. """
        rospy.init_node(node_name)
        self.ready_queue = PriorityQueue()
        """ Queue of waiting requests. """
        self.blocked_queue = PriorityQueue()
        """ Queue of blocked requests. """
        self.period = period
        """ Time duration between periodic rescheduling. """
        self.notification_set = set()
        """ Set of requester identifiers to notify. """
        self.timer = rospy.Timer(self.period, self.reschedule)
        self.lock = threading.RLock()
        """ Big Scheduler Lock. """
        self.pool = SchedulerClients(lock=self.lock)
        """ Resource pool of known ROCON clients. """
        self.sch = Scheduler(self.callback, lock=self.lock)
        """ Scheduler request handler. """

        # Handle messages until canceled.
        rospy.spin()
class SimpleSchedulerNode(object):
    """ Simple ROCON scheduler node.

    :param node_name: (str) Default name of scheduler node.
    :param period: (:class:`rospy.Duration`) Rescheduling time quantum.
    """
    def __init__(self, node_name='simple_scheduler',
                 period=rospy.Duration(1.0)):
        """ Constructor. """
        rospy.init_node(node_name)
        self.ready_queue = PriorityQueue()
        """ Queue of waiting requests. """
        self.blocked_queue = PriorityQueue()
        """ Queue of blocked requests. """
        self.period = period
        """ Time duration between periodic rescheduling. """
        self.notification_set = set()
        """ Set of requester identifiers to notify. """
        self.timer = rospy.Timer(self.period, self.reschedule)
        self.lock = threading.RLock()
        """ Big Scheduler Lock. """
        self.pool = SchedulerClients(lock=self.lock)
        """ Resource pool of known ROCON clients. """
        self.sch = Scheduler(self.callback, lock=self.lock)
        """ Scheduler request handler. """

        # Handle messages until canceled.
        rospy.spin()

    def callback(self, rset):
        """ Scheduler request callback.

        Called in the scheduler callback thread holding the Big
        Scheduler Lock.

        See: :class:`.concert_scheduler_requests.Scheduler` documentation.
        """
        rospy.logdebug('scheduler callback:')
        for rq in rset.values():
            rospy.logdebug('  ' + str(rq))
            if rq.msg.status == Request.NEW:
                self.queue(rq, rset.requester_id)
            elif rq.msg.status == Request.CANCELING:
                self.free(rq, rset.requester_id)
        self.dispatch()                 # try to allocate ready requests

    def dispatch(self):
        """ Grant any available resources to ready requests.

        Notifies all affected requesters.
        """
        while len(self.ready_queue) > 0:
            # Try to allocate top element in the ready queue.
            elem = self.ready_queue.pop()
            rospy.logdebug('trying to dispatch ' + str(elem))
            resources = []
            try:
                resources = self.pool.allocate(elem.request)
            except InvalidRequestError as ex:
                self.reject_request(elem, ex)
                continue                # skip to next queue element

            if not resources:           # top request cannot be satisfied?
                # Return it to head of queue.
                self.ready_queue.add(elem)
                break                   # stop looking

            try:
                self.pool.start_resources(resources)
                elem.request.grant(resources)
                rospy.loginfo('Request granted: ' + str(elem.request.uuid))
            except (FailedToStartRappError, TransitionError) as e:
                # Return allocated resources to the pool.
                rospy.logerr(str(e))
                self.pool.release_resources(resources)
            self.notification_set.add(elem.requester_id)

        # notify all affected requesters
        self.notify_requesters()

        # notify of resource pool changes, if necessary
        self.pool.notify_resources()

    def free(self, request, requester_id):
        """ Free all resources allocated for this *request*.

        :param request: (:class:`.ActiveRequest`)
        :param requester_id: (:class:`uuid.UUID`) Unique requester identifier.
        """
        self.pool.release_request(request)
        rospy.loginfo('Request canceled: ' + str(request.uuid))
        request.close()
        # remove request from any queues
        request_id = request.uuid
        for queue in [self.ready_queue, self.blocked_queue]:
            if request_id in queue:
                queue.remove(request_id)
                break                   # should not be in any other queue
        self.notification_set.add(requester_id)

    def notify_requesters(self):
        """ Notify affected requesters.

        :pre: self.notification_set contains requesters to notify.
        :post: self.notification_set is empty.
        """
        for requester_id in self.notification_set:
            try:
                self.sch.notify(requester_id)
            except KeyError:            # requester now missing?
                # shut down this requester
                self.shutdown_requester(requester_id)
        self.notification_set.clear()

    def queue(self, request, requester_id):
        """ Add *request* to ready queue, making it wait.

        :param request: resource request to be queued.
        :type request: :class:`.ActiveRequest`
        :param requester_id: Unique requester identifier.
        :type requester_id: :class:`uuid.UUID`
        """
        try:
            request.wait(reason=Request.BUSY)
        except TransitionError:         # request no longer active?
            return
        self.ready_queue.add(QueueElement(request, requester_id))
        rospy.loginfo('Request queued: ' + str(request.uuid))
        self.notification_set.add(requester_id)

    def reject_request(self, element, exception):
        """ Reject an invalid queue *element*.

        :param element: Queue element to reject.
        :type element: :class:`.QueueElement`
        :param exception: Associated exception object.
        """
        rospy.logwarn(str(exception))
        if hasattr(Request, "INVALID"):  # new reason code defined?
            element.request.cancel(Request.INVALID)
        else:
            element.request.cancel(Request.UNAVAILABLE)
        self.notification_set.add(element.requester_id)

    def reschedule(self, event):
        """ Periodic rescheduling thread.

        Moves requests that cannot be satisfied with
        currently-available resources to the blocked queue.  Also
        checks the blocked queue for requests that can be satisfied
        due to newly-arrived resources.

        Uses the Big Scheduler Lock to serialize these changes with
        operations done within the scheduler callback thread.
        """
        with self.lock:

            # Check ready queue for requests that cannot be resolved
            # using currently-available resources.
            if len(self.ready_queue) > 0:
                rospy.loginfo('reschedule ready ' + str(self.ready_queue))
            while len(self.ready_queue) > 0:
                # see if head of ready queue can be scheduled
                elem = self.ready_queue.peek()

                # see if all available or allocated resources would suffice
                criteria = {CurrentStatus.AVAILABLE, CurrentStatus.ALLOCATED}
                if self.pool.match_list(elem.request.msg.resources, criteria):
                    # request not blocked
                    break               # do not look further

                # move elem to blocked_queue
                self.ready_queue.pop()  # remove elem from ready queue
                rospy.loginfo('Request blocked: '
                              + str(elem.request.uuid))
                elem.request.wait(reason=Request.UNAVAILABLE)
                self.blocked_queue.add(elem)
                self.notification_set.add(elem.requester_id)

            # Check blocked queue for requests that can be resolved
            # using currently-available resources.
            for elem in self.blocked_queue.values():
                rospy.logdebug('checking request ' + str(elem.request.uuid))
                criteria = {CurrentStatus.AVAILABLE, CurrentStatus.ALLOCATED}
                if self.pool.match_list(elem.request.msg.resources, criteria):
                    rospy.loginfo('request ' + str(elem.request.uuid)
                                  + ' no longer blocked')
                    self.blocked_queue.remove(elem)
                    self.ready_queue.add(elem)
                    self.notification_set.add(elem.requester_id)

            # try to allocate any remaining ready requests
            self.dispatch()

    def shutdown_requester(self, requester_id):
        """ Shut down this requester, recovering all resources assigned. """
        for queue in [self.ready_queue, self.blocked_queue]:
            for rq in queue:
                if rq.requester_id == requester_id:
                    self.free(rq, requester_id)
class TestTimeoutScheduler(unittest.TestCase):

    def test_timeout_scheduler(self):
        """ Initialize ROCON scheduler node for timeout test. """
        rospy.init_node("timeout_scheduler")
        # simplifying assumptions: all requests want a single robot,
        # and any of these will do:
        self.avail = deque(           # FIFO queue of available robots
            [Resource(
                rapp='example_rapp', uri='rocon:/turtlebot/roberto'),
             Resource(
                rapp='example_rapp', uri='rocon:/turtlebot/marvin')])
        self.ready_queue = deque()      # FIFO queue of waiting requests
        self.seen_requester = False
        self.timer = rospy.Timer(rospy.Duration(2.0), self.check_finished)
        self.sch = Scheduler(self.callback, frequency=1.0)
        rospy.spin()

    def callback(self, rset):
        """ Scheduler request callback. """
        rospy.logdebug('scheduler callback:')
        for rq in rset.values():
            rospy.logdebug('  ' + str(rq))
            if rq.msg.status == Request.NEW:
                self.queue(rset.requester_id, rq)
            elif rq.msg.status == Request.CANCELING:
                self.free(rset.requester_id, rq)

    def check_finished(self, event):
        """ Timer event handler:

        Stops test after at least one requester has come and gone.
        """
        if self.seen_requester:
            if len(self.sch.requesters) == 0:
                rospy.loginfo('requester connection lost')
                rospy.signal_shutdown('test completed.')
        else:
            if len(self.sch.requesters) > 0:
                self.seen_requester = True

    def dispatch(self):
        """ Grant any available resources to waiting requests. """
        while len(self.ready_queue) > 0:
            if len(self.avail) == 0:    # no resources available?
                return
            resource = self.avail.popleft()
            requester_id, rq = self.ready_queue.popleft()
            try:                        # grant request & notify requester
                rq.grant([resource])
                self.sch.notify(requester_id)
                rospy.loginfo('Request granted: ' + str(rq.uuid))
            except (TransitionError, KeyError):
                # request no longer active or requester missing?
                # Put resource back at the front of the queue.
                self.avail.appendleft(resource)

    def free(self, requester_id, rq):
        """ Free all resources allocated for this request. """
        self.avail.extend(rq.allocations)
        rospy.loginfo('Request canceled: ' + str(rq.uuid))
        rq.close()
        self.dispatch()                 # grant waiting requests

    def queue(self, requester_id, rq):
        """ Add request to ready queue, making it wait. """
        try:
            rq.wait(reason=Request.BUSY)
        except TransitionError:         # request no longer active?
            return
        self.ready_queue.append((requester_id, rq))
        rospy.loginfo('Request queued: ' + str(rq.uuid))
        self.dispatch()
class TestTimeoutScheduler(unittest.TestCase):
    def test_timeout_scheduler(self):
        """ Initialize ROCON scheduler node for timeout test. """
        rospy.init_node("timeout_scheduler")
        # simplifying assumptions: all requests want a single robot,
        # and any of these will do:
        self.avail = deque(  # FIFO queue of available robots
            [
                Resource(rapp='example_rapp', uri='rocon:/turtlebot/roberto'),
                Resource(rapp='example_rapp', uri='rocon:/turtlebot/marvin')
            ])
        self.ready_queue = deque()  # FIFO queue of waiting requests
        self.seen_requester = False
        self.timer = rospy.Timer(rospy.Duration(2.0), self.check_finished)
        self.sch = Scheduler(self.callback, frequency=1.0)
        rospy.spin()

    def callback(self, rset):
        """ Scheduler request callback. """
        rospy.logdebug('scheduler callback:')
        for rq in rset.values():
            rospy.logdebug('  ' + str(rq))
            if rq.msg.status == Request.NEW:
                self.queue(rset.requester_id, rq)
            elif rq.msg.status == Request.CANCELING:
                self.free(rset.requester_id, rq)

    def check_finished(self, event):
        """ Timer event handler:

        Stops test after at least one requester has come and gone.
        """
        if self.seen_requester:
            if len(self.sch.requesters) == 0:
                rospy.loginfo('requester connection lost')
                rospy.signal_shutdown('test completed.')
        else:
            if len(self.sch.requesters) > 0:
                self.seen_requester = True

    def dispatch(self):
        """ Grant any available resources to waiting requests. """
        while len(self.ready_queue) > 0:
            if len(self.avail) == 0:  # no resources available?
                return
            resource = self.avail.popleft()
            requester_id, rq = self.ready_queue.popleft()
            try:  # grant request & notify requester
                rq.grant([resource])
                self.sch.notify(requester_id)
                rospy.loginfo('Request granted: ' + str(rq.uuid))
            except (TransitionError, KeyError):
                # request no longer active or requester missing?
                # Put resource back at the front of the queue.
                self.avail.appendleft(resource)

    def free(self, requester_id, rq):
        """ Free all resources allocated for this request. """
        self.avail.extend(rq.allocations)
        rospy.loginfo('Request canceled: ' + str(rq.uuid))
        rq.close()
        self.dispatch()  # grant waiting requests

    def queue(self, requester_id, rq):
        """ Add request to ready queue, making it wait. """
        try:
            rq.wait(reason=Request.BUSY)
        except TransitionError:  # request no longer active?
            return
        self.ready_queue.append((requester_id, rq))
        rospy.loginfo('Request queued: ' + str(rq.uuid))
        self.dispatch()
示例#14
0
class SimpleSchedulerNode(object):
    """ Simple ROCON scheduler node.

    :param node_name: (str) Default name of scheduler node.
    :param period: (:class:`rospy.Duration`) Rescheduling time quantum.
    """
    def __init__(self,
                 node_name='simple_scheduler',
                 period=rospy.Duration(1.0)):
        """ Constructor. """
        rospy.init_node(node_name)
        self.ready_queue = PriorityQueue()
        """ Queue of waiting requests. """
        self.blocked_queue = PriorityQueue()
        """ Queue of blocked requests. """
        self.period = period
        """ Time duration between periodic rescheduling. """
        self.notification_set = set()
        """ Set of requester identifiers to notify. """
        self.timer = rospy.Timer(self.period, self.reschedule)
        self.lock = threading.RLock()
        """ Big Scheduler Lock. """
        self.pool = SchedulerClients(lock=self.lock)
        """ Resource pool of known ROCON clients. """
        self.sch = Scheduler(self.callback, lock=self.lock)
        """ Scheduler request handler. """

        # Handle messages until canceled.
        rospy.spin()

    def callback(self, rset):
        """ Scheduler request callback.

        Called in the scheduler callback thread holding the Big
        Scheduler Lock.

        See: :class:`.concert_scheduler_requests.Scheduler` documentation.
        """
        rospy.logdebug('scheduler callback:')
        for rq in rset.values():
            rospy.logdebug('  ' + str(rq))
            if rq.msg.status == Request.NEW:
                self.queue(rq, rset.requester_id)
            elif rq.msg.status == Request.CANCELING:
                self.free(rq, rset.requester_id)
        self.dispatch()  # try to allocate ready requests

    def dispatch(self):
        """ Grant any available resources to ready requests.

        Notifies all affected requesters.
        """
        while len(self.ready_queue) > 0:
            # Try to allocate top element in the ready queue.
            elem = self.ready_queue.pop()
            rospy.logdebug('trying to dispatch ' + str(elem))
            resources = []
            try:
                resources = self.pool.allocate(elem.request)
            except InvalidRequestError as ex:
                self.reject_request(elem, ex)
                continue  # skip to next queue element

            if not resources:  # top request cannot be satisfied?
                # Return it to head of queue.
                self.ready_queue.add(elem)
                break  # stop looking

            try:
                self.pool.start_resources(resources)
                elem.request.grant(resources)
                rospy.loginfo('Request granted: ' + str(elem.request.uuid))
            except (FailedToStartRappError, TransitionError) as e:
                # Return allocated resources to the pool.
                rospy.logerr(str(e))
                self.pool.release_resources(resources)
            self.notification_set.add(elem.requester_id)

        # notify all affected requesters
        self.notify_requesters()

        # notify of resource pool changes, if necessary
        self.pool.notify_resources()

    def free(self, request, requester_id):
        """ Free all resources allocated for this *request*.

        :param request: (:class:`.ActiveRequest`)
        :param requester_id: (:class:`uuid.UUID`) Unique requester identifier.
        """
        self.pool.release_request(request)
        rospy.loginfo('Request canceled: ' + str(request.uuid))
        request.close()
        # remove request from any queues
        request_id = request.uuid
        for queue in [self.ready_queue, self.blocked_queue]:
            if request_id in queue:
                queue.remove(request_id)
                break  # should not be in any other queue
        self.notification_set.add(requester_id)

    def notify_requesters(self):
        """ Notify affected requesters.

        :pre: self.notification_set contains requesters to notify.
        :post: self.notification_set is empty.
        """
        for requester_id in self.notification_set:
            try:
                self.sch.notify(requester_id)
            except KeyError:  # requester now missing?
                # shut down this requester
                self.shutdown_requester(requester_id)
        self.notification_set.clear()

    def queue(self, request, requester_id):
        """ Add *request* to ready queue, making it wait.

        :param request: resource request to be queued.
        :type request: :class:`.ActiveRequest`
        :param requester_id: Unique requester identifier.
        :type requester_id: :class:`uuid.UUID`
        """
        try:
            request.wait(reason=Request.BUSY)
        except TransitionError:  # request no longer active?
            return
        self.ready_queue.add(QueueElement(request, requester_id))
        rospy.loginfo('Request queued: ' + str(request.uuid))
        self.notification_set.add(requester_id)

    def reject_request(self, element, exception):
        """ Reject an invalid queue *element*.

        :param element: Queue element to reject.
        :type element: :class:`.QueueElement`
        :param exception: Associated exception object.
        """
        rospy.logwarn(str(exception))
        if hasattr(Request, "INVALID"):  # new reason code defined?
            element.request.cancel(Request.INVALID)
        else:
            element.request.cancel(Request.UNAVAILABLE)
        self.notification_set.add(element.requester_id)

    def reschedule(self, event):
        """ Periodic rescheduling thread.

        Moves requests that cannot be satisfied with
        currently-available resources to the blocked queue.  Also
        checks the blocked queue for requests that can be satisfied
        due to newly-arrived resources.

        Uses the Big Scheduler Lock to serialize these changes with
        operations done within the scheduler callback thread.
        """
        with self.lock:

            # Check ready queue for requests that cannot be resolved
            # using currently-available resources.
            if len(self.ready_queue) > 0:
                rospy.loginfo('reschedule ready ' + str(self.ready_queue))
            while len(self.ready_queue) > 0:
                # see if head of ready queue can be scheduled
                elem = self.ready_queue.peek()

                # see if all available or allocated resources would suffice
                criteria = {CurrentStatus.AVAILABLE, CurrentStatus.ALLOCATED}
                if self.pool.match_list(elem.request.msg.resources, criteria):
                    # request not blocked
                    break  # do not look further

                # move elem to blocked_queue
                self.ready_queue.pop()  # remove elem from ready queue
                rospy.loginfo('Request blocked: ' + str(elem.request.uuid))
                elem.request.wait(reason=Request.UNAVAILABLE)
                self.blocked_queue.add(elem)
                self.notification_set.add(elem.requester_id)

            # Check blocked queue for requests that can be resolved
            # using currently-available resources.
            for elem in self.blocked_queue.values():
                rospy.logdebug('checking request ' + str(elem.request.uuid))
                criteria = {CurrentStatus.AVAILABLE, CurrentStatus.ALLOCATED}
                if self.pool.match_list(elem.request.msg.resources, criteria):
                    rospy.loginfo('request ' + str(elem.request.uuid) +
                                  ' no longer blocked')
                    self.blocked_queue.remove(elem)
                    self.ready_queue.add(elem)
                    self.notification_set.add(elem.requester_id)

            # try to allocate any remaining ready requests
            self.dispatch()

    def shutdown_requester(self, requester_id):
        """ Shut down this requester, recovering all resources assigned. """
        for queue in [self.ready_queue, self.blocked_queue]:
            for rq in queue:
                if rq.requester_id == requester_id:
                    self.free(rq, requester_id)