Repository: opsdroid/opsdroid

Issue:
Opsdroid does not exit with ctrl-c
<!-- Before you post an issue or if you are unsure about something join our matrix channel https://riot.im/app/#/room/#opsdroid-general:matrix.org and ask away! We are more than happy to help you. -->
# Description
Opsdroid is not exiting with ctrl-c. When starting Opsdroid locally, I was able to exit the bot with the ctrl-c escape key.  Now it only hangs, and I have to kill the process manually to exit,


## Steps to Reproduce
- Clone the repo and install the latest branch of Opsdroid
- execute `opsdroid start`

I have tested this without a configuration file, so it uses the default one, and also is happening when trying to connect with the slack connector.

## Expected Functionality
ctrl-c signal should work when executed.


## Experienced Functionality
- ctl-c signal is not working and opsdroid just hangs there. I has to be manually killed

## Versions
- **Opsdroid version:** v0.19.0+84.ga99dfb6
- **Python version:** Tested in 3.7.7 and 3.8.5 same issue on both
- **OS/Docker version:** Ubuntu 20.04

## Additional Details
Any other details you wish to include such as screenshots, console messages, etc.

```

--> opsdroid start
INFO opsdroid.logging: ========================================
INFO opsdroid.logging: Started opsdroid v0.19.0+84.ga99dfb6.
INFO opsdroid: ========================================
INFO opsdroid: You can customise your opsdroid by modifying your configuration.yaml.
INFO opsdroid: Read more at: http://opsdroid.readthedocs.io/#configuration
INFO opsdroid: Watch the Get Started Videos at: http://bit.ly/2fnC0Fh
INFO opsdroid: Install Opsdroid Desktop at: 
https://github.com/opsdroid/opsdroid-desktop/releases
INFO opsdroid: ========================================
WARNING opsdroid.loader: No databases in configuration. This will cause skills which store things in memory to lose data when opsdroid is restarted.


INFO opsdroid.core: Opsdroid is now running, press ctrl+c to exit.
INFO opsdroid.web: Started web server on http://0.0.0.0:8080
^C^Cc^C^C^C^C^C^C
```

<!-- Love opsdroid? Please consider supporting our collective:
 +👉  https://opencollective.com/opsdroid/donate -->



Discussion:
cc @cognifloyd. I think this may be due to recent signalling work.

Resolving patch:
diff --git a/opsdroid/core.py b/opsdroid/core.py
--- a/opsdroid/core.py
+++ b/opsdroid/core.py
@@ -56,7 +56,7 @@ def __init__(self, config=None, config_path=None):
         if os.name != "nt":
             for sig in (signal.SIGINT, signal.SIGTERM):
                 self.eventloop.add_signal_handler(
-                    sig, lambda: asyncio.ensure_future(self.handle_signal())
+                    sig, lambda: asyncio.ensure_future(self.handle_stop_signal())
                 )
             self.eventloop.add_signal_handler(
                 signal.SIGHUP, lambda: asyncio.ensure_future(self.reload())
@@ -155,9 +155,10 @@ def is_running(self):
         """Check whether opsdroid is running."""
         return self._running
 
-    async def handle_signal(self):
+    async def handle_stop_signal(self):
         """Handle signals."""
         self._running = False
+        await self.stop()
         await self.unload()
 
     def run(self):


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -217,13 +217,15 @@ async def mockedskill(message):
         mockedskill.config = {}
         return mockedskill
 
-    async def test_handle_signal(self):
+    async def test_handle_stop_signal(self):
         with OpsDroid() as opsdroid:
             opsdroid._running = True
             self.assertTrue(opsdroid.is_running())
+            opsdroid.stop = amock.CoroutineMock()
             opsdroid.unload = amock.CoroutineMock()
-            await opsdroid.handle_signal()
+            await opsdroid.handle_stop_signal()
             self.assertFalse(opsdroid.is_running())
+            self.assertTrue(opsdroid.stop.called)
             self.assertTrue(opsdroid.unload.called)
 
     async def test_unload_and_stop(self):