From: 
Subject: Debian changes

The Debian packaging of python-bellows is maintained in git, using a workflow
similar to the one described in dgit-maint-merge(7).
The Debian delta is represented by this one combined patch; there isn't a
patch queue that can be represented as a quilt series.

A detailed breakdown of the changes is available from their canonical
representation -- git commits in the packaging repository.
For example, to see the changes made by the Debian maintainer in the first
upload of upstream version 1.2.3, you could use:

    % git clone https://git.dgit.debian.org/python-bellows
    % cd python-bellows
    % git log --oneline 1.2.3..debian/1.2.3-1 -- . ':!debian'

(If you have dgit, use `dgit clone python-bellows`, rather than plain `git clone`.)

We don't use debian/source/options single-debian-patch because it has bugs.
Therefore, NMUs etc. may nevertheless have made additional patches.

---

diff --git a/bellows/thread.py b/bellows/thread.py
index 4311768..3ca053f 100644
--- a/bellows/thread.py
+++ b/bellows/thread.py
@@ -1,6 +1,7 @@
 import asyncio
 from concurrent.futures import ThreadPoolExecutor
 import functools
+import inspect
 import logging
 
 LOGGER = logging.getLogger(__name__)
@@ -52,21 +53,25 @@ class EventLoopThread:
         return thread_complete
 
     def force_stop(self):
-        if self.loop is None:
+        loop = self.loop
+
+        if loop is None or loop.is_closed():
             return
 
         def cancel_tasks_and_stop_loop():
-            tasks = asyncio.all_tasks(loop=self.loop)
+            tasks = asyncio.all_tasks(loop=loop)
 
             for task in tasks:
-                self.loop.call_soon_threadsafe(task.cancel)
+                loop.call_soon_threadsafe(task.cancel)
 
             gather = asyncio.gather(*tasks, return_exceptions=True)
-            gather.add_done_callback(
-                lambda _: self.loop.call_soon_threadsafe(self.loop.stop)
-            )
+            gather.add_done_callback(lambda _: loop.call_soon_threadsafe(loop.stop))
 
-        self.loop.call_soon_threadsafe(cancel_tasks_and_stop_loop)
+        try:
+            loop.call_soon_threadsafe(cancel_tasks_and_stop_loop)
+        except RuntimeError:
+            if not loop.is_closed():
+                raise
 
 
 class ThreadsafeProxy:
@@ -98,7 +103,7 @@ class ThreadsafeProxy:
                 # Disconnected
                 LOGGER.warning("Attempted to use a closed event loop")
                 return
-            if asyncio.iscoroutinefunction(func):
+            if inspect.iscoroutinefunction(func):
                 future = asyncio.run_coroutine_threadsafe(call(), loop)
                 return asyncio.wrap_future(future, loop=curr_loop)
             else:
diff --git a/pyproject.toml b/pyproject.toml
index 98db341..463b20c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,6 +21,7 @@ dependencies = [
 ]
 
 [tool.setuptools.packages.find]
+include = ["bellows", "bellows.*"]
 exclude = ["tests", "tests.*"]
 
 [project.optional-dependencies]
diff --git a/tests/test_thread.py b/tests/test_thread.py
index 72efa70..c7b2e39 100644
--- a/tests/test_thread.py
+++ b/tests/test_thread.py
@@ -85,6 +85,15 @@ async def test_thread_already_stopped(thread):
     thread.force_stop()
 
 
+async def test_thread_loop_already_closed():
+    loop = asyncio.new_event_loop()
+    thread = EventLoopThread()
+    thread.loop = loop
+
+    loop.close()
+    thread.force_stop()
+
+
 async def test_thread_run_coroutine_threadsafe(thread):
     inner_loop = None
 
