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
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()
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()
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()