Replace the concurrent map with an identical upstream version

This commit is contained in:
Bjørn Erik Pedersen
2026-04-19 22:14:14 +02:00
parent 927666005c
commit 7574e35b40
6 changed files with 16 additions and 216 deletions
-129
View File
@@ -1,129 +0,0 @@
// Copyright 2026 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 hmaps
import (
"iter"
"sync"
)
func NewMap[K comparable, T any]() *Map[K, T] {
return &Map[K, T]{
m: make(map[K]T),
}
}
// Map is a thread safe map backed by a Go map.
type Map[K comparable, T any] struct {
m map[K]T
mu sync.RWMutex
}
// Get gets the value for the given key.
// It returns the zero value of T if the key is not found.
func (m *Map[K, T]) Get(key K) T {
v, _ := m.Lookup(key)
return v
}
// Lookup looks up the given key in the map.
// It returns the value and a boolean indicating whether the key was found.
func (m *Map[K, T]) Lookup(key K) (T, bool) {
m.mu.RLock()
v, found := m.m[key]
m.mu.RUnlock()
return v, found
}
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
func (m *Map[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
v, found := m.Lookup(key)
if found {
return v, nil
}
m.mu.Lock()
defer m.mu.Unlock()
v, found = m.m[key]
if found {
return v, nil
}
v, err := create()
if err != nil {
return v, err
}
m.m[key] = v
return v, nil
}
// Set sets the given key to the given value.
func (m *Map[K, T]) Set(key K, value T) {
m.mu.Lock()
m.m[key] = value
m.mu.Unlock()
}
// Delete deletes the given key from the map.
// It returns true if the key was found and deleted, false otherwise.
func (m *Map[K, T]) Delete(key K) bool {
m.mu.Lock()
defer m.mu.Unlock()
if _, found := m.m[key]; found {
delete(m.m, key)
return true
}
return false
}
// WithWriteLock executes the given function with a write lock on the map.
func (m *Map[K, T]) WithWriteLock(f func(m map[K]T) error) error {
m.mu.Lock()
defer m.mu.Unlock()
return f(m.m)
}
// SetIfAbsent sets the given key to the given value if the key does not already exist in the map.
// It returns true if the value was set, false otherwise.
func (m *Map[K, T]) SetIfAbsent(key K, value T) bool {
m.mu.RLock()
if _, found := m.m[key]; !found {
m.mu.RUnlock()
return m.doSetIfAbsent(key, value)
}
m.mu.RUnlock()
return false
}
func (m *Map[K, T]) doSetIfAbsent(key K, value T) bool {
m.mu.Lock()
defer m.mu.Unlock()
if _, found := m.m[key]; !found {
m.m[key] = value
return true
}
return false
}
// All returns an iterator over all key/value pairs in the map.
// A read lock is held during the iteration.
func (m *Map[K, T]) All() iter.Seq2[K, T] {
return func(yield func(K, T) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.m {
if !yield(k, v) {
return
}
}
}
}
-72
View File
@@ -1,72 +0,0 @@
// Copyright 2026 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 hmaps
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestMap(t *testing.T) {
c := qt.New(t)
m := NewMap[string, int]()
m.Set("b", 42)
v, found := m.Lookup("b")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 42)
v = m.Get("b")
c.Assert(v, qt.Equals, 42)
v, found = m.Lookup("c")
c.Assert(found, qt.Equals, false)
c.Assert(v, qt.Equals, 0)
v = m.Get("c")
c.Assert(v, qt.Equals, 0)
v, err := m.GetOrCreate("d", func() (int, error) {
return 100, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, 100)
v, found = m.Lookup("d")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 100)
v, err = m.GetOrCreate("d", func() (int, error) {
return 200, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, 100)
wasSet := m.SetIfAbsent("e", 300)
c.Assert(wasSet, qt.Equals, true)
v, found = m.Lookup("e")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 300)
wasSet = m.SetIfAbsent("e", 400)
c.Assert(wasSet, qt.Equals, false)
v, found = m.Lookup("e")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 300)
m.WithWriteLock(func(m map[string]int) error {
m["f"] = 500
return nil
})
v, found = m.Lookup("f")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 500)
}
+7 -7
View File
@@ -21,8 +21,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/bep/helpers/maphelpers"
"github.com/gohugoio/go-radix" "github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files" "github.com/gohugoio/hugo/hugofs/files"
@@ -54,9 +54,9 @@ type allPagesAssembler struct {
rwRoot *doctree.NodeShiftTreeWalker[contentNode] // walks resources. rwRoot *doctree.NodeShiftTreeWalker[contentNode] // walks resources.
// Walking state. // Walking state.
seenTerms *hmaps.Map[term, sitesmatrix.Vectors] seenTerms *maphelpers.ConcurrentMap[term, sitesmatrix.Vectors]
droppedPages *hmaps.Map[*Site, []string] // e.g. drafts, expired, future. droppedPages *maphelpers.ConcurrentMap[*Site, []string] // e.g. drafts, expired, future.
seenRootSections *hmaps.Map[string, bool] seenRootSections *maphelpers.ConcurrentMap[string, bool]
seenHome bool // set before we fan out to multiple goroutines. seenHome bool // set before we fan out to multiple goroutines.
} }
@@ -75,7 +75,7 @@ func newAllPagesAssembler(
pw := rw.Extend() pw := rw.Extend()
pw.Tree = m.treePages pw.Tree = m.treePages
seenRootSections := hmaps.NewMap[string, bool]() seenRootSections := maphelpers.NewConcurrentMap[string, bool]()
seenRootSections.Set("", true) // home. seenRootSections.Set("", true) // home.
return &allPagesAssembler{ return &allPagesAssembler{
@@ -83,8 +83,8 @@ func newAllPagesAssembler(
h: h, h: h,
m: m, m: m,
assembleChanges: assembleChanges, assembleChanges: assembleChanges,
seenTerms: hmaps.NewMap[term, sitesmatrix.Vectors](), seenTerms: maphelpers.NewConcurrentMap[term, sitesmatrix.Vectors](),
droppedPages: hmaps.NewMap[*Site, []string](), droppedPages: maphelpers.NewConcurrentMap[*Site, []string](),
seenRootSections: seenRootSections, seenRootSections: seenRootSections,
assembleSectionsInParallel: !h.isRebuild(), // On partial rebuilds, there's potential data races with parallel section assembly. assembleSectionsInParallel: !h.isRebuild(), // On partial rebuilds, there's potential data races with parallel section assembly.
pwRoot: pw, pwRoot: pw,
+3 -2
View File
@@ -25,6 +25,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/bep/helpers/maphelpers"
"github.com/bep/logg" "github.com/bep/logg"
"github.com/gohugoio/go-radix" "github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/cache/dynacache" "github.com/gohugoio/hugo/cache/dynacache"
@@ -99,8 +100,8 @@ type HugoSites struct {
translationKeyPages *hmaps.SliceCache[page.Page] translationKeyPages *hmaps.SliceCache[page.Page]
pageTrees *pageTrees pageTrees *pageTrees
previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only. previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only.
previousSeenTerms *hmaps.Map[term, sitesmatrix.Vectors] // Set for rebuilds only. previousSeenTerms *maphelpers.ConcurrentMap[term, sitesmatrix.Vectors] // Set for rebuilds only.
printUnusedTemplatesInit sync.Once printUnusedTemplatesInit sync.Once
printPathWarningsInit sync.Once printPathWarningsInit sync.Once
+3 -3
View File
@@ -28,9 +28,9 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/bep/helpers/maphelpers"
"github.com/bep/textandbinarywriter" "github.com/bep/textandbinarywriter"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings" "github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/common/hugio"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -253,7 +253,7 @@ func (d *dispatcher[Q, R]) newCall(q Message[Q]) (*call[Q, R], error) {
if err := q.init(); err != nil { if err := q.init(); err != nil {
return nil, err return nil, err
} }
responseKinds := hmaps.NewMap[string, bool]() responseKinds := maphelpers.NewConcurrentMap[string, bool]()
for _, rk := range q.Header.ResponseKinds { for _, rk := range q.Header.ResponseKinds {
responseKinds.Set(rk, true) responseKinds.Set(rk, true)
} }
@@ -424,7 +424,7 @@ func (d *dispatcher[Q, R]) pendingCall(id uint32) *call[Q, R] {
type call[Q, R any] struct { type call[Q, R any] struct {
request Message[Q] request Message[Q]
response Message[R] response Message[R]
responseKinds *hmaps.Map[string, bool] responseKinds *maphelpers.ConcurrentMap[string, bool]
err error err error
donec chan *call[Q, R] donec chan *call[Q, R]
} }
+3 -3
View File
@@ -22,7 +22,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/gohugoio/hugo/common/hmaps" "github.com/bep/helpers/maphelpers"
"github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/cache/dynacache" "github.com/gohugoio/hugo/cache/dynacache"
@@ -58,7 +58,7 @@ func newResourceCache(rs *Spec, memCache *dynacache.Cache) *ResourceCache {
dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnChange, Weight: 40}, dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnChange, Weight: 40},
), ),
cacheResourceTransformationPublished: hmaps.NewMap[string, string](), cacheResourceTransformationPublished: maphelpers.NewConcurrentMap[string, string](),
} }
} }
@@ -72,7 +72,7 @@ type ResourceCache struct {
cacheResourceTransformation *dynacache.Partition[string, *resourceAdapterInner] cacheResourceTransformation *dynacache.Partition[string, *resourceAdapterInner]
// Used in rebuilds. Maps the target path to the last published transformation key. // Used in rebuilds. Maps the target path to the last published transformation key.
cacheResourceTransformationPublished *hmaps.Map[string, string] cacheResourceTransformationPublished *maphelpers.ConcurrentMap[string, string]
fileCache *filecache.Cache fileCache *filecache.Cache
} }