mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 15:49:16 -05:00
Dates were kept in each process's rewrite cache, so replicas sharing a database and storage dated independently: one dated a document, upstream changed in that second, and another gave the new document the same date, so a client holding it got 304 and kept the old document. - Date from metadata_dates instead of process memory: a package's date is the second it started serving its current representation, recorded once for every replica - Switch a package to another representation only in a second later than its recorded date, and serve it undated until then - Date nothing in the second the process started, so dates clients hold from before this change, or from the process it replaced, stay older - Serve no date ahead of the local clock, recorded by a replica whose clock is ahead - Date rewrites without the rewrite cache too; the cache keeps only ETags - Add two-replica, pre-fix date and clock skew regression tests
83 lines
2.9 KiB
Go
83 lines
2.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/git-pkgs/proxy/internal/database"
|
|
)
|
|
|
|
// metadataDateAttempts bounds how often metadataDate retries when other
|
|
// replicas keep changing a package's date between its read and its write.
|
|
const metadataDateAttempts = 3
|
|
|
|
// metadataDate returns the Last-Modified to serve a package's metadata with,
|
|
// as rewritten for proxyURL into the representation with the given ETag, or
|
|
// zero for none.
|
|
//
|
|
// The date is the second the package started serving that representation,
|
|
// recorded in the database so that every replica sharing it serves the same
|
|
// date. A switch to another representation is dated only in a second later
|
|
// than the recorded date, and then with that second, so a date is never given
|
|
// to two representations of a package: a client holding one never gets 304
|
|
// for another, whichever replica it asks. Until such a second the
|
|
// representation goes undated, and If-Modified-Since is not evaluated.
|
|
//
|
|
// A package's first date is likewise later than the second this process
|
|
// started, as the process it replaced may have dated the package then without
|
|
// recording it. A date ahead of this process's clock, recorded by a replica
|
|
// whose clock is ahead, is not served until the clock catches up. Without a
|
|
// database, or when it fails, nothing is dated.
|
|
func (p *Proxy) metadataDate(ecosystem, proxyURL, name, etag string) time.Time {
|
|
if p.DB == nil || etag == "" {
|
|
return time.Time{}
|
|
}
|
|
now := p.clock().Truncate(time.Second)
|
|
for range metadataDateAttempts {
|
|
date, settled, err := p.claimMetadataDate(ecosystem, proxyURL, name, etag, now)
|
|
if err != nil {
|
|
p.Logger.Warn("failed to date rewritten metadata", "ecosystem", ecosystem, "name", name, "error", err)
|
|
return time.Time{}
|
|
}
|
|
if settled {
|
|
if date.After(now) {
|
|
return time.Time{}
|
|
}
|
|
return date
|
|
}
|
|
}
|
|
return time.Time{}
|
|
}
|
|
|
|
// claimMetadataDate makes one attempt at recording the date of etag for a
|
|
// package at now. It reports false when another replica changed the date
|
|
// first, and the attempt should be made again.
|
|
func (p *Proxy) claimMetadataDate(ecosystem, proxyURL, name, etag string, now time.Time) (time.Time, bool, error) {
|
|
current, err := p.DB.GetMetadataDate(ecosystem, proxyURL, name)
|
|
if err != nil {
|
|
return time.Time{}, false, err
|
|
}
|
|
next := database.MetadataDate{ETag: etag, LastModified: now}
|
|
switch {
|
|
case current == nil:
|
|
if !now.After(p.started.Truncate(time.Second)) {
|
|
return time.Time{}, true, nil
|
|
}
|
|
inserted, err := p.DB.InsertMetadataDate(ecosystem, proxyURL, name, next)
|
|
return now, inserted, err
|
|
case current.ETag == etag:
|
|
return current.LastModified, true, nil
|
|
case !now.After(current.LastModified):
|
|
return time.Time{}, true, nil
|
|
default:
|
|
swapped, err := p.DB.SwapMetadataDate(ecosystem, proxyURL, name, *current, next)
|
|
return now, swapped, err
|
|
}
|
|
}
|
|
|
|
// clock reads the time metadata is dated by.
|
|
func (p *Proxy) clock() time.Time {
|
|
if p.now == nil {
|
|
return time.Now()
|
|
}
|
|
return p.now()
|
|
}
|