Skip to content

Commit 8c693a3

Browse files
authored
fix: cache file race condition (#2872)
1 parent 1db1cbd commit 8c693a3

1 file changed

Lines changed: 36 additions & 10 deletions

File tree

apiserver/paasng/paasng/platform/agent_sandbox/management/commands/cleanup_expired_agent_sandboxes.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
"""清理过期的沙箱实例"""
1919

20-
import threading
2120
import uuid
2221
from concurrent.futures import ThreadPoolExecutor, as_completed
2322
from dataclasses import dataclass
@@ -26,6 +25,8 @@
2625
from django.db import close_old_connections
2726
from django.utils import timezone
2827

28+
from paas_wl.infras.resources.base.base import get_client_by_cluster_name
29+
from paas_wl.infras.resources.base.kube_client import CoreDynamicClient
2930
from paasng.platform.agent_sandbox.constants import SandboxStatus
3031
from paasng.platform.agent_sandbox.exceptions import SandboxError
3132
from paasng.platform.agent_sandbox.models import Sandbox
@@ -50,6 +51,24 @@ class _DeleteResult:
5051
error: SandboxError | None = None
5152

5253

54+
def _stdout_log_line(message: str) -> str:
55+
ts = timezone.localtime(timezone.now()).strftime("%Y-%m-%d %H:%M:%S")
56+
return f"[{ts}] {message}"
57+
58+
59+
def _warmup_kube_discovery_cache(cluster_names: set[str]) -> None:
60+
"""
61+
预热(初始化)kubernetes dynamic client 的 discovery 磁盘缓存。
62+
避免并发初始化缓存时同时读写该文件。
63+
Ref: https://github.com/kubernetes-client/python/issues/2037
64+
"""
65+
for target in sorted(cluster_names):
66+
if not target:
67+
continue
68+
client = get_client_by_cluster_name(target)
69+
CoreDynamicClient(client)
70+
71+
5372
def _delete_expired_sandbox(sandbox_uuid: uuid.UUID) -> _DeleteResult:
5473
close_old_connections()
5574
sandbox = Sandbox.objects.select_related("application").get(uuid=sandbox_uuid)
@@ -99,31 +118,38 @@ def handle(self, dry_run, concurrency, *args, **options):
99118
return
100119

101120
sandbox_uuids = list(sandboxes.values_list("uuid", flat=True))
121+
targets = set(sandboxes.values_list("target", flat=True).distinct())
102122
deleted_count = 0
103123
failed_count = 0
104-
output_lock = threading.Lock()
105124
workers = min(concurrency, len(sandbox_uuids))
106125

126+
if targets:
127+
self.stdout.write(f"预热 {len(targets)} 个集群的 K8s discovery 缓存: {', '.join(sorted(targets))}")
128+
_warmup_kube_discovery_cache(targets)
129+
107130
self.stdout.write(f"并发数: {workers}")
108131

109132
with ThreadPoolExecutor(max_workers=workers) as executor:
110133
futures = [executor.submit(_delete_expired_sandbox, sbx_uuid) for sbx_uuid in sandbox_uuids]
111134
for future in as_completed(futures):
112135
result = future.result()
113-
with output_lock:
114-
if result.success:
115-
deleted_count += 1
116-
self.stdout.write(
136+
if result.success:
137+
deleted_count += 1
138+
self.stdout.write(
139+
_stdout_log_line(
117140
f"已删除 {deleted_count}/{total} uuid={result.sandbox.uuid} "
118141
f"name={result.sandbox.name} app_code={result.sandbox.application.code}"
119142
)
120-
else:
121-
failed_count += 1
122-
self.stdout.write(
123-
self.style.ERROR(
143+
)
144+
else:
145+
failed_count += 1
146+
self.stdout.write(
147+
self.style.ERROR(
148+
_stdout_log_line(
124149
f"删除失败 uuid={result.sandbox.uuid} name={result.sandbox.name} "
125150
f"app_code={result.sandbox.application.code}: {result.error}"
126151
)
127152
)
153+
)
128154

129155
self.stdout.write(f"\n清理完成: 删除 {deleted_count} 个, 失败 {failed_count} 个")

0 commit comments

Comments
 (0)