Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e3e25bd

Browse filesBrowse files
committed
Fix for the template map concurrency issue
1 parent a6d8a1f commit e3e25bd
Copy full SHA for e3e25bd

File tree

2 files changed

+157
-81
lines changed
Filter options

2 files changed

+157
-81
lines changed

‎template.go

Copy file name to clipboardExpand all lines: template.go
+152-76Lines changed: 152 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"strconv"
1717
"strings"
1818
"sync"
19+
"sync/atomic"
1920
)
2021

2122
// ErrorCSSClass httml CSS error class name
@@ -24,16 +25,12 @@ var ErrorCSSClass = "hasError"
2425
// TemplateLoader object handles loading and parsing of templates.
2526
// Everything below the application's views directory is treated as a template.
2627
type TemplateLoader struct {
27-
// Template data and implementation
28-
templatesAndEngineList []TemplateEngine
29-
// If an error was encountered parsing the templates, it is stored here.
30-
compileError *Error
3128
// Paths to search for templates, in priority order.
3229
paths []string
33-
// Map from template name to the path from whence it was loaded.
34-
TemplatePaths map[string]string
35-
// A map of looked up template results
36-
TemplateMap map[string]Template
30+
// load version seed for templates
31+
loadVersionSeed int
32+
// A templateRuntime of looked up template results
33+
runtimeLoader atomic.Value
3734
// Lock to prevent concurrent map writes
3835
templateMutex sync.Mutex
3936
}
@@ -55,34 +52,65 @@ var whiteSpacePattern = regexp.MustCompile(`\s+`)
5552
func NewTemplateLoader(paths []string) *TemplateLoader {
5653
loader := &TemplateLoader{
5754
paths: paths,
58-
templateMutex: sync.Mutex{},
5955
}
6056
return loader
6157
}
6258

59+
// WatchDir returns true of directory doesn't start with . (dot)
60+
// otherwise false
61+
func (loader *TemplateLoader) WatchDir(info os.FileInfo) bool {
62+
// Watch all directories, except the ones starting with a dot.
63+
return !strings.HasPrefix(info.Name(), ".")
64+
}
65+
66+
// WatchFile returns true of file doesn't start with . (dot)
67+
// otherwise false
68+
func (loader *TemplateLoader) WatchFile(basename string) bool {
69+
// Watch all files, except the ones starting with a dot.
70+
return !strings.HasPrefix(basename, ".")
71+
}
72+
73+
// DEPRECATED Use TemplateLang, will be removed in future release
74+
func (loader *TemplateLoader) Template(name string) (tmpl Template, err error) {
75+
runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime)
76+
return runtimeLoader.TemplateLang(name, "")
77+
}
78+
79+
func (loader *TemplateLoader) TemplateLang(name, lang string) (tmpl Template, err error) {
80+
runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime)
81+
return runtimeLoader.TemplateLang(name, lang)
82+
}
83+
6384
// Refresh method scans the views directory and parses all templates as Go Templates.
6485
// If a template fails to parse, the error is set on the loader.
6586
// (It's awkward to refresh a single Go Template)
6687
func (loader *TemplateLoader) Refresh() (err *Error) {
88+
loader.templateMutex.Lock()
89+
defer loader.templateMutex.Unlock()
90+
91+
loader.loadVersionSeed++
92+
runtimeLoader := &templateRuntime{loader: loader,
93+
version: loader.loadVersionSeed,
94+
templateMap: map[string]Template{}}
95+
6796
TRACE.Printf("Refreshing templates from %s", loader.paths)
68-
if len(loader.templatesAndEngineList) == 0 {
69-
if err = loader.InitializeEngines(GO_TEMPLATE); err != nil {
70-
return
71-
}
97+
if err = loader.initializeEngines(runtimeLoader, GO_TEMPLATE); err != nil {
98+
return
7299
}
73-
for _, engine := range loader.templatesAndEngineList {
100+
for _, engine := range runtimeLoader.templatesAndEngineList {
74101
engine.Event(TEMPLATE_REFRESH_REQUESTED, nil)
75102
}
76103
fireEvent(TEMPLATE_REFRESH_REQUESTED, nil)
77104
defer func() {
78-
for _, engine := range loader.templatesAndEngineList {
105+
for _, engine := range runtimeLoader.templatesAndEngineList {
79106
engine.Event(TEMPLATE_REFRESH_COMPLETED, nil)
80107
}
81108
fireEvent(TEMPLATE_REFRESH_COMPLETED, nil)
82-
// Reset the TemplateMap, we don't prepopulate the map because
83-
loader.TemplateMap = map[string]Template{}
84109

110+
// Reset the runtimeLoader
111+
loader.runtimeLoader.Store(runtimeLoader)
85112
}()
113+
86114
// Resort the paths, make sure the revel path is the last path,
87115
// so anything can override it
88116
revelTemplatePath := filepath.Join(RevelPath, "templates")
@@ -95,8 +123,8 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
95123
}
96124
TRACE.Printf("Refreshing templates from %s", loader.paths)
97125

98-
loader.compileError = nil
99-
loader.TemplatePaths = map[string]string{}
126+
runtimeLoader.compileError = nil
127+
runtimeLoader.TemplatePaths = map[string]string{}
100128

101129
for _, basePath := range loader.paths {
102130
// Walk only returns an error if the template loader is completely unusable
@@ -134,14 +162,14 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
134162
return nil
135163
}
136164

137-
fileBytes, err := loader.findAndAddTemplate(path, fullSrcDir, basePath)
165+
fileBytes, err := runtimeLoader.findAndAddTemplate(path, fullSrcDir, basePath)
138166

139167
// Store / report the first error encountered.
140-
if err != nil && loader.compileError == nil {
141-
loader.compileError, _ = err.(*Error)
142-
if nil == loader.compileError {
168+
if err != nil && runtimeLoader.compileError == nil {
169+
runtimeLoader.compileError, _ = err.(*Error)
170+
if nil == runtimeLoader.compileError {
143171
_, line, description := ParseTemplateError(err)
144-
loader.compileError = &Error{
172+
runtimeLoader.compileError = &Error{
145173
Title: "Template Compilation Error",
146174
Path: path,
147175
Description: description,
@@ -150,7 +178,7 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
150178
}
151179
}
152180
ERROR.Printf("Template compilation error (In %s around line %d):\n\t%s",
153-
path, loader.compileError.Line, err.Error())
181+
path, runtimeLoader.compileError.Line, err.Error())
154182
} else if nil != err { //&& strings.HasPrefix(templateName, "errors/") {
155183

156184
if compileError, ok := err.(*Error); ok {
@@ -174,26 +202,40 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
174202

175203
// If there was an error with the Funcs, set it and return immediately.
176204
if funcErr != nil {
177-
loader.compileError = funcErr.(*Error)
178-
return loader.compileError
205+
runtimeLoader.compileError = funcErr.(*Error)
206+
return runtimeLoader.compileError
179207
}
180208
}
181209

182210
// Note: compileError may or may not be set.
183-
return loader.compileError
211+
return runtimeLoader.compileError
212+
}
213+
214+
type templateRuntime struct {
215+
loader *TemplateLoader
216+
// load version for templates
217+
version int
218+
// Template data and implementation
219+
templatesAndEngineList []TemplateEngine
220+
// If an error was encountered parsing the templates, it is stored here.
221+
compileError *Error
222+
// Map from template name to the path from whence it was loaded.
223+
TemplatePaths map[string]string
224+
// A map of looked up template results
225+
templateMap map[string]Template
184226
}
185227

186228
// Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order
187229
// reads the template file into memory, replaces namespace keys with module (if found
188-
func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath string) (fileBytes []byte, err error) {
230+
func (runtimeLoader *templateRuntime) findAndAddTemplate(path, fullSrcDir, basePath string) (fileBytes []byte, err error) {
189231
templateName := filepath.ToSlash(path[len(fullSrcDir)+1:])
190232
// Convert template names to use forward slashes, even on Windows.
191233
if os.PathSeparator == '\\' {
192234
templateName = strings.Replace(templateName, `\`, `/`, -1) // `
193235
}
194236

195237
// Check to see if template was found
196-
if place, found := loader.TemplatePaths[templateName]; found {
238+
if place, found := runtimeLoader.TemplatePaths[templateName]; found {
197239
TRACE.Println("Not Loading, template is already exists: ", templateName, "\r\n\told file:",
198240
place, "\r\n\tnew file:", path)
199241
return
@@ -206,25 +248,25 @@ func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath stri
206248
}
207249
// Parse template file and replace the "_RNS_|" in the template with the module name
208250
// allow for namespaces to be renamed "_RNS_(.*?)|"
209-
if module := ModuleFromPath(path, false);module != nil {
251+
if module := ModuleFromPath(path, false); module != nil {
210252
fileBytes = namespaceReplace(fileBytes, module)
211253
}
212254

213255
// if we have an engine picked for this template process it now
214256
baseTemplate := NewBaseTemplate(templateName, path, basePath, fileBytes)
215257

216258
// Try to find a default engine for the file
217-
for _, engine := range loader.templatesAndEngineList {
259+
for _, engine := range runtimeLoader.templatesAndEngineList {
218260
if engine.Handles(baseTemplate) {
219-
_, err = loader.loadIntoEngine(engine, baseTemplate)
261+
_, err = runtimeLoader.loadIntoEngine(engine, baseTemplate)
220262
return
221263
}
222264
}
223265

224266
// Try all engines available
225267
var defaultError error
226-
for _, engine := range loader.templatesAndEngineList {
227-
if loaded, loaderr := loader.loadIntoEngine(engine, baseTemplate); loaded {
268+
for _, engine := range runtimeLoader.templatesAndEngineList {
269+
if loaded, loaderr := runtimeLoader.loadIntoEngine(engine, baseTemplate); loaded {
228270
return
229271
} else {
230272
TRACE.Printf("Engine '%s' unable to compile %s %s", engine.Name(), path, loaderr)
@@ -245,7 +287,14 @@ func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath stri
245287
return
246288
}
247289

248-
func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate *TemplateView) (loaded bool, err error) {
290+
func (runtimeLoader *templateRuntime) loadIntoEngine(engine TemplateEngine, baseTemplate *TemplateView) (loaded bool, err error) {
291+
if loadedTemplate, found := runtimeLoader.templateMap[baseTemplate.TemplateName]; found {
292+
// Duplicate template found in map
293+
TRACE.Println("template already exists in map: ", baseTemplate.TemplateName, " in engine ", engine.Name(), "\r\n\told file:",
294+
loadedTemplate.Location(), "\r\n\tnew file:", baseTemplate.FilePath)
295+
return
296+
}
297+
249298
if loadedTemplate := engine.Lookup(baseTemplate.TemplateName); loadedTemplate != nil {
250299
// Duplicate template found for engine
251300
TRACE.Println("template already exists: ", baseTemplate.TemplateName, " in engine ", engine.Name(), "\r\n\told file:",
@@ -254,7 +303,10 @@ func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate
254303
return
255304
}
256305
if err = engine.ParseAndAdd(baseTemplate); err == nil {
257-
loader.TemplatePaths[baseTemplate.TemplateName] = baseTemplate.FilePath
306+
if tmpl := engine.Lookup(baseTemplate.TemplateName); tmpl != nil {
307+
runtimeLoader.templateMap[baseTemplate.TemplateName] = tmpl
308+
}
309+
runtimeLoader.TemplatePaths[baseTemplate.TemplateName] = baseTemplate.FilePath
258310
TRACE.Printf("Engine '%s' compiled %s", engine.Name(), baseTemplate.FilePath)
259311
loaded = true
260312
} else {
@@ -263,20 +315,6 @@ func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate
263315
return
264316
}
265317

266-
// WatchDir returns true of directory doesn't start with . (dot)
267-
// otherwise false
268-
func (loader *TemplateLoader) WatchDir(info os.FileInfo) bool {
269-
// Watch all directories, except the ones starting with a dot.
270-
return !strings.HasPrefix(info.Name(), ".")
271-
}
272-
273-
// WatchFile returns true of file doesn't start with . (dot)
274-
// otherwise false
275-
func (loader *TemplateLoader) WatchFile(basename string) bool {
276-
// Watch all files, except the ones starting with a dot.
277-
return !strings.HasPrefix(basename, ".")
278-
}
279-
280318
// Parse the line, and description from an error message like:
281319
// html/template:Application/Register.html:36: no such template "footer.html"
282320
func ParseTemplateError(err error) (templateName string, line int, description string) {
@@ -301,56 +339,94 @@ func ParseTemplateError(err error) (templateName string, line int, description s
301339
return templateName, line, description
302340
}
303341

304-
// DEPRECATED Use TemplateLang, will be removed in future release
305-
func (loader *TemplateLoader) Template(name string) (tmpl Template, err error) {
306-
return loader.TemplateLang(name, "")
307-
}
308342
// Template returns the Template with the given name. The name is the template's path
309343
// relative to a template loader root.
310344
//
311345
// An Error is returned if there was any problem with any of the templates. (In
312346
// this case, if a template is returned, it may still be usable.)
313-
func (loader *TemplateLoader) TemplateLang(name, lang string) (tmpl Template, err error) {
314-
if loader.compileError != nil {
315-
return nil, loader.compileError
316-
}
317-
// Attempt to load a localized template first.
318-
if lang != "" {
319-
// Look up and return the template.
320-
tmpl = loader.templateLoad(name + "." + lang)
321-
}
322-
// Return non localized version
323-
if tmpl == nil {
324-
tmpl = loader.templateLoad(name)
347+
func (runtimeLoader *templateRuntime) TemplateLang(name, lang string) (tmpl Template, err error) {
348+
if runtimeLoader.compileError != nil {
349+
return nil, runtimeLoader.compileError
325350
}
326351

327-
if tmpl == nil && err == nil {
352+
// Fetch the template from the map
353+
tmpl = runtimeLoader.templateLoad(name, lang)
354+
if tmpl == nil {
328355
err = fmt.Errorf("Template %s not found.", name)
329356
}
330357

331358
return
332359
}
333360

334-
func (loader *TemplateLoader) templateLoad(name string) (tmpl Template) {
335-
if t,found := loader.TemplateMap[name];!found && t != nil {
336-
tmpl = t
361+
// Load and also updates map if name is not found (to speed up next lookup)
362+
func (runtimeLoader *templateRuntime) templateLoad(name, lang string) (tmpl Template) {
363+
langName := name
364+
found := false
365+
if lang != "" {
366+
// Look up and return the template.
367+
langName = name + "." + lang
368+
tmpl, found = runtimeLoader.templateMap[langName]
369+
if found {
370+
return
371+
}
372+
tmpl, found = runtimeLoader.templateMap[name]
337373
} else {
374+
tmpl, found = runtimeLoader.templateMap[name]
375+
if found {
376+
return
377+
}
378+
}
379+
380+
if !found {
381+
// Neither name is found
338382
// Look up and return the template.
339-
for _, engine := range loader.templatesAndEngineList {
383+
for _, engine := range runtimeLoader.templatesAndEngineList {
384+
if tmpl = engine.Lookup(langName); tmpl != nil {
385+
found = true
386+
break
387+
}
340388
if tmpl = engine.Lookup(name); tmpl != nil {
341-
loader.templateMutex.Lock()
342-
defer loader.templateMutex.Unlock()
343-
loader.TemplateMap[name] = tmpl
389+
found = true
344390
break
345391
}
346392
}
393+
if !found {
394+
return
395+
}
347396
}
397+
398+
// If we found anything store it in the map, we need to copy so we do not
399+
// run into concurrency issues
400+
runtimeLoader.loader.templateMutex.Lock()
401+
defer runtimeLoader.loader.templateMutex.Unlock()
402+
403+
// In case another thread has loaded the map, reload the atomic value and check
404+
newRuntimeLoader := runtimeLoader.loader.runtimeLoader.Load().(*templateRuntime)
405+
if newRuntimeLoader.version != runtimeLoader.version {
406+
return
407+
}
408+
409+
newTemplateMap := map[string]Template{}
410+
for k, v := range newRuntimeLoader.templateMap {
411+
newTemplateMap[k] = v
412+
}
413+
newTemplateMap[langName] = tmpl
414+
if _, found := newTemplateMap[name]; !found {
415+
newTemplateMap[name] = tmpl
416+
}
417+
runtimeCopy := &templateRuntime{}
418+
*runtimeCopy = *newRuntimeLoader
419+
runtimeCopy.templateMap = newTemplateMap
420+
421+
// Set the atomic value
422+
runtimeLoader.loader.runtimeLoader.Store(runtimeCopy)
348423
return
349424
}
350425

351426
func (i *TemplateView) Location() string {
352427
return i.FilePath
353428
}
429+
354430
func (i *TemplateView) Content() (content []string) {
355431
if i.FileBytes != nil {
356432
// Parse the bytes
@@ -362,7 +438,7 @@ func (i *TemplateView) Content() (content []string) {
362438
}
363439
return nil
364440
}
441+
365442
func NewBaseTemplate(templateName, filePath, basePath string, fileBytes []byte) *TemplateView {
366443
return &TemplateView{TemplateName: templateName, FilePath: filePath, FileBytes: fileBytes, BasePath: basePath}
367444
}
368-

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.