class_docstr: The dispatcher is responsible for assigning tasks to free workers, report results back to the master and
  communicate to the nameserver.
code: def discover_workers(self):
      self.discover_cond.acquire()
      sleep_interval = 1
  
      while True:
          self.logger.debug("DISPATCHER: Starting worker discovery")
          update = False
  
          with Pyro4.locateNS(
              host=self.nameserver, port=self.nameserver_port
          ) as ns:
              worker_names = ns.list(
                  prefix="hpbandster.run_%s.worker." % self.run_id
              )
              self.logger.debug(
                  "DISPATCHER: Found %i potential workers, %i currently in the pool."
                  % (len(worker_names), len(self.worker_pool))
              )
  
              for wn, uri in worker_names.items():
                  if not wn in self.worker_pool:
                      w = Worker(wn, uri)
                      if not w.is_alive():
                          self.logger.debug(
                              "DISPATCHER: skipping dead worker, %s"
                              % wn
                          )
                          continue
                      update = True
                      self.logger.info(
                          "DISPATCHER: discovered new worker, %s" % wn
                      )
                      self.worker_pool[wn] = w
  
          # check the current list of workers
          crashed_jobs = set()
  
          all_workers = list(self.worker_pool.keys())
          for wn in all_workers:
              # remove dead entries from the nameserver
              if not self.worker_pool[wn].is_alive():
                  self.logger.info(
                      "DISPATCHER: removing dead worker, %s" % wn
                  )
                  update = True
                  # todo check if there were jobs running on that that need to be rescheduled
  
                  current_job = self.worker_pool[wn].runs_job
  
                  if not current_job is None:
                      self.logger.info(
                          "Job %s was not completed" % str(current_job)
                      )
                      crashed_jobs.add(current_job)
  
                  del self.worker_pool[wn]
                  self.idle_workers.discard(wn)
                  continue
  
              if not self.worker_pool[wn].is_busy():
                  self.idle_workers.add(wn)
  
          # try to submit more jobs if something changed
          if update:
              if not self.queue_callback is None:
                  self.discover_cond.release()
                  self.queue_callback(len(self.worker_pool))
                  self.discover_cond.acquire()
              self.runner_cond.notify()
  
          for crashed_job in crashed_jobs:
              self.discover_cond.release()
              self.register_result(
                  crashed_job,
                  {
                      "result": None,
                      "exception": "Worker died unexpectedly.",
                  },
              )
              self.discover_cond.acquire()
  
          self.logger.debug("DISPATCHER: Finished worker discovery")
  
          # if (len(self.worker_pool) == 0 ): # ping for new workers if no workers are currently available
          # 	self.logger.debug('No workers available! Keep pinging')
          # 	self.discover_cond.wait(sleep_interval)
          # 	sleep_interval *= 2
          # else:
          self.discover_cond.wait(self.ping_interval)
  
          if self.shutdown_all_threads:
              self.logger.debug(
                  "DISPATCHER: discover_workers shutting down"
              )
              self.runner_cond.notify()
              self.discover_cond.release()
              return
  
imports: import threading
  import logging
  import queue
  import time
  import Pyro4
label_desc: Use image node_id 6 for calling the Dispatcher obj's underlying member method code with example usage: obj.discover_workers() without return types