-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue/7694 executor cleanup #7765
Changes from 5 commits
0d980fd
ed30a8c
ca300eb
2578db9
996c9a5
0531ecb
368a2d3
77b38ba
9ccb204
4d633a8
f216ec4
b120b1a
750f0a3
400f1d7
dd1cea8
78063a9
7181c79
9470de1
44c2622
ef2851f
fcd7eb4
8170cc7
42f2455
25c95e0
32c98cd
8c58031
76ee7ac
2653273
2a715fd
c9239d9
5532ff4
33fd080
a35b38d
76a2842
1a6682a
74c2c50
79b04f7
ed14455
f2e10d0
9fd9db2
61c8aa2
f2954c9
895b619
fffd6a1
cf7a1c7
a0469d5
9d4f7f1
8d5963a
558cdde
b536897
92f4b90
142cfc0
08ee9e9
025a81b
060a005
e815992
2bedb4a
9b7c7b9
7cba0da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,7 @@ | |
import uuid | ||
from abc import abstractmethod | ||
from asyncio import Future, transports | ||
from typing import Any, Coroutine, Optional, Sequence | ||
from typing import Optional, Sequence | ||
|
||
import inmanta.agent.cache | ||
import inmanta.agent.executor | ||
|
@@ -606,13 +606,9 @@ def __init__( | |
self.max_executors_per_agent = inmanta.agent.config.agent_executor_cap.get() | ||
|
||
self.stopping: bool = False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've already given more comment than I'm comfortable, so feel free to ignore this one: when used like this, I usually call this 'running' (and invert it) to make sure it doesn't only indicate 'stopping' but also 'stopped' |
||
# Keep a reference to all active backgrounds tasks to make sure they don't disappear | ||
# mid-execution and to be able to cancel them when shutting down. | ||
self.background_tasks: set[asyncio.Task[None]] = set() | ||
|
||
# Keep a reference to the last scheduled cleanup task for easy cancelation | ||
# and rescheduling when the retention time is updated | ||
self.next_executor_cleanup_task: Optional[asyncio.Task[None]] = None | ||
# We keep a reference to the periodic cleanup task to prevent it | ||
# from disappearing mid-execution https://docs.python.org/3.11/library/asyncio-task.html#creating-tasks | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wow, nice! |
||
self.cleanup_job: Optional[asyncio.Task[None]] = None | ||
|
||
def __add_executor(self, theid: executor.ExecutorId, the_executor: MPExecutor) -> None: | ||
self.executor_map[theid] = the_executor | ||
|
@@ -784,13 +780,10 @@ def _make_child(self, name: str, log_level: int, cli_log: bool) -> tuple[multipr | |
return p, parent_conn | ||
|
||
async def start(self) -> None: | ||
self.start_executor_retention_time_watcher() | ||
self.start_periodic_executor_cleanup() | ||
self.cleanup_job = asyncio.create_task(self.cleanup_inactive_executors()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the join method, you could |
||
|
||
async def stop(self) -> None: | ||
self.stopping = True | ||
for task in self.background_tasks: | ||
task.cancel() | ||
await asyncio.gather(*(child.stop() for child in self.children)) | ||
|
||
async def force_stop(self, grace_time: float) -> None: | ||
|
@@ -806,49 +799,11 @@ async def stop_for_agent(self, agent_name: str) -> list[MPExecutor]: | |
await asyncio.gather(*(child.stop() for child in children)) | ||
return children | ||
|
||
def start_executor_retention_time_watcher(self) -> None: | ||
""" | ||
Schedule a task that periodically checks for updates to the executor | ||
retention time. If such an update is detected, the previous recurring cleanup | ||
task is canceled and a new recurring cleanup task with the updated retention time | ||
is scheduled. | ||
""" | ||
|
||
async def watch_retention_time() -> None: | ||
if self.stopping: | ||
# Stop periodic re-scheduling when the manager is shutting down | ||
return | ||
|
||
retention_time_from_config = inmanta.agent.config.agent_executor_retention_time.get() | ||
|
||
if self.executor_retention_time != retention_time_from_config: | ||
self.executor_retention_time = retention_time_from_config | ||
self.start_periodic_executor_cleanup(restart=True) | ||
|
||
await asyncio.sleep(2) | ||
self.add_background_task(watch_retention_time()) | ||
|
||
self.add_background_task(watch_retention_time()) | ||
|
||
def start_periodic_executor_cleanup(self, restart: Optional[bool] = False) -> None: | ||
async def cleanup_inactive_executors(self) -> None: | ||
""" | ||
Schedule a task that removes inactive executors and re-schedules | ||
itself based on the retention time. | ||
|
||
:param restart: Cancel the previously scheduled cleanup task and start a new one. | ||
This task periodically cleans up idle executors | ||
""" | ||
|
||
async def cleanup_inactive_executors() -> None: | ||
""" | ||
This task cleans up idle executors and reschedules itself | ||
""" | ||
if self.stopping: | ||
# Stop periodic re-scheduling when the manager is shutting down | ||
return | ||
|
||
if restart and self.next_executor_cleanup_task: | ||
self.next_executor_cleanup_task.cancel() | ||
|
||
while not self.stopping: | ||
cleanup_start = datetime.datetime.now().astimezone() | ||
|
||
reschedule_interval: float = self.executor_retention_time | ||
|
@@ -884,18 +839,3 @@ async def cleanup_inactive_executors() -> None: | |
cleanup_end = datetime.datetime.now().astimezone() | ||
|
||
await asyncio.sleep(max(0.0, reschedule_interval - (cleanup_end - cleanup_start).total_seconds())) | ||
self.next_executor_cleanup_task = self.add_background_task(cleanup_inactive_executors()) | ||
|
||
self.add_background_task(cleanup_inactive_executors()) | ||
|
||
def add_background_task(self, coro: Coroutine[Any, Any, None]) -> asyncio.Task[None]: | ||
""" | ||
Wrap a coroutine in an asyncio Task and register it. | ||
""" | ||
task = asyncio.create_task(coro) | ||
|
||
self.background_tasks.add(task) | ||
# Make sure the task removes itself from the set when it's done | ||
task.add_done_callback(self.background_tasks.discard) | ||
|
||
return task |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,12 +16,14 @@ | |
Contact: [email protected] | ||
""" | ||
|
||
import functools | ||
import logging | ||
import os | ||
import random | ||
import socket | ||
|
||
import netifaces | ||
import pytest | ||
from tornado import netutil | ||
|
||
import inmanta.agent.config as cfg | ||
|
@@ -347,3 +349,18 @@ def test_option_is_list_empty(): | |
option: Option = Option("test", "list", "default,values", "documentation", cfg.is_list) | ||
option.set("") | ||
assert option.get() == [] | ||
|
||
|
||
def test_option_is_lower_bounded_int(): | ||
lower_bound = 1 | ||
option: Option = Option( | ||
"test", "lb_int", lower_bound, "documentation", functools.partial(cfg.is_lower_bounded_int, lower_bound) | ||
) | ||
option.set("2") | ||
assert option.get() == 2 | ||
|
||
option.set("0") | ||
with pytest.raises(ValueError) as exc_info: | ||
option.get() | ||
|
||
assert f"Value can not be lower than {lower_bound}" in str(exc_info.value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use the docstring of the function to generate documentation, so this won't look so pretty in the docs