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 __init__(self, node_name='simple_scheduler', period=rospy.Duration(1.0)): """ Constructor. """ rospy.init_node(node_name) self.pool = ResourcePool() self.pub_pool = rospy.Publisher('resource_pool', KnownResources, queue_size=1, latch=True) self.pub_pool.publish(self.pool.known_resources()) self.sub_client = rospy.Subscriber('concert_client_changes', ConcertClients, self.track_clients, queue_size=1, tcp_nodelay=True) self.ready_queue = PriorityQueue() """ Queue of waiting requests. """ self.blocked_queue = PriorityQueue() """ Queue of blocked requests. """ self.period = period """ Duration between periodic rescheduling. """ self.notification_set = set() """ Set of requester identifiers to notify. """ self.timer = rospy.Timer(self.period, self.reschedule) try: topic_name = rospy.get_param('~topic_name') self.sch = Scheduler(self.callback, topic=topic_name) except KeyError: self.sch = Scheduler(self.callback) # use the default rospy.spin()
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()
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.")
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 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 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 SimpleSchedulerNode(object): """ Simple scheduler node. :param node_name: (str) Default name of scheduler node. :param period: (:class:`rospy.Duration`) Rescheduling time quantum. Derived versions of this class can implement different scheduling policies. """ def __init__(self, node_name='simple_scheduler', period=rospy.Duration(1.0)): """ Constructor. """ rospy.init_node(node_name) self.pool = ResourcePool() self.pub_pool = rospy.Publisher('resource_pool', KnownResources, queue_size=1, latch=True) self.pub_pool.publish(self.pool.known_resources()) self.sub_client = rospy.Subscriber('concert_client_changes', ConcertClients, self.track_clients, queue_size=1, tcp_nodelay=True) self.ready_queue = PriorityQueue() """ Queue of waiting requests. """ self.blocked_queue = PriorityQueue() """ Queue of blocked requests. """ self.period = period """ Duration between periodic rescheduling. """ self.notification_set = set() """ Set of requester identifiers to notify. """ self.timer = rospy.Timer(self.period, self.reschedule) try: topic_name = rospy.get_param('~topic_name') self.sch = Scheduler(self.callback, topic=topic_name) except KeyError: self.sch = Scheduler(self.callback) # use the default rospy.spin() def callback(self, rset): """ Scheduler request callback. Called in the scheduler callback thread holding the Big Scheduler Lock. See: :class:`.rocon_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() 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: elem.request.grant(resources) rospy.loginfo( 'Request granted: ' + str(elem.request.uuid)) except TransitionError: # request no longer active? # Return allocated resources to the pool. self.pool.release_resources(resources) self.notification_set.add(elem.requester_id) # notify all affected requesters self.notify_requesters() # update resource_pool topic, if anything changed if self.pool.changed: self.pub_pool.publish(self.pool.known_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. Uses the Big Scheduler Lock to serialize changes with operations done within the scheduler callback thread. """ with self.sch.lock: while len(self.ready_queue) > 0: # see if head of ready queue can be scheduled elem = self.ready_queue.pop() # 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 self.ready_queue.add(elem) break # done rescheduling # move elem to blocked_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) # 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) def track_clients(self, msg): """ Concert clients message callback. Updates the resource pool based on client changes published by the concert conductor. Uses the Big Scheduler Lock to serialize changes with operations done within the scheduler callback thread. """ with self.sch.lock: self.pool.update(msg.clients)
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()