Skip to content

Commit 1da687d

Browse files
committed
refactor: удержать фоновые asyncio task до завершения
- сохранить ссылки на задачи инвалидации кэша в реестре - автоматически удалять завершённые task из набора - добавить тест на жизненный цикл фоновой задачи
1 parent b87f82b commit 1da687d

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

app/routes/tasks.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,20 @@
3636
RedisClient = Annotated[Redis, Depends(get_redis)]
3737
TaskFilters = Annotated[TaskListFilters, Depends()]
3838
logger = getLogger(__name__)
39+
_BACKGROUND_CACHE_INVALIDATION_TASKS: set[asyncio.Task[None]] = set()
3940

4041
_DUE_DATE_ERROR = HTTPException(
4142
status_code=status.HTTP_400_BAD_REQUEST,
4243
detail="Дедлайн не может быть в прошлом",
4344
)
4445

4546

47+
def _track_background_task(task: asyncio.Task[None]) -> None:
48+
"""Сохраняет strong reference на фоновую задачу до её завершения."""
49+
_BACKGROUND_CACHE_INVALIDATION_TASKS.add(task)
50+
task.add_done_callback(_BACKGROUND_CACHE_INVALIDATION_TASKS.discard)
51+
52+
4653
def _schedule_invalidate_user_tasks_cache(redis: Redis, user_id: UUID) -> None:
4754
"""Запускает инвалидацию кэша списка задач в фоне (fire-and-forget).
4855
Исключения в фоновой задаче логируются и не пробрасываются в вызывающий код.
@@ -58,7 +65,7 @@ async def _run() -> None:
5865
exc_info=True,
5966
)
6067

61-
asyncio.create_task(_run())
68+
_track_background_task(asyncio.create_task(_run()))
6269

6370

6471
@router.post(

tests/unit/routes/test_tasks_fire_forget.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
import pytest
1010

11-
from app.routes.tasks import _schedule_invalidate_user_tasks_cache
11+
from app.routes.tasks import (
12+
_BACKGROUND_CACHE_INVALIDATION_TASKS,
13+
_schedule_invalidate_user_tasks_cache,
14+
)
1215

1316
pytestmark = pytest.mark.asyncio
1417

@@ -28,3 +31,22 @@ async def test_schedule_invalidate_runs_in_background_and_does_not_propagate_exc
2831
await asyncio.sleep(0.05)
2932

3033
assert "Не удалось инвалидировать кэш задач пользователя" in caplog.text
34+
35+
36+
async def test_schedule_invalidate_keeps_task_reference_until_completion() -> None:
37+
"""Фоновая задача хранится в реестре до завершения и затем автоматически удаляется."""
38+
redis = AsyncMock()
39+
user_id = uuid4()
40+
41+
with patch(
42+
"app.routes.tasks.delete_cached_tasks_list_for_user",
43+
new_callable=AsyncMock,
44+
) as delete_cached_tasks:
45+
_schedule_invalidate_user_tasks_cache(redis, user_id)
46+
47+
assert len(_BACKGROUND_CACHE_INVALIDATION_TASKS) == 1
48+
49+
await asyncio.sleep(0.05)
50+
51+
delete_cached_tasks.assert_awaited_once_with(redis, user_id)
52+
assert not _BACKGROUND_CACHE_INVALIDATION_TASKS

0 commit comments

Comments
 (0)