ref(engine): Render templates in parallel

Signed-off-by: Jad Kik <jad.elkik@lightspeedhq.com>
pull/9971/head
Jad Kik 4 years ago
parent 29d273f985
commit af169e4d2d
No known key found for this signature in database
GPG Key ID: C43DA2849B37BC16

@ -22,8 +22,11 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime"
"sort" "sort"
"strconv"
"strings" "strings"
"sync"
"text/template" "text/template"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -102,14 +105,32 @@ var warnRegex = regexp.MustCompile(warnStartDelim + `(.*)` + warnEndDelim)
func warnWrap(warn string) string { func warnWrap(warn string) string {
return warnStartDelim + warn + warnEndDelim return warnStartDelim + warn + warnEndDelim
} }
func goid() int {
var buf [64]byte
n := runtime.Stack(buf[:], false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
id, err := strconv.Atoi(idField)
if err != nil {
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
}
return id
}
// initFunMap creates the Engine's FuncMap and adds context-specific functions. // initFunMap creates the Engine's FuncMap and adds context-specific functions.
func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) { func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) {
funcMap := funcMap() funcMap := funcMap()
includedNames := make(map[string]int) var includedNamesPerProcess sync.Map
// Add the 'include' function here so we can close over t. // Add the 'include' function here so we can close over t.
funcMap["include"] = func(name string, data interface{}) (string, error) { funcMap["include"] = func(name string, data chartutil.Values) (string, error) {
processId:=goid()
var includedNames_, ok = includedNamesPerProcess.Load(processId)
if !ok {
includedNames_ = make(map[string]int)
includedNamesPerProcess.Store(processId, includedNames_)
}
includedNames := includedNames_.(map[string]int)
var buf strings.Builder var buf strings.Builder
if v, ok := includedNames[name]; ok { if v, ok := includedNames[name]; ok {
if v > recursionMaxNums { if v > recursionMaxNums {
@ -126,6 +147,10 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render
// Add the 'tpl' function here // Add the 'tpl' function here
funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) { funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) {
// if tpl is not a template just return the string.
if !strings.Contains(tpl, "{") {
return tpl, nil
}
basePath, err := vals.PathValue("Template.BasePath") basePath, err := vals.PathValue("Template.BasePath")
if err != nil { if err != nil {
return "", errors.Wrapf(err, "cannot retrieve Template.Basepath from values inside tpl function: %s", tpl) return "", errors.Wrapf(err, "cannot retrieve Template.Basepath from values inside tpl function: %s", tpl)
@ -144,7 +169,7 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render
}, },
} }
result, err := e.renderWithReferences(templates, referenceTpls) result, err := e.renderWithReferencesInternal(templates, referenceTpls, false)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl) return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl)
} }
@ -200,6 +225,12 @@ func (e Engine) render(tpls map[string]renderable) (map[string]string, error) {
// renderWithReferences takes a map of templates/values to render, and a map of // renderWithReferences takes a map of templates/values to render, and a map of
// templates which can be referenced within them. // templates which can be referenced within them.
func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) (rendered map[string]string, err error) { func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) (rendered map[string]string, err error) {
return e.renderWithReferencesInternal(tpls, tpls, true)
}
// renderWithReferences takes a map of templates/values to render, and a map of
// templates which can be referenced within them.
func (e Engine) renderWithReferencesInternal(tpls, referenceTpls map[string]renderable, toplevel bool) (rendered map[string]string, err error) {
// Basically, what we do here is start with an empty parent template and then // Basically, what we do here is start with an empty parent template and then
// build up a list of templates -- one for each file. Once all of the templates // build up a list of templates -- one for each file. Once all of the templates
// have been parsed, we loop through again and execute every template. // have been parsed, we loop through again and execute every template.
@ -247,6 +278,11 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable)
} }
rendered = make(map[string]string, len(keys)) rendered = make(map[string]string, len(keys))
var renderResults chan RenderResults = nil
if toplevel {
renderResults = make(chan RenderResults, len(keys))
}
for _, filename := range keys { for _, filename := range keys {
// Don't render partials. We don't care out the direct output of partials. // Don't render partials. We don't care out the direct output of partials.
// They are only included from other templates. // They are only included from other templates.
@ -255,19 +291,53 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable)
} }
// At render time, add information about the template that is being rendered. // At render time, add information about the template that is being rendered.
vals := tpls[filename].vals vals := tpls[filename].vals
if vals["Template"] == nil {
vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath} vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath}
}
if toplevel {
go doRenderAsync(renderResults, filename, t, vals)
} else {
r,renderError := doRender(filename, t, vals)
if renderError != nil {
return map[string]string{}, cleanupExecError(filename, renderError)
}
rendered[filename] = r
}
}
if renderResults !=nil {
for _, filename := range keys {
if strings.HasPrefix(path.Base(filename), "_") {
continue
}
renderResult := <-renderResults
if renderResult.error != nil {
return map[string]string{}, cleanupExecError(renderResult.filename, renderResult.error)
}
rendered[renderResult.filename] = renderResult.rendered
}
}
return rendered, nil
}
type RenderResults struct {
filename string
rendered string
error error
}
func doRenderAsync(renderResults chan RenderResults, filename string, t* template.Template, vals chartutil.Values) {
rendered, error := doRender(filename, t, vals)
renderResults <- RenderResults{filename: filename, rendered: rendered, error: error}
}
func doRender(filename string, t* template.Template, vals chartutil.Values) (string, error) {
var buf strings.Builder var buf strings.Builder
if err := t.ExecuteTemplate(&buf, filename, vals); err != nil { if err := t.ExecuteTemplate(&buf, filename, vals); err != nil {
return map[string]string{}, cleanupExecError(filename, err) return "", cleanupExecError(filename, err)
} }
// Work around the issue where Go will emit "<no value>" even if Options(missing=zero) // Work around the issue where Go will emit "<no value>" even if Options(missing=zero)
// is set. Since missing=error will never get here, we do not need to handle // is set. Since missing=error will never get here, we do not need to handle
// the Strict case. // the Strict case.
rendered[filename] = strings.ReplaceAll(buf.String(), "<no value>", "") return strings.ReplaceAll(buf.String(), "<no value>", ""), nil
}
return rendered, nil
} }
func cleanupParseError(filename string, err error) error { func cleanupParseError(filename string, err error) error {

Loading…
Cancel
Save