Fix some server/watch rebuild issues

Two issues:

1. Fixe potential edit-loop in server/watch mode (see below)
2. Drain the cache eviction stack before we start calculating the change set. This should allow more fine grained rebuilds for bigger sites and/or in low memory situations.

The fix in 6c68142cc1 wasn't really fixing the complete problem.

In Hugo we have some steps that takes more time than others, one example being CSS building with TailwindCSS.

The symptom here is that sometimes when you:

1. Edit content or templates that does not trigger a CSS rebuild => Snappy rebuild.
2. Edit stylesheet or add a CSS class to template that triggers a CSS rebuild => relatively slow rebuild (expected)
3. Then back to content editing or template edits that should not trigger a CSS rebuild => relatively slow rebuild (not expected)

This commit fixes this by pulling the dynacache GC step up and merge it with the cache buster step.

Fixes #13316
This commit is contained in:
Bjørn Erik Pedersen
2025-01-31 10:35:01 +01:00
parent 778f0d9002
commit db28695ff5
6 changed files with 59 additions and 51 deletions

View File

@@ -1123,6 +1123,9 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
l logg.LevelLogger,
cachebuster func(s string) bool, changes []identity.Identity,
) error {
// Drain the cache eviction stack to start fresh.
h.Deps.MemCache.DrainEvictedIdentities()
h.Log.Debug().Log(logg.StringFunc(
func() string {
var sb strings.Builder
@@ -1163,17 +1166,32 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
}
// The order matters here:
// 1. Handle the cache busters first, as those may produce identities for the page reset step.
// 1. Then GC the cache, which may produce changes.
// 2. Then reset the page outputs, which may mark some resources as stale.
// 3. Then GC the cache.
if cachebuster != nil {
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
ll := l.WithField("substep", "gc dynacache cachebuster")
h.dynacacheGCCacheBuster(cachebuster)
return ll, nil
}); err != nil {
return err
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
ll := l.WithField("substep", "gc dynacache")
predicate := func(k any, v any) bool {
if cachebuster != nil {
if s, ok := k.(string); ok {
return cachebuster(s)
}
}
return false
}
h.MemCache.ClearOnRebuild(predicate, changes...)
h.Log.Trace(logg.StringFunc(func() string {
var sb strings.Builder
sb.WriteString("dynacache keys:\n")
for _, key := range h.MemCache.Keys(nil) {
sb.WriteString(fmt.Sprintf(" %s\n", key))
}
return sb.String()
}))
return ll, nil
}); err != nil {
return err
}
// Drain the cache eviction stack.
@@ -1238,23 +1256,6 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
return err
}
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
ll := l.WithField("substep", "gc dynacache")
h.MemCache.ClearOnRebuild(changes...)
h.Log.Trace(logg.StringFunc(func() string {
var sb strings.Builder
sb.WriteString("dynacache keys:\n")
for _, key := range h.MemCache.Keys(nil) {
sb.WriteString(fmt.Sprintf(" %s\n", key))
}
return sb.String()
}))
return ll, nil
}); err != nil {
return err
}
return nil
}

View File

@@ -27,7 +27,6 @@ import (
"github.com/bep/logg"
"github.com/gohugoio/hugo/bufferpool"
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugofs/files"
@@ -828,6 +827,11 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
addedContentPaths []*paths.Path
)
var (
addedOrChangedContent []pathChange
changes []identity.Identity
)
for _, ev := range eventInfos {
cpss := h.BaseFs.ResolvePaths(ev.Name)
pss := make([]*paths.Path, len(cpss))
@@ -854,6 +858,13 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
if err == nil && g != nil {
cacheBusters = append(cacheBusters, g)
}
if ev.added {
changes = append(changes, identity.StructuralChangeAdd)
}
if ev.removed {
changes = append(changes, identity.StructuralChangeRemove)
}
}
if ev.removed {
@@ -865,11 +876,6 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
}
}
var (
addedOrChangedContent []pathChange
changes []identity.Identity
)
// Find the most specific identity possible.
handleChange := func(pathInfo *paths.Path, delete, isDir bool) {
switch pathInfo.Component() {
@@ -1063,18 +1069,6 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
resourceFiles := h.fileEventsContentPaths(addedOrChangedContent)
defer func() {
// See issue 13316.
h.MemCache.DrainEvictedIdentitiesMatching(func(ki dynacache.KeyIdentity) bool {
for _, c := range changes {
if c.IdentifierBase() == ki.Identity.IdentifierBase() {
return true
}
}
return false
})
}()
changed := &WhatChanged{
needsPagesAssembly: needsPagesAssemble,
identitySet: make(identity.Identities),