<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def kill(self, exc_info=None):
 """ 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``. """

        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)