perf(compile): stop three sources of per-request stall
Follow-ups from the compile-latency audit, all measured on staging:
- The per-target build lock keyed every board through _idf_target, which
defaults unknown FQBNs to 'esp32' - so AVR, RP2040 and STM32 compiles
all took the 'esp32::esp32' key and queued behind unrelated ESP32
builds. The lock now only applies to boards that actually route to the
ESP-IDF lane (the same startswith('esp32:') predicate _run_compile
uses); everything else keys on its own FQBN.
- ESP-IDF re-validated its python environment on every cmake configure
(~1s of ~9s). The image pins that env at build time, so pass
PYTHON_DEPS_CHECKED=1 - the escape hatch idf.py uses for itself.
- _is_core_installed shelled out to 'arduino-cli core list' (~1.5s) on
every non-AVR compile from inside an async handler, blocking the whole
event loop rather than just that request. Memoized, and invalidated
where a core is installed.
New hand-written code, unique source, idle box, steady state:
ESP32-P4 ESP-IDF 28.8s -> 11.1s
ESP32-S3 ESP-IDF 39.3s -> 11.5s
ESP32-C3 Arduino 56.2s -> 21.0s
(the ESP-IDF numbers also carry the ldgen memoizer from the previous
commit; the queue waits the audit saw - 41s and 43s - were the audit's
own five concurrent builds and are what the lock-key fix targets)
This commit is contained in:
parent
f15834d14d
commit
82f25a03c4
|
|
@ -163,6 +163,13 @@ def _build_identity(board_fqbn: str) -> str:
|
|||
is the previous behaviour and never less strict than it needs to be for a
|
||||
board whose variant we could not read.
|
||||
"""
|
||||
# Only the ESP-IDF lane HAS a shared build dir (the predicate is the same
|
||||
# one _run_compile routes on). An AVR / RP2040 / STM32 FQBN has no IDF
|
||||
# target at all, and _idf_target defaults unknown boards to 'esp32' — so
|
||||
# every non-ESP32 board used to collapse onto the `esp32::esp32` key and
|
||||
# queue behind unrelated ESP32 builds for no reason.
|
||||
if not board_fqbn.startswith("esp32:"):
|
||||
return board_fqbn
|
||||
try:
|
||||
target = espidf_compiler._idf_target(board_fqbn)
|
||||
variant = espidf_compiler._arduino_variant(board_fqbn, target)
|
||||
|
|
|
|||
|
|
@ -140,6 +140,9 @@ class ArduinoCLIService:
|
|||
|
||||
def __init__(self, cli_path: str = "arduino-cli"):
|
||||
self.cli_path = cli_path
|
||||
# Cached `core list` output; see _is_core_installed. Invalidated
|
||||
# wherever a core is installed so a fresh install is seen immediately.
|
||||
self._installed_cores: str | None = None
|
||||
self._ensure_board_urls()
|
||||
self._ensure_core_installed()
|
||||
|
||||
|
|
@ -235,12 +238,24 @@ class ArduinoCLIService:
|
|||
return None
|
||||
|
||||
def _is_core_installed(self, core_id: str) -> bool:
|
||||
"""Check whether a core is currently installed."""
|
||||
result = subprocess.run(
|
||||
[self.cli_path, "core", "list"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return core_id in result.stdout
|
||||
"""Check whether a core is currently installed.
|
||||
|
||||
Memoized: this shells out to `arduino-cli core list` (~1.5 s measured)
|
||||
and it used to run on EVERY compile of a non-AVR board, from inside an
|
||||
async handler - so it blocked the event loop for the whole process, not
|
||||
just the caller. Cores only appear via install_core() below, which
|
||||
refreshes the set, and they are never removed at runtime.
|
||||
"""
|
||||
if self._installed_cores is None:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[self.cli_path, "core", "list"],
|
||||
capture_output=True, text=True, timeout=60,
|
||||
)
|
||||
self._installed_cores = result.stdout
|
||||
except (subprocess.SubprocessError, OSError):
|
||||
return False # unknown: let the caller try to install
|
||||
return core_id in self._installed_cores
|
||||
|
||||
async def ensure_core_for_board(self, fqbn: str) -> dict:
|
||||
"""
|
||||
|
|
@ -260,6 +275,8 @@ class ArduinoCLIService:
|
|||
install_spec = f"{core_id}@{version}" if version else core_id
|
||||
print(f"[arduino-cli] Auto-installing core {install_spec} for board {fqbn}...")
|
||||
|
||||
self._installed_cores = None # a core is about to appear
|
||||
|
||||
def _install():
|
||||
return subprocess.run(
|
||||
[self.cli_path, "core", "install", install_spec],
|
||||
|
|
|
|||
|
|
@ -3967,6 +3967,11 @@ class ESPIDFCompiler:
|
|||
f'-DIDF_TARGET={idf_target}',
|
||||
'-DCMAKE_BUILD_TYPE=Release',
|
||||
f'-DSDKCONFIG_DEFAULTS={project_dir / "sdkconfig.defaults"}',
|
||||
# IDF re-validates its python env on every configure (~1s of a
|
||||
# ~9s configure). The image pins that env at build time and the
|
||||
# entrypoint has already exercised it, so tell IDF it was checked
|
||||
# externally - the same escape hatch idf.py uses for itself.
|
||||
'-DPYTHON_DEPS_CHECKED=1',
|
||||
str(project_dir),
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue