make Close() calls idempotent
Previously, few of them resulted in panics when called more than once.
This commit is contained in:
committed by
Umputun
parent
067a8bcb21
commit
d7e9be99f9
+5
-5
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,5 @@
|
||||
Copyright (c) 2014 HashiCorp, Inc.
|
||||
|
||||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user