github.com/hernad/nomad@v1.6.112/helper/users/cache.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package users 5 6 import ( 7 "os/user" 8 "sync" 9 "time" 10 11 "github.com/hernad/nomad/lib/lang" 12 "oss.indeed.com/go/libtime" 13 ) 14 15 const ( 16 cacheTTL = 1 * time.Hour 17 failureTTL = 1 * time.Minute 18 ) 19 20 type entry[T any] lang.Pair[T, time.Time] 21 22 func (e *entry[T]) expired(now time.Time, ttl time.Duration) bool { 23 return now.After(e.Second.Add(ttl)) 24 } 25 26 type ( 27 userCache map[string]*entry[*user.User] 28 userFailureCache map[string]*entry[error] 29 ) 30 31 type lookupUserFunc func(string) (*user.User, error) 32 33 type cache struct { 34 clock libtime.Clock 35 lookupUser lookupUserFunc 36 37 lock sync.Mutex 38 users userCache 39 userFailures userFailureCache 40 } 41 42 func newCache() *cache { 43 return &cache{ 44 clock: libtime.SystemClock(), 45 lookupUser: internalLookupUser, 46 users: make(userCache), 47 userFailures: make(userFailureCache), 48 } 49 } 50 51 func (c *cache) GetUser(username string) (*user.User, error) { 52 c.lock.Lock() 53 defer c.lock.Unlock() 54 55 // record this moment as "now" for further cache operations 56 now := c.clock.Now() 57 58 // first check if the user is in the cache and the entry we have 59 // is not yet expired 60 usr, exists := c.users[username] 61 if exists && !usr.expired(now, cacheTTL) { 62 return usr.First, nil 63 } 64 65 // next check if there was a recent failure already, so we 66 // avoid spamming the OS with dead user lookups 67 failure, exists2 := c.userFailures[username] 68 if exists2 { 69 if !failure.expired(now, failureTTL) { 70 return nil, failure.First 71 } 72 // may as well cleanup expired case 73 delete(c.userFailures, username) 74 } 75 76 // need to perform an OS lookup 77 u, err := c.lookupUser(username) 78 79 // lookup was a failure, populate the failure cache 80 if err != nil { 81 c.userFailures[username] = &entry[error]{err, now} 82 return nil, err 83 } 84 85 // lookup was a success, populate the user cache 86 c.users[username] = &entry[*user.User]{u, now} 87 return u, nil 88 }