pages_cache.go (2887B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package page
15
16 import (
17 "sync"
18 )
19
20 type pageCacheEntry struct {
21 in []Pages
22 out Pages
23 }
24
25 func (entry pageCacheEntry) matches(pageLists []Pages) bool {
26 if len(entry.in) != len(pageLists) {
27 return false
28 }
29 for i, p := range pageLists {
30 if !pagesEqual(p, entry.in[i]) {
31 return false
32 }
33 }
34
35 return true
36 }
37
38 type pageCache struct {
39 sync.RWMutex
40 m map[string][]pageCacheEntry
41 }
42
43 func newPageCache() *pageCache {
44 return &pageCache{m: make(map[string][]pageCacheEntry)}
45 }
46
47 func (c *pageCache) clear() {
48 c.Lock()
49 defer c.Unlock()
50 c.m = make(map[string][]pageCacheEntry)
51 }
52
53 // get/getP gets a Pages slice from the cache matching the given key and
54 // all the provided Pages slices.
55 // If none found in cache, a copy of the first slice is created.
56 //
57 // If an apply func is provided, that func is applied to the newly created copy.
58 //
59 // The getP variant' apply func takes a pointer to Pages.
60 //
61 // The cache and the execution of the apply func is protected by a RWMutex.
62 func (c *pageCache) get(key string, apply func(p Pages), pageLists ...Pages) (Pages, bool) {
63 return c.getP(key, func(p *Pages) {
64 if apply != nil {
65 apply(*p)
66 }
67 }, pageLists...)
68 }
69
70 func (c *pageCache) getP(key string, apply func(p *Pages), pageLists ...Pages) (Pages, bool) {
71 c.RLock()
72 if cached, ok := c.m[key]; ok {
73 for _, entry := range cached {
74 if entry.matches(pageLists) {
75 c.RUnlock()
76 return entry.out, true
77 }
78 }
79 }
80 c.RUnlock()
81
82 c.Lock()
83 defer c.Unlock()
84
85 // double-check
86 if cached, ok := c.m[key]; ok {
87 for _, entry := range cached {
88 if entry.matches(pageLists) {
89 return entry.out, true
90 }
91 }
92 }
93
94 p := pageLists[0]
95 pagesCopy := append(Pages(nil), p...)
96
97 if apply != nil {
98 apply(&pagesCopy)
99 }
100
101 entry := pageCacheEntry{in: pageLists, out: pagesCopy}
102 if v, ok := c.m[key]; ok {
103 c.m[key] = append(v, entry)
104 } else {
105 c.m[key] = []pageCacheEntry{entry}
106 }
107
108 return pagesCopy, false
109 }
110
111 // pagesEqual returns whether p1 and p2 are equal.
112 func pagesEqual(p1, p2 Pages) bool {
113 if p1 == nil && p2 == nil {
114 return true
115 }
116
117 if p1 == nil || p2 == nil {
118 return false
119 }
120
121 if p1.Len() != p2.Len() {
122 return false
123 }
124
125 if p1.Len() == 0 {
126 return true
127 }
128
129 for i := 0; i < len(p1); i++ {
130 if p1[i] != p2[i] {
131 return false
132 }
133 }
134 return true
135 }