Validate the serial cache by AVD name, not by the serial still existing
Emulator serials are ports, and ports get recycled. The cache checked only that its cached serial was still in `adb devices`, which stays true after the AVD that was on it exits and a different one takes the port -- so the entry never expires and every call resolves to somebody else's emulator. Measured on 2026-09-04 with ai-app.serial and ai-app-2.serial both holding emulator-5554: from ~/repos/ai-app-2, `emu up` reported "'ai-app-2' is already running (emulator-5554)" and handed back a device that `adb -s emulator-5554 emu avd name` calls ai-app. Every adb, ui-trace and emu call from that checkout went to the other session's emulator, and the right AVD could not be started at all because the tool believed it was up. That is the exact fan-out this repo exists to prevent, and nothing says so. Asking the cached serial its name is one round trip against one device, not the per-device sweep the cache exists to avoid, and a mismatch drops the entry so the slow path can answer. It goes in avd_serial because `adb`, `ui-trace` and both of `emu`'s uses -- resolving a serial, and deciding whether to start one -- already come through it; a check in only one of them would be the same bug with a smaller blast radius. Found from ai-app-2; ai-app's session confirmed its own work had landed on the right device and asked for the fix here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b678bf3ccd
commit
43f3786bed
1 file changed
+21
-5
+21
-5
@@ -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
|
||||
|
||||
Reference in new issue
Block a user