The reporter's setup: a PWA whose start_url is served with
cache-control: private, max-age=1209600, and the document
replaces itself with a newer one via location.reload(). While
the app is running everything looks right. Kill it (swipe from recents or
force-stop), launch it again, and Chrome renders the old
document out of the HTTP cache. I could only reproduce this on Android;
desktop Chrome always came back with the fresh document.
There is no clean shutdown on Android. The app gets a STOPPED signal when it leaves the screen, and whatever happens afterwards (freezer, SIGKILL) gives the process no further chance to write anything. Both HTTP cache backends keep part of a written entry in memory for as long as the entry is open: SimpleCache holds the headers (stream 0) plus the EOF/CRC records and only writes them when the entry closes; the SQL backend buffers header and body writes in the entry object and flushes when it is destroyed. In this repro the entry for the displayed start_url document is still open when the kill arrives, so the fresh copy never reaches disk and the stale one wins the next cold launch.
CL
8051344 hooks the same STOPPED signal the SimpleCache index already uses
to save itself (INDEX_WRITE_REASON_ANDROID_STOPPED) and
checkpoints every open entry: buffered headers and data go to disk, the
entry stays open and usable. On a Pixel 6a with the patched APK, a cold
relaunch after force-stop shows the fresh document with zero network
requests, and the on-disk entry files are md5-identical before and after
the kill. Deterministic unit tests cover both backends.
python3 repro_server.py --port 8000 --reload-delay 4000
adb reverse tcp:8000 tcp:8000 # or any public HTTPS tunnel
# stock Chrome: install the PWA, open it (red V1 shows, reloads to green V2),
# force-stop, cold-launch the installed app
# -> red V1 shows again for 4s = the bug
# with ChromePublic-fixed.apk
# -> green V2 immediately, zero requests
The stale V1 self-heals through its own reload, so with no delay the bug
is invisible on screen. The reliable signal on a cold relaunch is the
combination of sessionStorage.reloaded == 1 (the V1 bootstrap
ran) and exactly one GET / in the server log (V1's reload).
Zero requests and no flag means the cache served fresh V2 directly, which
is what the patched build does.