github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/hugolib/pageCache.go (about) 1 // Copyright 2015 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 hugolib 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 !fastEqualPages(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 // get/getP gets a Pages slice from the cache matching the given key and 48 // all the provided Pages slices. 49 // If none found in cache, a copy of the first slice is created. 50 // 51 // If an apply func is provided, that func is applied to the newly created copy. 52 // 53 // The getP variant' apply func takes a pointer to Pages. 54 // 55 // The cache and the execution of the apply func is protected by a RWMutex. 56 func (c *pageCache) get(key string, apply func(p Pages), pageLists ...Pages) (Pages, bool) { 57 return c.getP(key, func(p *Pages) { 58 if apply != nil { 59 apply(*p) 60 } 61 }, pageLists...) 62 } 63 64 func (c *pageCache) getP(key string, apply func(p *Pages), pageLists ...Pages) (Pages, bool) { 65 c.RLock() 66 if cached, ok := c.m[key]; ok { 67 for _, entry := range cached { 68 if entry.matches(pageLists) { 69 c.RUnlock() 70 return entry.out, true 71 } 72 } 73 } 74 c.RUnlock() 75 76 c.Lock() 77 defer c.Unlock() 78 79 // double-check 80 if cached, ok := c.m[key]; ok { 81 for _, entry := range cached { 82 if entry.matches(pageLists) { 83 return entry.out, true 84 } 85 } 86 } 87 88 p := pageLists[0] 89 pagesCopy := append(Pages(nil), p...) 90 91 if apply != nil { 92 apply(&pagesCopy) 93 } 94 95 entry := pageCacheEntry{in: pageLists, out: pagesCopy} 96 if v, ok := c.m[key]; ok { 97 c.m[key] = append(v, entry) 98 } else { 99 c.m[key] = []pageCacheEntry{entry} 100 } 101 102 return pagesCopy, false 103 104 } 105 106 // "fast" as in: we do not compare every element for big slices, but that is 107 // good enough for our use cases. 108 // TODO(bep) there is a similar method in pagination.go. DRY. 109 func fastEqualPages(p1, p2 Pages) bool { 110 if p1 == nil && p2 == nil { 111 return true 112 } 113 114 if p1 == nil || p2 == nil { 115 return false 116 } 117 118 if p1.Len() != p2.Len() { 119 return false 120 } 121 122 if p1.Len() == 0 { 123 return true 124 } 125 126 step := 1 127 128 if len(p1) >= 50 { 129 step = len(p1) / 10 130 } 131 132 for i := 0; i < len(p1); i += step { 133 if p1[i] != p2[i] { 134 return false 135 } 136 } 137 return true 138 }