From: Bjørn Erik Pedersen Date: Sun, 19 Apr 2026 20:14:14 +0000 (+0200) Subject: Replace the concurrent map with an identical upstream version X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=7574e35b403ac4100649520c8ff718cdc1be1e8a;p=brevno-suite%2Fhugo Replace the concurrent map with an identical upstream version --- diff --git a/common/hmaps/map.go b/common/hmaps/map.go deleted file mode 100644 index 8bb1cfa2b..000000000 --- a/common/hmaps/map.go +++ /dev/null @@ -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 - } - } - } -} diff --git a/common/hmaps/map_test.go b/common/hmaps/map_test.go deleted file mode 100644 index 7791bc6da..000000000 --- a/common/hmaps/map_test.go +++ /dev/null @@ -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) -} diff --git a/hugolib/content_map_page_assembler.go b/hugolib/content_map_page_assembler.go index 3c7769224..3d0136687 100644 --- a/hugolib/content_map_page_assembler.go +++ b/hugolib/content_map_page_assembler.go @@ -21,8 +21,8 @@ import ( "strings" "time" + "github.com/bep/helpers/maphelpers" "github.com/gohugoio/go-radix" - "github.com/gohugoio/hugo/common/hmaps" "github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/hugofs/files" @@ -54,9 +54,9 @@ type allPagesAssembler struct { rwRoot *doctree.NodeShiftTreeWalker[contentNode] // walks resources. // Walking state. - seenTerms *hmaps.Map[term, sitesmatrix.Vectors] - droppedPages *hmaps.Map[*Site, []string] // e.g. drafts, expired, future. - seenRootSections *hmaps.Map[string, bool] + seenTerms *maphelpers.ConcurrentMap[term, sitesmatrix.Vectors] + droppedPages *maphelpers.ConcurrentMap[*Site, []string] // e.g. drafts, expired, future. + seenRootSections *maphelpers.ConcurrentMap[string, bool] seenHome bool // set before we fan out to multiple goroutines. } @@ -75,7 +75,7 @@ func newAllPagesAssembler( pw := rw.Extend() pw.Tree = m.treePages - seenRootSections := hmaps.NewMap[string, bool]() + seenRootSections := maphelpers.NewConcurrentMap[string, bool]() seenRootSections.Set("", true) // home. return &allPagesAssembler{ @@ -83,8 +83,8 @@ func newAllPagesAssembler( h: h, m: m, assembleChanges: assembleChanges, - seenTerms: hmaps.NewMap[term, sitesmatrix.Vectors](), - droppedPages: hmaps.NewMap[*Site, []string](), + seenTerms: maphelpers.NewConcurrentMap[term, sitesmatrix.Vectors](), + droppedPages: maphelpers.NewConcurrentMap[*Site, []string](), seenRootSections: seenRootSections, assembleSectionsInParallel: !h.isRebuild(), // On partial rebuilds, there's potential data races with parallel section assembly. pwRoot: pw, diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 50ae2f2d9..87c04035d 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -25,6 +25,7 @@ import ( "sync/atomic" "time" + "github.com/bep/helpers/maphelpers" "github.com/bep/logg" "github.com/gohugoio/go-radix" "github.com/gohugoio/hugo/cache/dynacache" @@ -99,8 +100,8 @@ type HugoSites struct { translationKeyPages *hmaps.SliceCache[page.Page] pageTrees *pageTrees - previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only. - previousSeenTerms *hmaps.Map[term, sitesmatrix.Vectors] // Set for rebuilds only. + previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only. + previousSeenTerms *maphelpers.ConcurrentMap[term, sitesmatrix.Vectors] // Set for rebuilds only. printUnusedTemplatesInit sync.Once printPathWarningsInit sync.Once diff --git a/internal/warpc/warpc.go b/internal/warpc/warpc.go index 58dcf5dc7..061c08768 100644 --- a/internal/warpc/warpc.go +++ b/internal/warpc/warpc.go @@ -28,9 +28,9 @@ import ( "sync/atomic" "time" + "github.com/bep/helpers/maphelpers" "github.com/bep/textandbinarywriter" - "github.com/gohugoio/hugo/common/hmaps" "github.com/gohugoio/hugo/common/hstrings" "github.com/gohugoio/hugo/common/hugio" "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 { return nil, err } - responseKinds := hmaps.NewMap[string, bool]() + responseKinds := maphelpers.NewConcurrentMap[string, bool]() for _, rk := range q.Header.ResponseKinds { 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 { request Message[Q] response Message[R] - responseKinds *hmaps.Map[string, bool] + responseKinds *maphelpers.ConcurrentMap[string, bool] err error donec chan *call[Q, R] } diff --git a/resources/resource_cache.go b/resources/resource_cache.go index dbdd829d2..a9c71c6ce 100644 --- a/resources/resource_cache.go +++ b/resources/resource_cache.go @@ -22,7 +22,7 @@ import ( "strings" "sync" - "github.com/gohugoio/hugo/common/hmaps" + "github.com/bep/helpers/maphelpers" "github.com/gohugoio/hugo/resources/resource" "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}, ), - cacheResourceTransformationPublished: hmaps.NewMap[string, string](), + cacheResourceTransformationPublished: maphelpers.NewConcurrentMap[string, string](), } } @@ -72,7 +72,7 @@ type ResourceCache struct { cacheResourceTransformation *dynacache.Partition[string, *resourceAdapterInner] // 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 }