Skip to content

Commit 89f9ee4

Browse files
Add timeout and symbol length limits to demangling (#540)
1 parent 9cf20b4 commit 89f9ee4

File tree

2 files changed

+35
-4
lines changed

2 files changed

+35
-4
lines changed

src/launchpad/utils/apple/cwl_demangle.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import shutil
55
import subprocess
66
import tempfile
7+
import time
78
import uuid
89

910
from dataclasses import dataclass
@@ -13,6 +14,12 @@
1314

1415
logger = get_logger(__name__)
1516

17+
# Default timeout for cwl-demangle subprocess (in seconds)
18+
DEFAULT_DEMANGLE_TIMEOUT = int(os.environ.get("LAUNCHPAD_DEMANGLE_TIMEOUT", "10"))
19+
20+
# Default chunk size for batching symbols
21+
DEFAULT_CHUNK_SIZE = int(os.environ.get("LAUNCHPAD_DEMANGLE_CHUNK_SIZE", "500"))
22+
1623

1724
@dataclass
1825
class CwlDemangleResult:
@@ -75,7 +82,7 @@ def demangle_all(self) -> Dict[str, CwlDemangleResult]:
7582
self.queue.clear()
7683

7784
# Process in chunks to avoid potential issues with large inputs
78-
chunk_size = 5000
85+
chunk_size = DEFAULT_CHUNK_SIZE
7986
total_chunks = (len(names) + chunk_size - 1) // chunk_size
8087

8188
chunks: List[Tuple[List[str], int]] = []
@@ -84,7 +91,7 @@ def demangle_all(self) -> Dict[str, CwlDemangleResult]:
8491
chunk_idx = i // chunk_size
8592
chunks.append((chunk, chunk_idx))
8693

87-
# Only use parallel processing if workload justifies multiprocessing overhead (≥4 chunks = ≥20K symbols)
94+
# Only use parallel processing if workload justifies multiprocessing overhead (≥4 chunks)
8895
do_in_parallel = self.use_parallel and total_chunks >= 4
8996

9097
logger.debug(
@@ -149,6 +156,8 @@ def _demangle_chunk_worker(
149156
if not chunk:
150157
return {}
151158

159+
start_time = time.time()
160+
152161
binary_path = shutil.which("cwl-demangle")
153162
if binary_path is None:
154163
logger.error("cwl-demangle binary not found in PATH")
@@ -178,9 +187,16 @@ def _demangle_chunk_worker(
178187
command_parts.append("--continue-on-error")
179188

180189
try:
181-
result = subprocess.run(command_parts, capture_output=True, text=True, check=True)
190+
result = subprocess.run(
191+
command_parts, capture_output=True, text=True, check=True, timeout=DEFAULT_DEMANGLE_TIMEOUT
192+
)
193+
except subprocess.TimeoutExpired:
194+
elapsed = time.time() - start_time
195+
logger.exception("cwl-demangle subprocess timed out", extra={"chunk_idx": chunk_idx, "elapsed": elapsed})
196+
return {}
182197
except subprocess.CalledProcessError:
183-
logger.exception(f"cwl-demangle failed for chunk {chunk_idx}")
198+
elapsed = time.time() - start_time
199+
logger.exception("cwl-demangle subprocess failed", extra={"chunk_idx": chunk_idx, "elapsed": elapsed})
184200
return {}
185201

186202
batch_result = json.loads(result.stdout)

tests/integration/test_cwl_demangle.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,21 @@ def test_environment_variable_disables_parallel(self):
100100
demangler = CwlDemangler()
101101
assert demangler.use_parallel is False
102102

103+
def test_timeout_configuration(self):
104+
"""Test LAUNCHPAD_DEMANGLE_TIMEOUT env var configures timeout."""
105+
demangler = CwlDemangler()
106+
107+
# Test with custom timeout
108+
with mock.patch.dict(os.environ, {"LAUNCHPAD_DEMANGLE_TIMEOUT": "10"}):
109+
# Generate a few symbols to trigger sequential processing
110+
symbols = self._generate_symbols(100)
111+
for symbol in symbols:
112+
demangler.add_name(symbol)
113+
114+
result = demangler.demangle_all()
115+
# Should succeed with custom timeout
116+
assert len(result) == 100
117+
103118
def _generate_symbols(self, count: int) -> list[str]:
104119
"""Generate valid Swift mangled symbols."""
105120
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

0 commit comments

Comments
 (0)