diff --git a/bin/cloakserve b/bin/cloakserve index 0e545b2..27ea917 100755 --- a/bin/cloakserve +++ b/bin/cloakserve @@ -407,6 +407,8 @@ class ChromePool: self._default = cp logger.info("Chrome ready (seed=%s, port=%d, pid=%d)", actual_seed, port, process.pid) + if self._connections.get(seed_key, 0) == 0: + self._schedule_idle_cleanup(seed_key) return cp async def _cleanup_process(self, key: str) -> None: diff --git a/tests/test_cloakserve.py b/tests/test_cloakserve.py index 268b120..7be64cb 100644 --- a/tests/test_cloakserve.py +++ b/tests/test_cloakserve.py @@ -7,7 +7,7 @@ import json import sys from pathlib import Path from types import SimpleNamespace -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest @@ -585,6 +585,25 @@ class TestConnectionTracking: asyncio.run(run()) + @patch("subprocess.Popen") + def test_launch_without_connections_schedules_idle_cleanup(self, mock_popen): + async def run(): + mock_proc = MagicMock() + mock_proc.pid = 12345 + mock_proc.poll.return_value = None + mock_popen.return_value = mock_proc + + pool = self._make_pool(idle_timeout=1.0) + with patch.object(pool, "_wait_for_cdp", return_value=True): + await pool.get_or_launch("seed1") + + assert "seed1" in pool._idle_tasks + pool._cancel_idle_cleanup("seed1") + await asyncio.sleep(0) + assert "seed1" not in pool._idle_tasks + + asyncio.run(run()) + # --------------------------------------------------------------------------- # Seed validation (CVE fix — path traversal via fingerprint param)