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 }