mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 15:49:16 -05:00
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()
|
||
|
|
}
|