Repository: metricq/metricq-python (language: python)

Issue:
Agents using the `IntervalSource` might enter a zombie state
If `update()` raises another exception than `PublishFailed`, the `task()` coroutine will finish, but the remainder of the source keeps somewhat alive. Hence, we got ourselves a zombie 🧟. 


Discussion / hints:

Fix patch:
diff --git a/MANIFEST.in b/MANIFEST.in
index c89580a4..897804ad 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,6 +5,7 @@ recursive-include tools *.py
 recursive-include docs *.py
 recursive-include docs *.rst
 recursive-include docs *.json
+include docs/_static/custom.css
 include docs/.nojekyll
 include docs/Makefile
 include docs/make.bat
@@ -26,3 +27,4 @@ exclude .git-blame-ignore-revs
 exclude .gitmodules
 exclude .pre-commit-config.yaml
 exclude .github_changelog_generator
+exclude .dockerignore
diff --git a/metricq/interval_source.py b/metricq/interval_source.py
index c7550dc9..fc5a9ecd 100644
--- a/metricq/interval_source.py
+++ b/metricq/interval_source.py
@@ -136,6 +136,12 @@ async def task(self) -> None:
                 # During the reconnection phase, we need to save the task from
                 # being cancelled.
                 logger.debug("Failed to send metric value: {}", e)
+            except Exception as e:
+                # Some exception escaped from the client, we want to stop the
+                # zombie apocalypse, so we request a stop here. If agents don't
+                # handle their exceptions, we will. Again, we need to save the
+                # task from being cancelled.
+                await self.stop(e)
 
             try:
                 if self._period is None:
diff --git a/pyproject.toml b/pyproject.toml
index b28e40ab..d7d69638 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -44,3 +44,6 @@ ignore = [
 
 [tool.mypy]
 exclude = 'setup.py|\.?.*env|conf.py|build'
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"


Test patch:
diff --git a/tests/test_interval_source.py b/tests/test_interval_source.py
index 7b6f845c..621ca0b0 100644
--- a/tests/test_interval_source.py
+++ b/tests/test_interval_source.py
@@ -3,6 +3,7 @@
 
 import pytest
 
+from metricq.exceptions import AgentStopped
 from metricq.interval_source import IntervalSource
 from metricq.types import Timedelta
 
@@ -68,3 +69,21 @@ def __init__(self) -> None:
 
         with pytest.raises(TypeError, match=r"Setting .* to None is not supported"):
             _FaultySource()
+
+
+async def test_update_method_exception() -> None:
+    class TestException(Exception):
+        pass
+
+    class TestIntervalSource(IntervalSource):
+        async def update(self) -> None:
+            raise TestException()
+
+    source = TestIntervalSource(
+        period=10.0,
+        token="test_source",
+        management_url="amqp://localhost",
+    )
+
+    with pytest.raises(AgentStopped):
+        await source.task()