ent.refreshLock.Unlock()
}
}
- expireCache := func() {
- for _, ent := range s.handler.cache {
- ent.refreshAfter = time.Now()
- }
- }
waitPendingUpdates := func() {
for _, ent := range s.handler.cache {
ent.refreshLock.Lock()
defer ent.mtx.Unlock()
}
}
+ refreshNow := func() {
+ waitPendingUpdates()
+ for _, ent := range s.handler.cache {
+ ent.refreshAfter = time.Now()
+ }
+ }
+ expireNow := func() {
+ waitPendingUpdates()
+ for _, ent := range s.handler.cache {
+ ent.expireAfter = time.Now()
+ }
+ }
// Easy path: first req fetches, subsequent reqs use cache.
c.Check(countRailsReqs(), check.Equals, 0)
// Race after expiry: concurrent reqs return the cached data
// but initiate a new fetch in the background.
- expireCache()
+ refreshNow()
holdReqs = make(chan struct{})
wg = getDDConcurrently(5, http.StatusOK, check.Commentf("race after expiry"))
reqsBefore = countRailsReqs()
// make an upstream attempt for each incoming request because
// we have nothing better to return
clearCache()
- wantError = true
+ wantError, wantBadContent = true, false
reqsBefore = countRailsReqs()
holdReqs = make(chan struct{})
wg = getDDConcurrently(5, http.StatusBadGateway, check.Commentf("error at startup"))
c.Check(countRailsReqs(), check.Equals, reqsBefore+5)
// Response status is OK but body is not a discovery document
- wantError = false
- wantBadContent = true
+ wantError, wantBadContent = false, true
reqsBefore = countRailsReqs()
c.Check(getDD(), check.Equals, http.StatusBadGateway)
c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
// Error condition clears => caller gets OK, cache is warmed
// up
- wantBadContent = false
+ wantError, wantBadContent = false, false
reqsBefore = countRailsReqs()
getDDConcurrently(5, http.StatusOK, check.Commentf("success after errors at startup")).Wait()
c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
// Error with warm cache => caller gets OK (with no attempt to
// re-fetch)
- wantError = true
+ wantError, wantBadContent = true, false
reqsBefore = countRailsReqs()
getDDConcurrently(5, http.StatusOK, check.Commentf("error with warm cache")).Wait()
c.Check(countRailsReqs(), check.Equals, reqsBefore)
- expireCache()
- // Error with expired cache => caller gets OK with stale data
+ // Error with stale cache => caller gets OK with stale data
// while the re-fetch is attempted in the background
+ refreshNow()
+ wantError, wantBadContent = true, false
reqsBefore = countRailsReqs()
holdReqs = make(chan struct{})
- getDDConcurrently(5, http.StatusOK, check.Commentf("error with expired cache")).Wait()
+ getDDConcurrently(5, http.StatusOK, check.Commentf("error with stale cache")).Wait()
close(holdReqs)
// Only one attempt to re-fetch (holdReqs ensured the first
// update took long enough for the last incoming request to
// arrive)
c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
- waitPendingUpdates()
- expireCache()
- wantError = false
+ refreshNow()
+ wantError, wantBadContent = false, false
reqsBefore = countRailsReqs()
holdReqs = make(chan struct{})
getDDConcurrently(5, http.StatusOK, check.Commentf("refresh cache after error condition clears")).Wait()
close(holdReqs)
+ waitPendingUpdates()
+ c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
+
+ // Make sure expireAfter is getting set
+ waitPendingUpdates()
+ exp := s.handler.cache["/discovery/v1/apis/arvados/v1/rest"].expireAfter.Sub(time.Now())
+ c.Check(exp > cacheTTL, check.Equals, true)
+ c.Check(exp < cacheExpire, check.Equals, true)
+
+ // After the cache *expires* it behaves as if uninitialized:
+ // each incoming request does a new upstream request until one
+ // succeeds.
+ //
+ // First check failure after expiry:
+ expireNow()
+ wantError, wantBadContent = true, false
+ reqsBefore = countRailsReqs()
+ holdReqs = make(chan struct{})
+ wg = getDDConcurrently(5, http.StatusBadGateway, check.Commentf("error after expiry"))
+ close(holdReqs)
+ wg.Wait()
+ c.Check(countRailsReqs(), check.Equals, reqsBefore+5)
+
+ // Success after expiry:
+ wantError, wantBadContent = false, false
+ reqsBefore = countRailsReqs()
+ holdReqs = make(chan struct{})
+ wg = getDDConcurrently(5, http.StatusOK, check.Commentf("success after expiry"))
+ close(holdReqs)
+ wg.Wait()
c.Check(countRailsReqs(), check.Equals, reqsBefore+1)
}
testCases := map[string]map[string]bool{
"api_clients/" + arvadostest.TrustedWorkbenchAPIClientUUID: nil,
"api_client_authorizations/" + auth.UUID: {"href": true, "modified_by_client_uuid": true, "modified_by_user_uuid": true},
- "authorized_keys/" + arvadostest.AdminAuthorizedKeysUUID: nil,
+ "authorized_keys/" + arvadostest.AdminAuthorizedKeysUUID: {"href": true},
"collections/" + arvadostest.CollectionWithUniqueWordsUUID: {"href": true},
"containers/" + arvadostest.RunningContainerUUID: nil,
"container_requests/" + arvadostest.QueuedContainerRequestUUID: nil,