Skip to content

Commit 8f2562e

Browse files
Added a done() method to BackgroundService to check for completion
This is usefull if we have type: `asyncio.Task | Actor` and need to check if actor finished. Signed-off-by: Elzbieta Kotulska <[email protected]>
1 parent e1ce323 commit 8f2562e

File tree

3 files changed

+40
-17
lines changed

3 files changed

+40
-17
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
## New Features
1212

13-
<!-- Here goes the main new features and examples or instructions on how to use them -->
13+
* Added a `done()` method to `BackgroundService` to check for completion.
1414

1515
## Bug Fixes
1616

src/frequenz/sdk/actor/_background_service.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ def is_running(self) -> bool:
161161
"""
162162
return any(not task.done() for task in self._tasks)
163163

164+
def done(self) -> bool:
165+
"""Return whether this background service is done.
166+
167+
A service is considered done when all tasks
168+
are finished or raised exception or were cancelled.
169+
170+
Returns:
171+
Whether this background service is done.
172+
"""
173+
return not self.is_running
174+
164175
def cancel(self, msg: str | None = None) -> None:
165176
"""Cancel all running tasks spawned by this background service.
166177

tests/actor/test_background_service.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ def event_loop_policy() -> async_solipsism.EventLoopPolicy:
1717
return async_solipsism.EventLoopPolicy()
1818

1919

20+
def assert_service_done(service: BackgroundService) -> None:
21+
"""Assert if service is done."""
22+
assert service.is_running is False
23+
assert service.done() is True
24+
25+
26+
def assert_service_is_running(service: BackgroundService) -> None:
27+
"""Assert if service is running."""
28+
assert service.is_running is True
29+
assert service.done() is False
30+
31+
2032
class FakeService(BackgroundService):
2133
"""A background service that does nothing."""
2234

@@ -49,7 +61,7 @@ async def test_construction_defaults() -> None:
4961
fake_service = FakeService()
5062
assert fake_service.name == str(id(fake_service))
5163
assert fake_service.tasks == set()
52-
assert fake_service.is_running is False
64+
assert_service_done(fake_service)
5365
assert str(fake_service) == f"FakeService[{fake_service.name}]"
5466
assert repr(fake_service) == f"FakeService(name={fake_service.name!r}, tasks=set())"
5567

@@ -59,50 +71,50 @@ async def test_construction_custom() -> None:
5971
fake_service = FakeService(name="test")
6072
assert fake_service.name == "test"
6173
assert fake_service.tasks == set()
62-
assert fake_service.is_running is False
74+
assert_service_done(fake_service)
6375

6476

6577
async def test_start_await() -> None:
6678
"""Test a background service starts and can be awaited."""
6779
fake_service = FakeService(name="test")
6880
assert fake_service.name == "test"
69-
assert fake_service.is_running is False
81+
assert_service_done(fake_service)
7082

7183
# Is a no-op if the service is not running
7284
await fake_service.stop()
73-
assert fake_service.is_running is False
85+
assert_service_done(fake_service)
7486

7587
fake_service.start()
76-
assert fake_service.is_running is True
88+
assert_service_is_running(fake_service)
7789

7890
# Should stop immediately
7991
async with asyncio.timeout(1.0):
8092
await fake_service
8193

82-
assert fake_service.is_running is False
94+
assert_service_done(fake_service)
8395

8496

8597
async def test_start_stop() -> None:
8698
"""Test a background service starts and stops correctly."""
8799
fake_service = FakeService(name="test", sleep=2.0)
88100
assert fake_service.name == "test"
89-
assert fake_service.is_running is False
101+
assert_service_done(fake_service)
90102

91103
# Is a no-op if the service is not running
92104
await fake_service.stop()
93-
assert fake_service.is_running is False
105+
assert_service_done(fake_service)
94106

95107
fake_service.start()
96-
assert fake_service.is_running is True
108+
assert_service_is_running(fake_service)
97109

98110
await asyncio.sleep(1.0)
99-
assert fake_service.is_running is True
111+
assert_service_is_running(fake_service)
100112

101113
await fake_service.stop()
102-
assert fake_service.is_running is False
114+
assert_service_done(fake_service)
103115

104116
await fake_service.stop()
105-
assert fake_service.is_running is False
117+
assert_service_done(fake_service)
106118

107119

108120
@pytest.mark.parametrize("method", ["await", "wait", "stop"])
@@ -113,7 +125,7 @@ async def test_start_and_crash(
113125
exc = RuntimeError("error")
114126
fake_service = FakeService(name="test", exc=exc)
115127
assert fake_service.name == "test"
116-
assert fake_service.is_running is False
128+
assert_service_done(fake_service)
117129

118130
fake_service.start()
119131
with pytest.raises(BaseExceptionGroup) as exc_info:
@@ -140,10 +152,10 @@ async def test_start_and_crash(
140152
async def test_async_context_manager() -> None:
141153
"""Test a background service works as an async context manager."""
142154
async with FakeService(name="test", sleep=1.0) as fake_service:
143-
assert fake_service.is_running is True
155+
assert_service_is_running(fake_service)
144156
# Is a no-op if the service is running
145157
fake_service.start()
146158
await asyncio.sleep(0)
147-
assert fake_service.is_running is True
159+
assert_service_is_running(fake_service)
148160

149-
assert fake_service.is_running is False
161+
assert_service_done(fake_service)

0 commit comments

Comments
 (0)