1
0
Fork 0
mirror of https://github.com/gohugoio/hugo.git synced 2025-03-07 02:23:32 +00:00
hugo/navigation/menu_cache.go
satotake 785a31b5b8
navigation: Cache and copy Menu for sorting
.Site.Menus is mutated when it is sorted for now and this causes concurrency problem ()
In this patch, each related sort function copies Menu before sorting to prevent
race condition.

Pages already have such a sort and cache logic and this patch is identical to it.

Closes 
2021-05-23 10:42:01 +02:00

113 lines
2.2 KiB
Go

// Copyright 2021 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package navigation
import (
"sync"
)
type menuCacheEntry struct {
in []Menu
out Menu
}
func (entry menuCacheEntry) matches(menuList []Menu) bool {
if len(entry.in) != len(menuList) {
return false
}
for i, m := range menuList {
if !menuEqual(m, entry.in[i]) {
return false
}
}
return true
}
func newMenuCache() *menuCache {
return &menuCache{m: make(map[string][]menuCacheEntry)}
}
func (c *menuCache) clear() {
c.Lock()
defer c.Unlock()
c.m = make(map[string][]menuCacheEntry)
}
type menuCache struct {
sync.RWMutex
m map[string][]menuCacheEntry
}
func menuEqual(m1, m2 Menu) bool {
if m1 == nil && m2 == nil {
return true
}
if m1 == nil || m2 == nil {
return false
}
if len(m1) != len(m2) {
return false
}
if len(m1) == 0 {
return true
}
for i := 0; i < len(m1); i++ {
if m1[i] != m2[i] {
return false
}
}
return true
}
func (c *menuCache) get(key string, apply func(m Menu), menuLists ...Menu) (Menu, bool) {
return c.getP(key, func(m *Menu) {
if apply != nil {
apply(*m)
}
}, menuLists...)
}
func (c *menuCache) getP(key string, apply func(m *Menu), menuLists ...Menu) (Menu, bool) {
c.Lock()
defer c.Unlock()
if cached, ok := c.m[key]; ok {
for _, entry := range cached {
if entry.matches(menuLists) {
return entry.out, true
}
}
}
m := menuLists[0]
menuCopy := append(Menu(nil), m...)
if apply != nil {
apply(&menuCopy)
}
entry := menuCacheEntry{in: menuLists, out: menuCopy}
if v, ok := c.m[key]; ok {
c.m[key] = append(v, entry)
} else {
c.m[key] = []menuCacheEntry{entry}
}
return menuCopy, false
}