Accept Retry-After header on discovery lookup failures

This commit is contained in:
Jakob Borg 2015-12-01 09:57:53 +01:00
parent 3008dc76d3
commit 9f2dc4554d
3 changed files with 39 additions and 11 deletions

View File

@ -44,6 +44,13 @@ type prioritizedAddress struct {
addr string addr string
} }
// An error may implement cachedError, in which case it will be interrogated
// to see how long we should cache the error. This overrides the default
// negative cache time.
type cachedError interface {
CacheFor() time.Duration
}
func NewCachingMux() *CachingMux { func NewCachingMux() *CachingMux {
return &CachingMux{ return &CachingMux{
Supervisor: suture.NewSimple("discover.cachingMux"), Supervisor: suture.NewSimple("discover.cachingMux"),
@ -84,10 +91,11 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
continue continue
} }
if !cacheEntry.found && time.Since(cacheEntry.when) < finder.negCacheTime { valid := time.Now().Before(cacheEntry.validUntil) || time.Since(cacheEntry.when) < finder.negCacheTime
if !cacheEntry.found && valid {
// It's a negative, valid entry. We should not make another // It's a negative, valid entry. We should not make another
// attempt right now. // attempt right now.
l.Debugln("negative cache entry for", deviceID, "at", finder) l.Debugln("negative cache entry for", deviceID, "at", finder, "valid until", cacheEntry.when.Add(finder.negCacheTime), "or", cacheEntry.validUntil)
continue continue
} }
@ -111,10 +119,14 @@ func (m *CachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, relays
}) })
} else { } else {
// Lookup returned error, add a negative cache entry. // Lookup returned error, add a negative cache entry.
m.caches[i].Set(deviceID, CacheEntry{ entry := CacheEntry{
when: time.Now(), when: time.Now(),
found: false, found: false,
}) }
if err, ok := err.(cachedError); ok {
entry.validUntil = time.Now().Add(err.CacheFor())
}
m.caches[i].Set(deviceID, entry)
} }
} }
m.mut.Unlock() m.mut.Unlock()

View File

@ -22,10 +22,11 @@ type Finder interface {
} }
type CacheEntry struct { type CacheEntry struct {
Direct []string `json:"direct"` Direct []string `json:"direct"`
Relays []Relay `json:"relays"` Relays []Relay `json:"relays"`
when time.Time // When did we get the result when time.Time // When did we get the result
found bool // Is it a success (cacheTime applies) or a failure (negCacheTime applies)? found bool // Is it a success (cacheTime applies) or a failure (negCacheTime applies)?
validUntil time.Time // Validity time, overrides normal calculation
} }
// A FinderService is a Finder that has background activity and must be run as // A FinderService is a Finder that has background activity and must be run as

View File

@ -56,6 +56,16 @@ type serverOptions struct {
id string // expected server device ID id string // expected server device ID
} }
// A lookupError is any other error but with a cache validity time attached.
type lookupError struct {
error
cacheFor time.Duration
}
func (e lookupError) CacheFor() time.Duration {
return e.cacheFor
}
func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, relayStat RelayStatusProvider) (FinderService, error) { func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, relayStat RelayStatusProvider) (FinderService, error) {
server, opts, err := parseOptions(server) server, opts, err := parseOptions(server)
if err != nil { if err != nil {
@ -138,11 +148,16 @@ func (c *globalClient) Lookup(device protocol.DeviceID) (direct []string, relays
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
resp.Body.Close() resp.Body.Close()
l.Debugln("globalClient.Lookup", qURL, resp.Status) l.Debugln("globalClient.Lookup", qURL, resp.Status)
return nil, nil, errors.New(resp.Status) err := errors.New(resp.Status)
if secs, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && secs > 0 {
err = lookupError{
error: err,
cacheFor: time.Duration(secs) * time.Second,
}
}
return nil, nil, err
} }
// TODO: Handle 429 and Retry-After?
var ann announcement var ann announcement
err = json.NewDecoder(resp.Body).Decode(&ann) err = json.NewDecoder(resp.Body).Decode(&ann)
resp.Body.Close() resp.Body.Close()