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
 | |
| }
 |