Skip to content

Commit 8dacd04

Browse files
committed
fix: add process whitelist to _free_port, add worker startup interval config
1 parent 6cc1c4a commit 8dacd04

4 files changed

Lines changed: 48 additions & 9 deletions

File tree

src/manager/service.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def load_config(self) -> Dict[str, Any]:
7676
"launch_mode": "headless",
7777
"script_injection_enabled": False,
7878
"worker_mode_enabled": False,
79+
"worker_startup_interval": 5,
7980
"log_enabled": True,
8081
}
8182

@@ -450,7 +451,7 @@ async def _start_worker_mode(
450451
started_count += 1
451452
logger.info(f"启动Worker {worker_id} (端口:{worker.port})")
452453
if index < len(worker_ids) - 1:
453-
await asyncio.sleep(15)
454+
await asyncio.sleep(config.get("worker_startup_interval", 5))
454455

455456
if self.stop_event.is_set():
456457
self.service_status = "stopped"

src/static/dashboard.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,13 @@ <h2 class="text-2xl font-bold mb-6 text-white border-b border-[#30363d] pb-2">{{
410410
</div>
411411
</div>
412412

413+
<div v-if="config.worker_mode_enabled" class="bg-[#161b22] p-4 rounded-lg border border-[#30363d]">
414+
<label class="block text-sm font-medium text-gray-300 mb-2">{{ t('config.workerStartupInterval') }}</label>
415+
<input v-model.number="config.worker_startup_interval" type="number" min="1" max="120"
416+
class="w-full bg-[#0d1117] border border-[#30363d] rounded px-3 py-2 text-white focus:border-blue-500 focus:outline-none transition">
417+
<p class="text-xs text-gray-600 mt-1">{{ t('config.workerStartupIntervalDesc') }}</p>
418+
</div>
419+
413420
<div class="flex justify-end pt-4">
414421
<button @click="saveConfig"
415422
class="px-6 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded font-medium transition shadow-lg">{{
@@ -1047,7 +1054,11 @@ <h3 class="text-lg font-bold text-white mb-4">{{ currentPrompt.text }}</h3>
10471054

10481055
const fetchConfig = async () => {
10491056
const res = await fetch('/api/config');
1050-
config.value = await res.json();
1057+
const data = await res.json();
1058+
if (data.worker_startup_interval === undefined) {
1059+
data.worker_startup_interval = 5;
1060+
}
1061+
config.value = data;
10511062
};
10521063

10531064
const saveConfig = async () => {

src/static/i18n.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ const locales = {
5757
scriptInjectionDesc: '启用后可添加 AI Studio 未列出的模型(已被弃用)',
5858
logEnabled: '启用日志',
5959
logEnabledDesc: '禁用日志可提升性能(需重启服务生效)',
60+
workerStartupInterval: 'Worker 启动间隔(秒)',
61+
workerStartupIntervalDesc: '多个 Worker 之间依次启动的等待时间,默认 5 秒',
6062
save: '保存配置'
6163
},
6264
auth: {
@@ -209,6 +211,8 @@ const locales = {
209211
scriptInjectionDesc: '啟用後可添加 AI Studio 未列出的模型(已被棄用)',
210212
logEnabled: '啟用日誌',
211213
logEnabledDesc: '禁用日誌可提升性能(需重啟服務生效)',
214+
workerStartupInterval: 'Worker 啟動間隔(秒)',
215+
workerStartupIntervalDesc: '多個 Worker 之間依次啟動的等待時間,預設 5 秒',
212216
save: '儲存設定'
213217
},
214218
auth: {
@@ -361,6 +365,8 @@ const locales = {
361365
scriptInjectionDesc: 'Enable to add unlisted models in AI Studio (Deprecated)',
362366
logEnabled: 'Enable Logging',
363367
logEnabledDesc: 'Disabling logs improves performance (requires service restart)',
368+
workerStartupInterval: 'Worker Startup Interval (seconds)',
369+
workerStartupIntervalDesc: 'Delay between starting each Worker, default 5 seconds',
364370
save: 'Save Config'
365371
},
366372
auth: {
@@ -513,6 +519,8 @@ const locales = {
513519
scriptInjectionDesc: '有効にするとAI Studioに未掲載のモデルを追加できます(非推奨)',
514520
logEnabled: 'ログを有効にする',
515521
logEnabledDesc: 'ログを無効にするとパフォーマンスが向上します(サービス再起動が必要)',
522+
workerStartupInterval: 'Worker起動間隔(秒)',
523+
workerStartupIntervalDesc: '各Workerの起動間隔、デフォルト5秒',
516524
save: '設定を保存'
517525
},
518526
auth: {
@@ -665,6 +673,8 @@ const locales = {
665673
scriptInjectionDesc: '활성화하면 AI Studio에 나열되지 않은 모델 추가 가능 (더 이상 사용되지 않음)',
666674
logEnabled: '로깅 활성화',
667675
logEnabledDesc: '로그 비활성화 시 성능 향상 (서비스 재시작 필요)',
676+
workerStartupInterval: 'Worker 시작 간격 (초)',
677+
workerStartupIntervalDesc: '각 Worker 시작 사이의 대기 시간, 기본 5초',
668678
save: '설정 저장'
669679
},
670680
auth: {

src/worker/pool.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,9 @@ def _cancel_restart(self, worker_id: str):
244244
if task and not task.done():
245245
task.cancel()
246246

247+
_SAFE_PROCESS_NAMES = {"python", "python3", "pythonw", "camoufox", "firefox", "uv"}
248+
247249
def _free_port(self, port: int) -> None:
248-
"""Kill any process occupying the given TCP port."""
249250
try:
250251
if platform.system() == "Windows":
251252
result = subprocess.run(
@@ -257,20 +258,36 @@ def _free_port(self, port: int) -> None:
257258
parts = line.split()
258259
pid = parts[-1]
259260
if pid.isdigit():
260-
subprocess.run(
261-
["taskkill", "/PID", pid, "/F"],
262-
capture_output=True
261+
proc_check = subprocess.run(
262+
["tasklist", "/FI", f"PID eq {pid}", "/FO", "CSV", "/NH"],
263+
capture_output=True, text=True
263264
)
264-
logger.info(f"Freed port {port} (killed PID {pid})")
265+
proc_name = proc_check.stdout.strip().split(",")[0].strip('"').lower().replace(".exe", "")
266+
if proc_name in self._SAFE_PROCESS_NAMES:
267+
subprocess.run(
268+
["taskkill", "/PID", pid, "/F"],
269+
capture_output=True
270+
)
271+
logger.info(f"Freed port {port} (killed {proc_name} PID {pid})")
272+
else:
273+
logger.warning(f"Port {port} occupied by {proc_name} (PID {pid}), skipping")
265274
else:
266275
result = subprocess.run(
267276
["lsof", "-ti", f"tcp:{port}"],
268277
capture_output=True, text=True
269278
)
270279
for pid in result.stdout.strip().splitlines():
271280
if pid.isdigit():
272-
subprocess.run(["kill", "-9", pid], capture_output=True)
273-
logger.info(f"Freed port {port} (killed PID {pid})")
281+
proc_check = subprocess.run(
282+
["ps", "-p", pid, "-o", "comm="],
283+
capture_output=True, text=True
284+
)
285+
proc_name = proc_check.stdout.strip().lower()
286+
if any(safe in proc_name for safe in self._SAFE_PROCESS_NAMES):
287+
subprocess.run(["kill", "-9", pid], capture_output=True)
288+
logger.info(f"Freed port {port} (killed {proc_name} PID {pid})")
289+
else:
290+
logger.warning(f"Port {port} occupied by {proc_name} (PID {pid}), skipping")
274291
except Exception as e:
275292
logger.warning(f"Failed to free port {port}: {e}")
276293

0 commit comments

Comments
 (0)