From 4a366fcfee24b3a5a5045b16c3b87b76147adf5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 17 Oct 2018 09:28:04 +0200 Subject: [PATCH] Prevent stale content in Fast Render Mode We do that by re-render visited pages that is not already in the stack. This may potentially do some double work, but that small penalty should be well worth it. Fixes #5281 --- commands/commandeer.go | 5 +-- commands/hugo.go | 27 +++++++++----- commands/server.go | 14 +++++++- common/types/evictingqueue.go | 7 ++++ common/types/evictingqueue_test.go | 3 ++ hugolib/hugo_sites.go | 5 +++ hugolib/hugo_sites_build.go | 56 ++++++++++++++++++------------ hugolib/site_render.go | 2 +- 8 files changed, 83 insertions(+), 36 deletions(-) diff --git a/commands/commandeer.go b/commands/commandeer.go index 2b76462fe..94b2c6553 100644 --- a/commands/commandeer.go +++ b/commands/commandeer.go @@ -324,10 +324,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error { fs.Destination = new(afero.MemMapFs) } - doLiveReload := !c.h.buildWatch && !config.GetBool("disableLiveReload") - fastRenderMode := doLiveReload && !config.GetBool("disableFastRender") - - if fastRenderMode { + if c.fastRenderMode { // For now, fast render mode only. It should, however, be fast enough // for the full variant, too. changeDetector := &fileChangeDetector{ diff --git a/commands/hugo.go b/commands/hugo.go index 6cb2ec012..deaa1f7ff 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -618,13 +618,20 @@ func (c *commandeer) buildSites() (err error) { return c.hugo.Build(hugolib.BuildCfg{}) } +func (c *commandeer) handleBuildErr(err error, msg string) { + c.buildErr = err + c.logger.ERROR.Printf("%s: %s", msg, err) + if !c.h.quiet && c.h.verbose { + herrors.PrintStackTrace(err) + } +} + func (c *commandeer) rebuildSites(events []fsnotify.Event) error { defer c.timeTrack(time.Now(), "Total") c.buildErr = nil visited := c.visitedURLs.PeekAllSet() - doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") - if doLiveReload && !c.Cfg.GetBool("disableFastRender") { + if c.fastRenderMode { // Make sure we always render the home pages for _, l := range c.languages { @@ -640,6 +647,15 @@ func (c *commandeer) rebuildSites(events []fsnotify.Event) error { return c.hugo.Build(hugolib.BuildCfg{RecentlyVisited: visited}, events...) } +func (c *commandeer) partialReRender(urls ...string) error { + c.buildErr = nil + visited := make(map[string]bool) + for _, url := range urls { + visited[url] = true + } + return c.hugo.Build(hugolib.BuildCfg{RecentlyVisited: visited, PartialReRender: true}) +} + func (c *commandeer) fullRebuild() { c.commandeerHugoState = &commandeerHugoState{} err := c.loadConfig(true, true) @@ -907,15 +923,10 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher, c.changeDetector.PrepareNew() if err := c.rebuildSites(dynamicEvents); err != nil { - c.buildErr = err - c.logger.ERROR.Printf("Rebuild failed: %s", err) - if !c.h.quiet && c.h.verbose { - herrors.PrintStackTrace(err) - } + c.handleBuildErr(err, "Rebuild failed") } if doLiveReload { - if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 { changed := c.changeDetector.changed() if c.changeDetector != nil && len(changed) == 0 { diff --git a/commands/server.go b/commands/server.go index ffdbc95c9..7b7164eea 100644 --- a/commands/server.go +++ b/commands/server.go @@ -345,10 +345,22 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro w.Header().Set("Pragma", "no-cache") } - if f.c.fastRenderMode { + if f.c.fastRenderMode && f.c.buildErr == nil { p := r.RequestURI if strings.HasSuffix(p, "/") || strings.HasSuffix(p, "html") || strings.HasSuffix(p, "htm") { + if !f.c.visitedURLs.Contains(p) { + // If not already on stack, re-render that single page. + if err := f.c.partialReRender(p); err != nil { + f.c.handleBuildErr(err, fmt.Sprintf("Failed to render %q", p)) + if f.c.showErrorInBrowser { + http.Redirect(w, r, p, 301) + return + } + } + } + f.c.visitedURLs.Add(p) + } } h.ServeHTTP(w, r) diff --git a/common/types/evictingqueue.go b/common/types/evictingqueue.go index 152dc4c41..884762426 100644 --- a/common/types/evictingqueue.go +++ b/common/types/evictingqueue.go @@ -52,6 +52,13 @@ func (q *EvictingStringQueue) Add(v string) { q.mu.Unlock() } +// Contains returns whether the queue contains v. +func (q *EvictingStringQueue) Contains(v string) bool { + q.mu.Lock() + defer q.mu.Unlock() + return q.set[v] +} + // Peek looks at the last element added to the queue. func (q *EvictingStringQueue) Peek() string { q.mu.Lock() diff --git a/common/types/evictingqueue_test.go b/common/types/evictingqueue_test.go index a33f1a344..a7b1e1d54 100644 --- a/common/types/evictingqueue_test.go +++ b/common/types/evictingqueue_test.go @@ -36,6 +36,9 @@ func TestEvictingStringQueue(t *testing.T) { queue.Add("a") queue.Add("b") + assert.True(queue.Contains("a")) + assert.False(queue.Contains("foo")) + assert.Equal([]string{"b", "a"}, queue.PeekAll()) assert.Equal("b", queue.Peek()) queue.Add("c") diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index d9eb9f57d..7f70967d6 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -368,6 +368,11 @@ type BuildCfg struct { SkipRender bool // Use this to indicate what changed (for rebuilds). whatChanged *whatChanged + + // This is a partial re-render of some selected pages. This means + // we should skip most of the processing. + PartialReRender bool + // Recently visited URLs. This is used for partial re-rendering. RecentlyVisited map[string]bool } diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index 5bb328aa2..13fbfd57e 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -41,29 +41,31 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error { conf.whatChanged = &whatChanged{source: true, other: true} } - for _, s := range h.Sites { - s.Deps.BuildStartListeners.Notify() - } + if !config.PartialReRender { + for _, s := range h.Sites { + s.Deps.BuildStartListeners.Notify() + } - if len(events) > 0 { - // Rebuild - if err := h.initRebuild(conf); err != nil { + if len(events) > 0 { + // Rebuild + if err := h.initRebuild(conf); err != nil { + return err + } + } else { + if err := h.init(conf); err != nil { + return err + } + } + + if err := h.process(conf, events...); err != nil { return err } - } else { - if err := h.init(conf); err != nil { + + if err := h.assemble(conf); err != nil { return err } } - if err := h.process(conf, events...); err != nil { - return err - } - - if err := h.assemble(conf); err != nil { - return err - } - if err := h.render(conf); err != nil { return err } @@ -226,8 +228,10 @@ func (h *HugoSites) assemble(config *BuildCfg) error { } func (h *HugoSites) render(config *BuildCfg) error { - for _, s := range h.Sites { - s.initRenderFormats() + if !config.PartialReRender { + for _, s := range h.Sites { + s.initRenderFormats() + } } for _, s := range h.Sites { @@ -240,15 +244,23 @@ func (h *HugoSites) render(config *BuildCfg) error { isRenderingSite := s == s2 - if err := s2.preparePagesForRender(isRenderingSite && i == 0); err != nil { - return err + if !config.PartialReRender { + if err := s2.preparePagesForRender(isRenderingSite && i == 0); err != nil { + return err + } } } if !config.SkipRender { - if err := s.render(config, i); err != nil { - return err + if config.PartialReRender { + if err := s.renderPages(config); err != nil { + return err + } + } else { + if err := s.render(config, i); err != nil { + return err + } } } } diff --git a/hugolib/site_render.go b/hugolib/site_render.go index 13fbb43cd..6583acd06 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -43,7 +43,7 @@ func (s *Site) renderPages(cfg *BuildCfg) error { go pageRenderer(s, pages, results, wg) } - if len(s.headlessPages) > 0 { + if !cfg.PartialReRender && len(s.headlessPages) > 0 { wg.Add(1) go headlessPagesPublisher(s, wg) }