mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	Expand the text in the init timeout message to give the reader more hints as to what may be causing the issue. The current error sent me on a wild goose chase trying to debug my shortcodes, but it turned out that the issue was just that I wasn't caching my resources/ directory on my build server. Once I found this blog post, I solved the issue in minutes: https://www.henriksommerfeld.se/hugo-timeout-not-a-circular-loop-in-shortcode/ Googling the error message yields several additional posts from users who focus on loops in their shortcode when the problem seems to be overly-expensive builds.
		
			
				
	
	
		
			199 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			199 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2019 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 lazy
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
)
 | 
						|
 | 
						|
// New creates a new empty Init.
 | 
						|
func New() *Init {
 | 
						|
	return &Init{}
 | 
						|
}
 | 
						|
 | 
						|
// Init holds a graph of lazily initialized dependencies.
 | 
						|
type Init struct {
 | 
						|
	mu sync.Mutex
 | 
						|
 | 
						|
	prev     *Init
 | 
						|
	children []*Init
 | 
						|
 | 
						|
	init onceMore
 | 
						|
	out  interface{}
 | 
						|
	err  error
 | 
						|
	f    func() (interface{}, error)
 | 
						|
}
 | 
						|
 | 
						|
// Add adds a func as a new child dependency.
 | 
						|
func (ini *Init) Add(initFn func() (interface{}, error)) *Init {
 | 
						|
	if ini == nil {
 | 
						|
		ini = New()
 | 
						|
	}
 | 
						|
	return ini.add(false, initFn)
 | 
						|
}
 | 
						|
 | 
						|
// AddWithTimeout is same as Add, but with a timeout that aborts initialization.
 | 
						|
func (ini *Init) AddWithTimeout(timeout time.Duration, f func(ctx context.Context) (interface{}, error)) *Init {
 | 
						|
	return ini.Add(func() (interface{}, error) {
 | 
						|
		return ini.withTimeout(timeout, f)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Branch creates a new dependency branch based on an existing and adds
 | 
						|
// the given dependency as a child.
 | 
						|
func (ini *Init) Branch(initFn func() (interface{}, error)) *Init {
 | 
						|
	if ini == nil {
 | 
						|
		ini = New()
 | 
						|
	}
 | 
						|
	return ini.add(true, initFn)
 | 
						|
}
 | 
						|
 | 
						|
// BranchdWithTimeout is same as Branch, but with a timeout.
 | 
						|
func (ini *Init) BranchWithTimeout(timeout time.Duration, f func(ctx context.Context) (interface{}, error)) *Init {
 | 
						|
	return ini.Branch(func() (interface{}, error) {
 | 
						|
		return ini.withTimeout(timeout, f)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Do initializes the entire dependency graph.
 | 
						|
func (ini *Init) Do() (interface{}, error) {
 | 
						|
	if ini == nil {
 | 
						|
		panic("init is nil")
 | 
						|
	}
 | 
						|
 | 
						|
	ini.init.Do(func() {
 | 
						|
		prev := ini.prev
 | 
						|
		if prev != nil {
 | 
						|
			// A branch. Initialize the ancestors.
 | 
						|
			if prev.shouldInitialize() {
 | 
						|
				_, err := prev.Do()
 | 
						|
				if err != nil {
 | 
						|
					ini.err = err
 | 
						|
					return
 | 
						|
				}
 | 
						|
			} else if prev.inProgress() {
 | 
						|
				// Concurrent initialization. The following init func
 | 
						|
				// may depend on earlier state, so wait.
 | 
						|
				prev.wait()
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if ini.f != nil {
 | 
						|
			ini.out, ini.err = ini.f()
 | 
						|
		}
 | 
						|
 | 
						|
		for _, child := range ini.children {
 | 
						|
			if child.shouldInitialize() {
 | 
						|
				_, err := child.Do()
 | 
						|
				if err != nil {
 | 
						|
					ini.err = err
 | 
						|
					return
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
 | 
						|
	ini.wait()
 | 
						|
 | 
						|
	return ini.out, ini.err
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
// TODO(bep) investigate if we can use sync.Cond for this.
 | 
						|
func (ini *Init) wait() {
 | 
						|
	var counter time.Duration
 | 
						|
	for !ini.init.Done() {
 | 
						|
		counter += 10
 | 
						|
		if counter > 600000000 {
 | 
						|
			panic("BUG: timed out in lazy init")
 | 
						|
		}
 | 
						|
		time.Sleep(counter * time.Microsecond)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (ini *Init) inProgress() bool {
 | 
						|
	return ini != nil && ini.init.InProgress()
 | 
						|
}
 | 
						|
 | 
						|
func (ini *Init) shouldInitialize() bool {
 | 
						|
	return !(ini == nil || ini.init.Done() || ini.init.InProgress())
 | 
						|
}
 | 
						|
 | 
						|
// Reset resets the current and all its dependencies.
 | 
						|
func (ini *Init) Reset() {
 | 
						|
	mu := ini.init.ResetWithLock()
 | 
						|
	defer mu.Unlock()
 | 
						|
	for _, d := range ini.children {
 | 
						|
		d.Reset()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (ini *Init) add(branch bool, initFn func() (interface{}, error)) *Init {
 | 
						|
	ini.mu.Lock()
 | 
						|
	defer ini.mu.Unlock()
 | 
						|
 | 
						|
	if branch {
 | 
						|
		return &Init{
 | 
						|
			f:    initFn,
 | 
						|
			prev: ini,
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	ini.checkDone()
 | 
						|
	ini.children = append(ini.children, &Init{
 | 
						|
		f: initFn,
 | 
						|
	})
 | 
						|
 | 
						|
	return ini
 | 
						|
}
 | 
						|
 | 
						|
func (ini *Init) checkDone() {
 | 
						|
	if ini.init.Done() {
 | 
						|
		panic("init cannot be added to after it has run")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (ini *Init) withTimeout(timeout time.Duration, f func(ctx context.Context) (interface{}, error)) (interface{}, error) {
 | 
						|
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 | 
						|
	defer cancel()
 | 
						|
	c := make(chan verr, 1)
 | 
						|
 | 
						|
	go func() {
 | 
						|
		v, err := f(ctx)
 | 
						|
		select {
 | 
						|
		case <-ctx.Done():
 | 
						|
			return
 | 
						|
		default:
 | 
						|
			c <- verr{v: v, err: err}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	select {
 | 
						|
	case <-ctx.Done():
 | 
						|
		return nil, errors.New("timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.")
 | 
						|
	case ve := <-c:
 | 
						|
		return ve.v, ve.err
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
type verr struct {
 | 
						|
	v   interface{}
 | 
						|
	err error
 | 
						|
}
 |