Repository: locustio/locust

Issue:
Cannot stop locust in case of exceptions in on_stop()
### Describe the bug
Locust execution cannot be stopped by the UI if any exception occurs during the cleanup phase in `on_stop() `. 

### Expected behavior
Execution of the task set should be stopped regardless of any exceptions in the on_stop methods.

### Actual behavior
In case of any exceptions in the on_stop method, the following error appears in the logs: 
```
Traceback (most recent call last):
  File "/.venv/lib/python3.11/site-packages/locust/user/task.py", line 353, in run
    self.wait()
  File "/.venv/lib/python3.11/site-packages/locust/user/task.py", line 445, in wait
    self._sleep(self.wait_time())
  File "/.venv/lib/python3.11/site-packages/locust/user/task.py", line 451, in _sleep
    gevent.sleep(seconds)
  File "/.venv/lib/python3.11/site-packages/gevent/hub.py", line 166, in sleep
    hub.wait(t)
  File "src/gevent/_hub_primitives.py", line 46, in gevent._gevent_c_hub_primitives.WaitOperationsGreenlet.wait
  File "src/gevent/_hub_primitives.py", line 55, in gevent._gevent_c_hub_primitives.WaitOperationsGreenlet.wait
  File "src/gevent/_waiter.py", line 154, in gevent._gevent_c_waiter.Waiter.get
  File "src/gevent/_greenlet_primitives.py", line 61, in gevent._gevent_c_greenlet_primitives.SwitchOutGreenletWithLoop.switch
  File "src/gevent/_greenlet_primitives.py", line 61, in gevent._gevent_c_greenlet_primitives.SwitchOutGreenletWithLoop.switch
  File "src/gevent/_greenlet_primitives.py", line 65, in gevent._gevent_c_greenlet_primitives.SwitchOutGreenletWithLoop.switch
  File "src/gevent/_gevent_c_greenlet_primitives.pxd", line 35, in gevent._gevent_c_greenlet_primitives._greenlet_switch
greenlet.GreenletExit
```
After that users will continue to send requests. They cannot be stopped through UI with Stop button, setting users to 0, etc. Then they only can be stopped by killing the locust instances. Test execution does not stop in headless mode as well.

### Steps to reproduce
```
from locust import HttpUser, task, between, TaskSet

class LocustUser(HttpUser):
    wait_time = between(1, 5)

    @task
    class MyTaskSet(TaskSet):
        @task
        def hello_world(self):
            self.client.get('/hello')

        def on_stop(self):
            assert True == False 
```

### Environment

- OS: MacOS 13.5.2
- Python version: 3.11.5
- Locust version: 2.16.1
- Locust command line that you ran: locust -f locust_file.py -u 2 --host http://localhost:8089  
- Locust file contents (anonymized if necessary): example above


Discussion:

Resolving patch:
diff --git a/locust/user/task.py b/locust/user/task.py
--- a/locust/user/task.py
+++ b/locust/user/task.py
@@ -352,13 +352,19 @@ def run(self):
                 else:
                     self.wait()
             except InterruptTaskSet as e:
-                self.on_stop()
+                try:
+                    self.on_stop()
+                except Exception:
+                    logging.error("Uncaught exception in on_stop: \n%s", traceback.format_exc())
                 if e.reschedule:
                     raise RescheduleTaskImmediately(e.reschedule) from e
                 else:
                     raise RescheduleTask(e.reschedule) from e
             except (StopUser, GreenletExit):
-                self.on_stop()
+                try:
+                    self.on_stop()
+                except Exception:
+                    logging.error("Uncaught exception in on_stop: \n%s", traceback.format_exc())
                 raise
             except Exception as e:
                 self.user.environment.events.user_error.fire(user_instance=self, exception=e, tb=e.__traceback__)


Test patch: