File: cache.go

package info (click to toggle)
golang-github-antchfx-xpath 1.3.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 284 kB
  • sloc: makefile: 2
file content (80 lines) | stat: -rw-r--r-- 2,406 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package xpath

import (
	"regexp"
	"sync"
)

type loadFunc func(key interface{}) (interface{}, error)

const (
	defaultCap = 65536
)

// The reason we're building a simple capacity-resetting loading cache (when capacity reached) instead of using
// something like github.com/hashicorp/golang-lru is primarily due to (not wanting to create) external dependency.
// Currently this library has 0 external dep (other than go sdk), and supports go 1.6, 1.9, and 1.10 (and later).
// Creating external lib dependencies (plus their transitive dependencies) would make things hard if not impossible.
// We expect under most circumstances, the defaultCap is big enough for any long running services that use this
// library if their xpath regexp cardinality is low. However, in extreme cases when the capacity is reached, we
// simply reset the cache, taking a small subsequent perf hit (next to nothing considering amortization) in trade
// of more complex and less performant LRU type of construct.
type loadingCache struct {
	sync.RWMutex
	cap   int
	load  loadFunc
	m     map[interface{}]interface{}
	reset int
}

// NewLoadingCache creates a new instance of a loading cache with capacity. Capacity must be >= 0, or
// it will panic. Capacity == 0 means the cache growth is unbounded.
func NewLoadingCache(load loadFunc, capacity int) *loadingCache {
	if capacity < 0 {
		panic("capacity must be >= 0")
	}
	return &loadingCache{cap: capacity, load: load, m: make(map[interface{}]interface{})}
}

func (c *loadingCache) get(key interface{}) (interface{}, error) {
	c.RLock()
	v, found := c.m[key]
	c.RUnlock()
	if found {
		return v, nil
	}
	v, err := c.load(key)
	if err != nil {
		return nil, err
	}
	c.Lock()
	if c.cap > 0 && len(c.m) >= c.cap {
		c.m = map[interface{}]interface{}{key: v}
		c.reset++
	} else {
		c.m[key] = v
	}
	c.Unlock()
	return v, nil
}

var (
	// RegexpCache is a loading cache for string -> *regexp.Regexp mapping. It is exported so that in rare cases
	// client can customize load func and/or capacity.
	RegexpCache = defaultRegexpCache()
)

func defaultRegexpCache() *loadingCache {
	return NewLoadingCache(
		func(key interface{}) (interface{}, error) {
			return regexp.Compile(key.(string))
		}, defaultCap)
}

func getRegexp(pattern string) (*regexp.Regexp, error) {
	exp, err := RegexpCache.get(pattern)
	if err != nil {
		return nil, err
	}
	return exp.(*regexp.Regexp), nil
}