diff --git a/lib/project-avd.sh b/lib/project-avd.sh index ed53c4a..e428738 100644 --- a/lib/project-avd.sh +++ b/lib/project-avd.sh @@ -38,9 +38,24 @@ running_avds() { # # Cached, because asking costs one console round trip per attached device and # this runs on every single adb call -- which includes a `tap` inside a loop. -# The cache is checked against `adb devices` first, so an emulator that has -# gone away cannot leave a stale serial behind; that check is one round trip -# whatever the answer. +# +# The cache is validated by asking the cached serial its AVD name, not merely +# by checking that the serial is still attached. Those are different questions +# once ports get recycled, and the cheap one says yes about the wrong device: +# emulator serials are ports, so when ai-app-2 exits and ai-app starts, ai-app +# takes 5554 and ai-app-2's stale cache entry stays "valid" for ever. Measured +# on 2026-09-04, with both ai-app.serial and ai-app-2.serial holding +# emulator-5554: every adb, ui-trace and emu call from ~/repos/ai-app-2 +# resolved to the other checkout's emulator, and `emu up` refused to start the +# right AVD because it believed it was already running. That is precisely the +# fan-out this whole tool exists to prevent, and it is silent. +# +# The name check is one round trip on a cache hit, against the one device the +# cache names -- not the per-device sweep the cache exists to avoid. It lives +# here rather than in any caller because `adb`, `ui-trace` and both of `emu`'s +# uses (resolving a serial, and deciding whether to start) all come through +# this function, and a check in only one of them is the same bug with a +# smaller blast radius. avd_serial() { _adb=$1 _avd=$2 @@ -48,11 +63,12 @@ avd_serial() { _cache="$_dir/$_avd.serial" if [ -r "$_cache" ]; then _cached=$(cat "$_cache") - if "$_adb" devices 2>/dev/null | awk '$2 == "device" {print $1}' | - grep -qx "$_cached"; then + _name=$("$_adb" -s "$_cached" emu avd name 2>/dev/null | head -n1 | tr -d '\r') + if [ "$_name" = "$_avd" ]; then echo "$_cached" return 0 fi + rm -f "$_cache" fi _found=$(running_avds "$_adb" | awk -F'\t' -v want="$_avd" '$2 == want {print $1; exit}') [ -z "$_found" ] && return 1