Skip to content

Commit e375037

Browse files
authored
Use globally shared HTMLRender (#24436)
The old `HTMLRender` is not ideal. 1. It shouldn't be initialized multiple times, it consumes a lot of memory and is slow. 2. It shouldn't depend on short-lived requests, the `WatchLocalChanges` needs a long-running context. 3. It doesn't make sense to use FuncsMap slice. HTMLRender was designed to only work for GItea's specialized 400+ templates, so it's good to make it a global shared instance.
1 parent 8f4dafc commit e375037

File tree

11 files changed

+37
-34
lines changed

11 files changed

+37
-34
lines changed

modules/context/context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ func getCsrfOpts() CsrfOptions {
677677

678678
// Contexter initializes a classic context for a request.
679679
func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
680-
_, rnd := templates.HTMLRenderer(ctx)
680+
rnd := templates.HTMLRenderer()
681681
csrfOpts := getCsrfOpts()
682682
if !setting.IsProd {
683683
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose

modules/context/package.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func determineAccessMode(ctx *Context) (perm.AccessMode, error) {
131131

132132
// PackageContexter initializes a package context for a request.
133133
func PackageContexter(ctx gocontext.Context) func(next http.Handler) http.Handler {
134-
_, rnd := templates.HTMLRenderer(ctx)
134+
rnd := templates.HTMLRenderer()
135135
return func(next http.Handler) http.Handler {
136136
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
137137
ctx := Context{

modules/templates/helper.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"html"
1111
"html/template"
1212
"net/url"
13-
"regexp"
1413
"strings"
1514
"time"
1615

@@ -26,12 +25,9 @@ import (
2625
"code.gitea.io/gitea/services/gitdiff"
2726
)
2827

29-
// Used from static.go && dynamic.go
30-
var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}[\s]*$`)
31-
3228
// NewFuncMap returns functions for injecting to templates
33-
func NewFuncMap() []template.FuncMap {
34-
return []template.FuncMap{map[string]interface{}{
29+
func NewFuncMap() template.FuncMap {
30+
return map[string]interface{}{
3531
"DumpVar": dumpVar,
3632

3733
// -----------------------------------------------------------------
@@ -192,7 +188,7 @@ func NewFuncMap() []template.FuncMap {
192188

193189
"FilenameIsImage": FilenameIsImage,
194190
"TabSizeClass": TabSizeClass,
195-
}}
191+
}
196192
}
197193

198194
// Safe render raw as HTML

modules/templates/htmlrenderer.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package templates
66
import (
77
"bufio"
88
"bytes"
9-
"context"
109
"errors"
1110
"fmt"
1211
"io"
@@ -15,24 +14,29 @@ import (
1514
"regexp"
1615
"strconv"
1716
"strings"
17+
"sync"
1818
"sync/atomic"
1919
texttemplate "text/template"
2020

2121
"code.gitea.io/gitea/modules/assetfs"
22+
"code.gitea.io/gitea/modules/graceful"
2223
"code.gitea.io/gitea/modules/log"
2324
"code.gitea.io/gitea/modules/setting"
2425
"code.gitea.io/gitea/modules/templates/scopedtmpl"
2526
"code.gitea.io/gitea/modules/util"
2627
)
2728

28-
var rendererKey interface{} = "templatesHtmlRenderer"
29-
3029
type TemplateExecutor scopedtmpl.TemplateExecutor
3130

3231
type HTMLRender struct {
3332
templates atomic.Pointer[scopedtmpl.ScopedTemplate]
3433
}
3534

35+
var (
36+
htmlRender *HTMLRender
37+
htmlRenderOnce sync.Once
38+
)
39+
3640
var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors")
3741

3842
func (h *HTMLRender) HTML(w io.Writer, status int, name string, data interface{}) error {
@@ -55,14 +59,14 @@ func (h *HTMLRender) TemplateLookup(name string) (TemplateExecutor, error) {
5559
return nil, ErrTemplateNotInitialized
5660
}
5761

58-
return tmpls.Executor(name, NewFuncMap()[0])
62+
return tmpls.Executor(name, NewFuncMap())
5963
}
6064

6165
func (h *HTMLRender) CompileTemplates() error {
6266
assets := AssetFS()
6367
extSuffix := ".tmpl"
6468
tmpls := scopedtmpl.NewScopedTemplate()
65-
tmpls.Funcs(NewFuncMap()[0])
69+
tmpls.Funcs(NewFuncMap())
6670
files, err := ListWebTemplateAssetNames(assets)
6771
if err != nil {
6872
return nil
@@ -86,35 +90,36 @@ func (h *HTMLRender) CompileTemplates() error {
8690
return nil
8791
}
8892

89-
// HTMLRenderer returns the current html renderer for the context or creates and stores one within the context for future use
90-
func HTMLRenderer(ctx context.Context) (context.Context, *HTMLRender) {
91-
if renderer, ok := ctx.Value(rendererKey).(*HTMLRender); ok {
92-
return ctx, renderer
93-
}
93+
// HTMLRenderer init once and returns the globally shared html renderer
94+
func HTMLRenderer() *HTMLRender {
95+
htmlRenderOnce.Do(initHTMLRenderer)
96+
return htmlRender
97+
}
9498

99+
func initHTMLRenderer() {
95100
rendererType := "static"
96101
if !setting.IsProd {
97102
rendererType = "auto-reloading"
98103
}
99-
log.Log(1, log.DEBUG, "Creating "+rendererType+" HTML Renderer")
104+
log.Debug("Creating %s HTML Renderer", rendererType)
100105

101-
renderer := &HTMLRender{}
102-
if err := renderer.CompileTemplates(); err != nil {
106+
htmlRender = &HTMLRender{}
107+
if err := htmlRender.CompileTemplates(); err != nil {
103108
p := &templateErrorPrettier{assets: AssetFS()}
104109
wrapFatal(p.handleFuncNotDefinedError(err))
105110
wrapFatal(p.handleUnexpectedOperandError(err))
106111
wrapFatal(p.handleExpectedEndError(err))
107112
wrapFatal(p.handleGenericTemplateError(err))
108113
log.Fatal("HTMLRenderer CompileTemplates error: %v", err)
109114
}
115+
110116
if !setting.IsProd {
111-
go AssetFS().WatchLocalChanges(ctx, func() {
112-
if err := renderer.CompileTemplates(); err != nil {
117+
go AssetFS().WatchLocalChanges(graceful.GetManager().ShutdownContext(), func() {
118+
if err := htmlRender.CompileTemplates(); err != nil {
113119
log.Error("Template error: %v\n%s", err, log.Stack(2))
114120
}
115121
})
116122
}
117-
return context.WithValue(ctx, rendererKey, renderer), renderer
118123
}
119124

120125
func wrapFatal(msg string) {

modules/templates/mailer.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package templates
66
import (
77
"context"
88
"html/template"
9+
"regexp"
910
"strings"
1011
texttmpl "text/template"
1112

@@ -14,6 +15,8 @@ import (
1415
"code.gitea.io/gitea/modules/setting"
1516
)
1617

18+
var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`)
19+
1720
// mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject
1821
func mailSubjectTextFuncMap() texttmpl.FuncMap {
1922
return texttmpl.FuncMap{
@@ -55,9 +58,7 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) {
5558
bodyTemplates := template.New("")
5659

5760
subjectTemplates.Funcs(mailSubjectTextFuncMap())
58-
for _, funcs := range NewFuncMap() {
59-
bodyTemplates.Funcs(funcs)
60-
}
61+
bodyTemplates.Funcs(NewFuncMap())
6162

6263
assetFS := AssetFS()
6364
refreshTemplates := func() {

routers/api/v1/misc/markup_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const (
3030
)
3131

3232
func createContext(req *http.Request) (*context.Context, *httptest.ResponseRecorder) {
33-
_, rnd := templates.HTMLRenderer(req.Context())
33+
rnd := templates.HTMLRenderer()
3434
resp := httptest.NewRecorder()
3535
c := &context.Context{
3636
Req: req,

routers/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func GlobalInitInstalled(ctx context.Context) {
175175

176176
// NormalRoutes represents non install routes
177177
func NormalRoutes(ctx context.Context) *web.Route {
178-
ctx, _ = templates.HTMLRenderer(ctx)
178+
_ = templates.HTMLRenderer()
179179
r := web.NewRoute()
180180
r.Use(common.ProtocolMiddlewares()...)
181181

routers/install/install.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func getSupportedDbTypeNames() (dbTypeNames []map[string]string) {
5555

5656
// Init prepare for rendering installation page
5757
func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
58-
_, rnd := templates.HTMLRenderer(ctx)
58+
rnd := templates.HTMLRenderer()
5959
dbTypeNames := getSupportedDbTypeNames()
6060
return func(next http.Handler) http.Handler {
6161
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {

routers/install/routes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
6666
if !setting.IsProd {
6767
store["ErrorMsg"] = combinedErr
6868
}
69-
_, rnd := templates.HTMLRenderer(ctx)
69+
rnd := templates.HTMLRenderer()
7070
err = rnd.HTML(w, http.StatusInternalServerError, "status/500", templates.BaseVars().Merge(store))
7171
if err != nil {
7272
log.Error("%v", err)

routers/web/base.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (d *dataStore) GetData() map[string]interface{} {
120120
// RecoveryWith500Page returns a middleware that recovers from any panics and writes a 500 and a log if so.
121121
// This error will be created with the gitea 500 page.
122122
func RecoveryWith500Page(ctx goctx.Context) func(next http.Handler) http.Handler {
123-
_, rnd := templates.HTMLRenderer(ctx)
123+
rnd := templates.HTMLRenderer()
124124
return func(next http.Handler) http.Handler {
125125
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
126126
defer func() {

routers/web/web.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ func Routes(ctx gocontext.Context) *web.Route {
114114
routes.RouteMethods("/apple-touch-icon.png", "GET, HEAD", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
115115
routes.RouteMethods("/favicon.ico", "GET, HEAD", misc.StaticRedirect("/assets/img/favicon.png"))
116116

117-
ctx, _ = templates.HTMLRenderer(ctx)
117+
_ = templates.HTMLRenderer()
118+
118119
common := []any{
119120
common.Sessioner(),
120121
RecoveryWith500Page(ctx),

0 commit comments

Comments
 (0)