Skip to main content

Module conditional

Module conditional 

Source
Expand description

Conditional GETs: ask GitHub whether a listing changed before paying for it again.

§Why

A demand poll re-reads the same few listings every interval, and on an idle host they are the same bytes every time. GitHub’s REST documentation (“Best practices for using the REST API”, Use conditional requests if appropriate, https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api#use-conditional-requests-if-appropriate):

Most endpoints return an etag header, and many endpoints return a last-modified header. You can use the values of these headers to make conditional GET requests. … If the data has not changed, you will receive a 304 Not Modified response.

Making a conditional request does not count against your primary rate limit if a 304 response is returned and the request was made while correctly authorized with an Authorization header.

So ConditionalCache keeps each listing’s validators and body, sends the validators back, and on 304 hands the caller the body it already has. The caller cannot tell the two apart except through Fetched::not_modified, which is the point: parsing is unchanged, and an idle poll costs nothing against the hourly quota.

§What was measured before this was relied on

Against api.github.com, with gh api -i, read-only:

  • GET /repos/{o}/{r}/actions/runs?status=queued|in_progress&per_page=100, GET …/actions/runs/{id}/jobs?filter=latest&per_page=100 and GET /repos/{o}/{r}/actions/runners?per_page=100 each return a weak ETag (W/"…") and Cache-Control: private, max-age=60.
  • For an unchanged listing the ETag is stable. Five listings were asked twelve times, ten seconds apart: an idle repository’s queued and in-progress runs, a queued-runs listing holding two stuck runs, one of those runs’ jobs, and a repository’s runners. All sixty answers were 304, one distinct ETag per listing.
  • A listing that is changing is not: while CI ran, the jobs of an in-progress run changed on 11 of 18 polls ten seconds apart, and the in-progress runs listing on 1 of 18. That is why an active target keeps the longer active interval.
  • A 304 carries the account’s x-ratelimit-* headers, and twenty 304s in a row left x-ratelimit-used unchanged apart from other clients’ traffic on the same account — consistent with the documented exemption.
  • The ETag differs between credentials for the same listing (the response varies on Authorization), so it is cached per client and is never shared between machines. After a token renewal the first poll of each listing may be a full 200; that is one request per listing per eight hours.

§Bounded

A job listing’s URL names a run id, so a busy repository mints new keys all day. Entries unused for IDLE_ENTRY_TTL are dropped, and the map never holds more than MAX_ENTRIES.

Structs§

ConditionalCache
Validators and bodies of the listings one client polls.
Fetched
One response, and whether GitHub said it was unchanged.

Constants§

IDLE_ENTRY_TTL
How long an entry nobody asked for is kept.
MAX_ENTRIES
The most entries kept. A ceiling against a pathological number of runs, not a size anyone should reach: ten repositories with a handful of active runs each use a few dozen.