Skip to content

Invariant 2 promised something no cache can deliver - #51

Merged
sotashimozono merged 2 commits into
mainfrom
invariant-2
Sep 13, 2026
Merged

sotashimozono merged 2 commits into
mainfrom
invariant-2

Conversation

@sotashimozono

Copy link
Copy Markdown
Member

The claim was too strong

A revisit costs zero remote round trips; conditional GET is answered inside localhost.

The second half is true and verified. The first half is true inside the freshness window and cannot be true outside it: once a listing is not trusted, "is this still the current version?" cannot be answered without asking.

Measured against a real host over SSH:

revisit
straight after the load 0
~3 s later 10

That is the window doing its job. The README now says which window the claim holds in, and what a revisit costs outside it.

The e2e now checks it over the real transport

a_revisit_costs_no_remote_round_trips holds a FakeRemote and a three-byte fixture. It revisits instantly, so it never meets the freshness window — it would pass with any TTL, including none.

The new check drives a real browser against whatever host the run is pointed at, reads the round-trip count off GET /_control/hosts, and asserts a revisit adds nothing. Neutered against DEFAULT_TTL = Duration::ZERO:

ok    the first visit costs the remote something
FAIL  and a revisit inside the freshness window costs it nothing
      a revisit went to the remote, so it was not answered from cache
      85 !== 69

The unit test passes unchanged under that same neutering.

It navigates away and back rather than calling reload(). A reload tells the browser to re-fetch, and measuring it would not be measuring this claim.

What this came out of

An investigation into what looked like a much worse bug — "a revisit costs 13–30 round trips on every real site". It was not. Written up in full in #49, but in short:

  • Driven directly, the daemon's cache is exact: repeat read 0 round trips, range 0, conditional 0 with a 304. Through the alias Host and through the loopback fallback alike.
  • page.reload() is not a revisit.
  • "0 conditional requests" was false — request.headers() is the pre-flight snapshot and misses headers the browser adds. The 304s in the responses prove revalidation was happening.
  • 342 of 412 browser requests on one page were Chrome's own PDF viewer loading its internals, not requests to our origin at all.

I also built the listing TTL as an idle window rather than an absolute one, and reverted it: it passed neutered tests but made no measurable difference, and moved the Documenter page in the wrong direction. Shipping a public API change on an argument the measurement does not support is not how this repository works.

Verified

cargo fmt --check, clippy -D warnings, 177 tests, and the full e2e against a real host: all checks pass.

It said a revisit costs zero remote round trips, with no qualification. Once a
listing is not trusted, "is this still the current version?" cannot be answered
without asking — so outside the freshness window a revisit costs a listing
refresh by construction, and no amount of caching changes that.

Measured against a real host: a revisit straight after a load costs zero, and the
same revisit three seconds later costs ten. That is the window working, not the
cache failing.

The claim now says which window it is true in, and what it costs outside it. It is
still a strong claim and it is the one that holds.

The e2e now checks it over the real transport. The unit test for this holds a fake
remote and a three-byte fixture, so it revisits instantly and cannot see the
window at all — it would pass with any TTL, including none. Neutered against a
zero TTL the new check fails, which the unit test does not.

It navigates away and back rather than reloading. A reload tells the browser to
re-fetch; measuring that would not be measuring this claim.

Found while investigating what looked like a much worse bug, which it was not: the
daemon's cache does exactly what it says when driven directly, and two of the
numbers that suggested otherwise were instrument error. The detail is in #49.

Signed-off-by: Souta <shimozono-sota631@g.ecc.u-tokyo.ac.jp>
@codecov

codecov Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ng it

Two faults, both mine, both the same shape: the instrument was part of what it
measured.

It sampled the round-trip count off `GET /_control/hosts`, which runs `ssh -G`
once per configured host. Taken either side of a page load, that call takes long
enough to expire the listings being measured. It passed against a real host and
failed in CI, which is the worst way round. The count is now on `hello`, the cheap
route, which is two milliseconds.

Then, with a fast counter, it failed everywhere — because the slow call had been
acting as a settling delay, and without it the first load's tail landed in the
second load's bucket.

The deeper fault is that it asserted something nobody had established. Invariant 2
is about a request. "A whole browser page costs nothing the second time" is a
larger claim, and it is false: against a real host this fixture costs four round
trips on a browser revisit, because a page fetches things no HTML scan can see and
the freshness window is a wall clock a test has to race.

So it now checks what invariant 2 says, over the real transport, where it is
exact: a second read costs nothing, a range out of a held body costs nothing, and
the browser's own validator is answered here with a 304 rather than there. Against
a zero TTL all three fail; the unit tests pass unchanged, which is the point of
having these as well.

Signed-off-by: Souta <shimozono-sota631@g.ecc.u-tokyo.ac.jp>
@sotashimozono

Copy link
Copy Markdown
Member Author

Pushed a fix, and the reason is worth writing down

The check I added here failed in CI while passing against a real host. Twice over, the instrument was part of what it measured.

It sampled the counter off /_control/hosts, which runs ssh -G once per configured host. Taken either side of a page load, that call takes long enough to expire the listings being measured. The count now comes off hello — the cheap route, two milliseconds — and reporting it there is part of this change.

With a fast counter it then failed everywhere, because the slow call had been acting as a settling delay. Without it, the first load's tail landed in the second load's bucket.

And underneath both: it asserted something nobody had established. Invariant 2 is a claim about a request. "A whole browser page costs nothing the second time" is a larger claim and it is false — against a real host this fixture costs four round trips on a browser revisit, because a page fetches things no HTML scan can see, and the freshness window is a wall clock a test has to race.

So the check now tests what invariant 2 actually says, over the real transport, where it is exact:

ok    the first read of a file costs the remote something
ok    reading it again costs nothing
ok    and a range out of it costs nothing
ok    the browser's own validator is answered here, not there

Neutered against DEFAULT_TTL = Duration::ZERO, the last three fail (4 !== 0). The unit tests pass unchanged under the same neutering, which is why these are worth having as well.

This is the third time today that a measurement here turned out to perturb its subject — the other two are in #49. The pattern is worth naming: a counter you cannot read without disturbing is not a counter.

@sotashimozono
sotashimozono merged commit 5129212 into main Sep 13, 2026
19 checks passed
@sotashimozono
sotashimozono deleted the invariant-2 branch September 13, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant