make Close() calls idempotent

Previously, few of them resulted in panics when called more than once.
This commit is contained in:
Dmitry Verkhoturov
2023-01-03 01:41:26 -06:00
committed by Umputun
parent 067a8bcb21
commit d7e9be99f9
34 changed files with 298 additions and 97 deletions
+5 -5
View File
@@ -29,17 +29,13 @@ linters:
- revive
- govet
- unconvert
- megacheck
- structcheck
- unused
- gas
- gocyclo
- misspell
- unparam
- varcheck
- deadcode
- typecheck
- ineffassign
- varcheck
- stylecheck
- gochecknoinits
- exportloopref
@@ -67,5 +63,9 @@ issues:
- text: "Use of weak cryptographic primitive"
linters:
- gosec
- path: _test\.go
text: "Potential Slowloris Attack because ReadHeaderTimeout is not configured in the http.Server"
linters:
- gosec
exclude-use-default: false
+4 -4
View File
@@ -153,10 +153,6 @@ Such provider acts like any other, i.e. will be registered as `/auth/local/login
The API for this provider supports both GET and POST requests:
* GET request with user credentials provided as query params:
```
GET /auth/<name>/login?user=<user>&passwd=<password>&aud=<site_id>&session=[1|0]
```
* POST request could be encoded as application/x-www-form-urlencoded or application/json:
```
POST /auth/<name>/login?session=[1|0]
@@ -172,6 +168,10 @@ The API for this provider supports both GET and POST requests:
"aud": "bar",
}
```
* GET request with user credentials provided as query params, but be aware that [the https query string is not secure](https://stackoverflow.com/a/323286/633961):
```
GET /auth/<name>/login?user=<user>&passwd=<password>&aud=<site_id>&session=[1|0]
```
_note: password parameter doesn't have to be naked/real password and can be any kind of password hash prepared by caller._
+1 -2
View File
@@ -59,7 +59,6 @@ func (gf *GridFS) Get(avatar string) (reader io.ReadCloser, size int, err error)
return io.NopCloser(buf), int(sz), nil
}
//
// ID returns a fingerprint of the avatar content. Uses MD5 because gridfs provides it directly
func (gf *GridFS) ID(avatar string) (id string) {
@@ -143,7 +142,7 @@ func (gf *GridFS) List() (ids []string, err error) {
return ids, nil
}
// Close gridfs does nothing but satisfies interface
// Close gridfs store
func (gf *GridFS) Close() error {
ctx, cancel := context.WithTimeout(context.Background(), gf.timeout)
defer cancel()
+1 -1
View File
@@ -104,7 +104,7 @@ func (fs *LocalFS) List() (ids []string, err error) {
return ids, nil
}
// Close gridfs does nothing but satisfies interface
// Close LocalFS does nothing but satisfies interface
func (fs *LocalFS) Close() error {
return nil
}
+2 -1
View File
@@ -88,7 +88,8 @@ func (c *CustomServer) Run(ctx context.Context) {
}
c.httpServer = &http.Server{
Addr: fmt.Sprintf(":%s", port),
Addr: fmt.Sprintf(":%s", port),
ReadHeaderTimeout: 5 * time.Second,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.HasSuffix(r.URL.Path, "/authorize"):
+2 -1
View File
@@ -57,7 +57,8 @@ func (d *DevAuthServer) Run(ctx context.Context) { // nolint (gocyclo)
}
d.httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", d.Provider.Port),
Addr: fmt.Sprintf(":%d", d.Provider.Port),
ReadHeaderTimeout: 5 * time.Second,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
d.Logf("[DEBUG] dev oauth request %s %s %+v", r.Method, r.URL, r.Header)
switch {
+1 -1
View File
@@ -177,7 +177,7 @@ func (em *Sender) client() (c *smtp.Client, err error) {
}
if em.tls {
conn, e := tls.Dial("tcp", srvAddress, tlsConf)
conn, e := tls.DialWithDialer(&net.Dialer{Timeout: em.timeOut}, "tcp", srvAddress, tlsConf)
if e != nil {
return nil, fmt.Errorf("failed to dial smtp tls to %s: %w", srvAddress, e)
}
+1 -5
View File
@@ -26,18 +26,14 @@ linters:
- revive
- govet
- unconvert
- megacheck
- structcheck
- gas
- gocyclo
- dupl
- misspell
- unparam
- varcheck
- deadcode
- unused
- typecheck
- ineffassign
- varcheck
- stylecheck
- gochecknoinits
- exportloopref
+23 -16
View File
@@ -30,22 +30,30 @@ Main features:
## Usage
```go
cache, err := lcw.NewLruCache(lcw.MaxKeys(500), lcw.MaxCacheSize(65536), lcw.MaxValSize(200), lcw.MaxKeySize(32))
if err != nil {
panic("failed to create cache")
package main
import (
"github.com/go-pkgz/lcw"
)
func main() {
cache, err := lcw.NewLruCache(lcw.MaxKeys(500), lcw.MaxCacheSize(65536), lcw.MaxValSize(200), lcw.MaxKeySize(32))
if err != nil {
panic("failed to create cache")
}
defer cache.Close()
val, err := cache.Get("key123", func() (interface{}, error) {
res, err := getDataFromSomeSource(params) // returns string
return res, err
})
if err != nil {
panic("failed to get data")
}
s := val.(string) // cached value
}
defer cache.Close()
val, err := cache.Get("key123", func() (lcw.Value, error) {
res, err := getDataFromSomeSource(params) // returns string
return res, err
})
if err != nil {
panic("failed to get data")
}
s := val.(string) // cached value
```
### Cache with URI
@@ -73,7 +81,6 @@ Cache can be created with URIs:
that mutable values can be changed outside of cache. `ExampleLoadingCache_Mutability` illustrates that.
- All byte-size limits (MaxCacheSize and MaxValSize) only work for values implementing `lcw.Sizer` interface.
- Negative limits (max options) rejected
- `lgr.Value` wraps `interface{}` and should be converted back to the concrete type.
- The implementation started as a part of [remark42](https://github.com/umputun/remark)
and later on moved to [go-pkgz/rest](https://github.com/go-pkgz/rest/tree/master/cache)
library and finally generalized to become `lcw`.
-2
View File
@@ -6,8 +6,6 @@
// 3 flavors of cache provided - NoP (do-nothing cache), ExpirableCache (TTL based), and LruCache
package lcw
//go:generate sh -c "mockery -inpkg -name LoadingCache -print > /tmp/cache-mock.tmp && mv /tmp/cache-mock.tmp cache_mock.go"
import (
"fmt"
)
+6
View File
@@ -185,6 +185,12 @@ func (c *LoadingCache) ItemCount() int {
func (c *LoadingCache) Close() {
c.mu.Lock()
defer c.mu.Unlock()
// don't panic in case service is already closed
select {
case <-c.done:
return
default:
}
close(c.done)
}
+30
View File
@@ -0,0 +1,30 @@
linters:
enable:
- megacheck
- revive
- govet
- unconvert
- megacheck
- gas
- gocyclo
- dupl
- misspell
- unparam
- unused
- typecheck
- ineffassign
- stylecheck
- exportloopref
- gocritic
- nakedret
- gosimple
- prealloc
fast: false
disable-all: true
issues:
exclude-rules:
- path: _test\.go
linters:
- dupl
exclude-use-default: false
+1 -2
View File
@@ -44,7 +44,7 @@ func New2Q(size int) (*TwoQueueCache, error) {
// New2QParams creates a new TwoQueueCache using the provided
// parameter values.
func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) {
func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, error) {
if size <= 0 {
return nil, fmt.Errorf("invalid size")
}
@@ -138,7 +138,6 @@ func (c *TwoQueueCache) Add(key, value interface{}) {
// Add to the recently seen list
c.ensureSpace(false)
c.recent.Add(key, value)
return
}
// ensureSpace is used to ensure we have space in the cache
+2
View File
@@ -1,3 +1,5 @@
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
1. Definitions
+1 -1
View File
@@ -7,7 +7,7 @@ thread safe LRU cache. It is based on the cache in Groupcache.
Documentation
=============
Full docs are available on [Godoc](http://godoc.org/github.com/hashicorp/golang-lru)
Full docs are available on [Godoc](https://pkg.go.dev/github.com/hashicorp/golang-lru)
Example
=======
-1
View File
@@ -173,7 +173,6 @@ func (c *ARCCache) Add(key, value interface{}) {
// Add to the recently seen list
c.t1.Add(key, value)
return
}
// replace is used to adaptively evict from either T1 or T2
+100 -19
View File
@@ -6,10 +6,17 @@ import (
"github.com/hashicorp/golang-lru/simplelru"
)
const (
// DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
DefaultEvictedBufferSize = 16
)
// Cache is a thread-safe fixed size LRU cache.
type Cache struct {
lru simplelru.LRUCache
lock sync.RWMutex
lru *simplelru.LRU
evictedKeys, evictedVals []interface{}
onEvictedCB func(k, v interface{})
lock sync.RWMutex
}
// New creates an LRU of the given size.
@@ -19,30 +26,63 @@ func New(size int) (*Cache, error) {
// NewWithEvict constructs a fixed size cache with the given eviction
// callback.
func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted))
if err != nil {
return nil, err
func NewWithEvict(size int, onEvicted func(key, value interface{})) (c *Cache, err error) {
// create a cache with default settings
c = &Cache{
onEvictedCB: onEvicted,
}
c := &Cache{
lru: lru,
if onEvicted != nil {
c.initEvictBuffers()
onEvicted = c.onEvicted
}
return c, nil
c.lru, err = simplelru.NewLRU(size, onEvicted)
return
}
func (c *Cache) initEvictBuffers() {
c.evictedKeys = make([]interface{}, 0, DefaultEvictedBufferSize)
c.evictedVals = make([]interface{}, 0, DefaultEvictedBufferSize)
}
// onEvicted save evicted key/val and sent in externally registered callback
// outside of critical section
func (c *Cache) onEvicted(k, v interface{}) {
c.evictedKeys = append(c.evictedKeys, k)
c.evictedVals = append(c.evictedVals, v)
}
// Purge is used to completely clear the cache.
func (c *Cache) Purge() {
var ks, vs []interface{}
c.lock.Lock()
c.lru.Purge()
if c.onEvictedCB != nil && len(c.evictedKeys) > 0 {
ks, vs = c.evictedKeys, c.evictedVals
c.initEvictBuffers()
}
c.lock.Unlock()
// invoke callback outside of critical section
if c.onEvictedCB != nil {
for i := 0; i < len(ks); i++ {
c.onEvictedCB(ks[i], vs[i])
}
}
}
// Add adds a value to the cache. Returns true if an eviction occurred.
func (c *Cache) Add(key, value interface{}) (evicted bool) {
var k, v interface{}
c.lock.Lock()
evicted = c.lru.Add(key, value)
if c.onEvictedCB != nil && evicted {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock()
return evicted
if c.onEvictedCB != nil && evicted {
c.onEvictedCB(k, v)
}
return
}
// Get looks up a key's value from the cache.
@@ -75,13 +115,21 @@ func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
// recent-ness or deleting it for being stale, and if not, adds the value.
// Returns whether found and whether an eviction occurred.
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) {
var k, v interface{}
c.lock.Lock()
defer c.lock.Unlock()
if c.lru.Contains(key) {
c.lock.Unlock()
return true, false
}
evicted = c.lru.Add(key, value)
if c.onEvictedCB != nil && evicted {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock()
if c.onEvictedCB != nil && evicted {
c.onEvictedCB(k, v)
}
return false, evicted
}
@@ -89,47 +137,80 @@ func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) {
// recent-ness or deleting it for being stale, and if not, adds the value.
// Returns whether found and whether an eviction occurred.
func (c *Cache) PeekOrAdd(key, value interface{}) (previous interface{}, ok, evicted bool) {
var k, v interface{}
c.lock.Lock()
defer c.lock.Unlock()
previous, ok = c.lru.Peek(key)
if ok {
c.lock.Unlock()
return previous, true, false
}
evicted = c.lru.Add(key, value)
if c.onEvictedCB != nil && evicted {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock()
if c.onEvictedCB != nil && evicted {
c.onEvictedCB(k, v)
}
return nil, false, evicted
}
// Remove removes the provided key from the cache.
func (c *Cache) Remove(key interface{}) (present bool) {
var k, v interface{}
c.lock.Lock()
present = c.lru.Remove(key)
if c.onEvictedCB != nil && present {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock()
if c.onEvictedCB != nil && present {
c.onEvictedCB(k, v)
}
return
}
// Resize changes the cache size.
func (c *Cache) Resize(size int) (evicted int) {
var ks, vs []interface{}
c.lock.Lock()
evicted = c.lru.Resize(size)
if c.onEvictedCB != nil && evicted > 0 {
ks, vs = c.evictedKeys, c.evictedVals
c.initEvictBuffers()
}
c.lock.Unlock()
if c.onEvictedCB != nil && evicted > 0 {
for i := 0; i < len(ks); i++ {
c.onEvictedCB(ks[i], vs[i])
}
}
return evicted
}
// RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() (key interface{}, value interface{}, ok bool) {
func (c *Cache) RemoveOldest() (key, value interface{}, ok bool) {
var k, v interface{}
c.lock.Lock()
key, value, ok = c.lru.RemoveOldest()
if c.onEvictedCB != nil && ok {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock()
if c.onEvictedCB != nil && ok {
c.onEvictedCB(k, v)
}
return
}
// GetOldest returns the oldest entry
func (c *Cache) GetOldest() (key interface{}, value interface{}, ok bool) {
c.lock.Lock()
func (c *Cache) GetOldest() (key, value interface{}, ok bool) {
c.lock.RLock()
key, value, ok = c.lru.GetOldest()
c.lock.Unlock()
c.lock.RUnlock()
return
}
+3 -3
View File
@@ -25,7 +25,7 @@ type entry struct {
// NewLRU constructs an LRU of the given size
func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
if size <= 0 {
return nil, errors.New("Must provide a positive size")
return nil, errors.New("must provide a positive size")
}
c := &LRU{
size: size,
@@ -109,7 +109,7 @@ func (c *LRU) Remove(key interface{}) (present bool) {
}
// RemoveOldest removes the oldest item from the cache.
func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) {
func (c *LRU) RemoveOldest() (key, value interface{}, ok bool) {
ent := c.evictList.Back()
if ent != nil {
c.removeElement(ent)
@@ -120,7 +120,7 @@ func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) {
}
// GetOldest returns the oldest entry
func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) {
func (c *LRU) GetOldest() (key, value interface{}, ok bool) {
ent := c.evictList.Back()
if ent != nil {
kv := ent.Value.(*entry)
+3 -2
View File
@@ -1,3 +1,4 @@
// Package simplelru provides simple LRU implementation based on build-in container/list.
package simplelru
// LRUCache is the interface for simple LRU cache.
@@ -34,6 +35,6 @@ type LRUCache interface {
// Clears all cache entries.
Purge()
// Resizes cache, returning number evicted
Resize(int) int
// Resizes cache, returning number evicted
Resize(int) int
}
+16
View File
@@ -0,0 +1,16 @@
package lru
import (
"crypto/rand"
"math"
"math/big"
"testing"
)
func getRand(tb testing.TB) int64 {
out, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
tb.Fatal(err)
}
return out.Int64()
}