github.com/letsencrypt/boulder@v0.20251208.0/email/cache.go (about)

     1  package email
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"sync"
     7  
     8  	"github.com/golang/groupcache/lru"
     9  	"github.com/prometheus/client_golang/prometheus"
    10  	"github.com/prometheus/client_golang/prometheus/promauto"
    11  )
    12  
    13  type EmailCache struct {
    14  	sync.Mutex
    15  	cache    *lru.Cache
    16  	requests *prometheus.CounterVec
    17  }
    18  
    19  func NewHashedEmailCache(maxEntries int, stats prometheus.Registerer) *EmailCache {
    20  	requests := promauto.With(stats).NewCounterVec(prometheus.CounterOpts{
    21  		Name: "email_cache_requests",
    22  	}, []string{"status"})
    23  
    24  	return &EmailCache{
    25  		cache:    lru.New(maxEntries),
    26  		requests: requests,
    27  	}
    28  }
    29  
    30  func hashEmail(email string) string {
    31  	sum := sha256.Sum256([]byte(email))
    32  	return hex.EncodeToString(sum[:])
    33  }
    34  
    35  func (c *EmailCache) Seen(email string) bool {
    36  	if c == nil {
    37  		// If the cache is nil we assume it was not configured.
    38  		return false
    39  	}
    40  
    41  	hash := hashEmail(email)
    42  
    43  	c.Lock()
    44  	defer c.Unlock()
    45  
    46  	_, ok := c.cache.Get(hash)
    47  	if !ok {
    48  		c.requests.WithLabelValues("miss").Inc()
    49  		return false
    50  	}
    51  
    52  	c.requests.WithLabelValues("hit").Inc()
    53  	return true
    54  }
    55  
    56  func (c *EmailCache) Remove(email string) {
    57  	if c == nil {
    58  		// If the cache is nil we assume it was not configured.
    59  		return
    60  	}
    61  
    62  	hash := hashEmail(email)
    63  
    64  	c.Lock()
    65  	defer c.Unlock()
    66  
    67  	c.cache.Remove(hash)
    68  }
    69  
    70  // StoreIfAbsent stores the email in the cache if it is not already present, as
    71  // a single atomic operation. It returns true if the email was stored and false
    72  // if it was already in the cache. If the cache is nil, true is always returned.
    73  func (c *EmailCache) StoreIfAbsent(email string) bool {
    74  	if c == nil {
    75  		// If the cache is nil we assume it was not configured.
    76  		return true
    77  	}
    78  
    79  	hash := hashEmail(email)
    80  
    81  	c.Lock()
    82  	defer c.Unlock()
    83  
    84  	_, ok := c.cache.Get(hash)
    85  	if ok {
    86  		c.requests.WithLabelValues("hit").Inc()
    87  		return false
    88  	}
    89  	c.cache.Add(hash, nil)
    90  	c.requests.WithLabelValues("miss").Inc()
    91  	return true
    92  }