code: def kill(self, exc_info=None):
          
          if self._being_killed:
              # this happens if a managed thread exits with an exception
              # while the container is being killed or if multiple errors
              # happen simultaneously
              _log.debug('already killing %s ... waiting for death', self)
              try:
                  self._died.wait()
              except:
                  pass  # don't re-raise if we died with an exception
              return
  
          self._being_killed = True
  
          if self._died.ready():
              _log.debug('already stopped %s', self)
              return
  
          if exc_info is not None:
              _log.info('killing %s due to %s', self, exc_info[1])
          else:
              _log.info('killing %s', self)
  
          # protect against extensions that throw during kill; the container
          # is already dying with an exception, so ignore anything else
          def safely_kill_extensions(ext_set):
              try:
                  ext_set.kill()
              except Exception as exc:
                  _log.warning('Extension raised `%s` during kill', exc)
  
          safely_kill_extensions(self.entrypoints.all)
          self._kill_worker_threads()
          safely_kill_extensions(self.extensions.all)
          self._kill_managed_threads()
  
          self.started = False
  
          # if `kill` is called after `stop`, they race to send this
          if not self._died.ready():
              self._died.send(None, exc_info)
docs: Kill the container in a semi-graceful way.
  
          Entrypoints are killed, followed by any active worker threads.
          Next, dependencies are killed. Finally, any remaining managed threads
          are killed.
  
          If ``exc_info`` is provided, the exception will be raised by
          :meth:`~wait``.