Content-Length: 974136 | pFad | http://github.com/revel/revel/commit/d58f86e700c2de64a8fdc97a58292998a7699b3e

64 Merge pull request #1225 from runner-mei/template-map-sync2 · revel/revel@d58f86e · GitHub
Skip to content

Commit d58f86e

Browse files
notzippyakensho
authored andcommitted
Merge pull request #1225 from runner-mei/template-map-sync2
Fix for the template map concurrency issue
1 parent 417d285 commit d58f86e

File tree

2 files changed

+161
-81
lines changed

2 files changed

+161
-81
lines changed

template.go

Lines changed: 156 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"regexp"
1616
"strconv"
1717
"strings"
18+
"sync"
19+
"sync/atomic"
1820
)
1921

2022
// ErrorCSSClass httml CSS error class name
@@ -23,16 +25,14 @@ var ErrorCSSClass = "hasError"
2325
// TemplateLoader object handles loading and parsing of templates.
2426
// Everything below the application's views directory is treated as a template.
2527
type TemplateLoader struct {
26-
// Template data and implementation
27-
templatesAndEngineList []TemplateEngine
28-
// If an error was encountered parsing the templates, it is stored here.
29-
compileError *Error
3028
// Paths to search for templates, in priority order.
3129
paths []string
32-
// Map from template name to the path from whence it was loaded.
33-
TemplatePaths map[string]string
34-
// A map of looked up template results
35-
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
34+
// Lock to prevent concurrent map writes
35+
templateMutex sync.Mutex
3636
}
3737

3838
type Template interface {
@@ -56,32 +56,61 @@ func NewTemplateLoader(paths []string) *TemplateLoader {
5656
return loader
5757
}
5858

59-
// Refresh method scans the views directory and parses all templates as Go Templates.
60-
// If a template fails to parse, the error is set on the loader.
61-
// (It's awkward to refresh a single Go Template)
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+
6284
// Refresh method scans the views directory and parses all templates as Go Templates.
6385
// If a template fails to parse, the error is set on the loader.
6486
// (It's awkward to refresh a single Go Template)
6587
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+
6696
TRACE.Printf("Refreshing templates from %s", loader.paths)
67-
if len(loader.templatesAndEngineList) == 0 {
68-
if err = loader.InitializeEngines(GO_TEMPLATE); err != nil {
69-
return
70-
}
97+
if err = loader.initializeEngines(runtimeLoader, GO_TEMPLATE); err != nil {
98+
return
7199
}
72-
for _, engine := range loader.templatesAndEngineList {
100+
for _, engine := range runtimeLoader.templatesAndEngineList {
73101
engine.Event(TEMPLATE_REFRESH_REQUESTED, nil)
74102
}
75103
fireEvent(TEMPLATE_REFRESH_REQUESTED, nil)
76104
defer func() {
77-
for _, engine := range loader.templatesAndEngineList {
105+
for _, engine := range runtimeLoader.templatesAndEngineList {
78106
engine.Event(TEMPLATE_REFRESH_COMPLETED, nil)
79107
}
80108
fireEvent(TEMPLATE_REFRESH_COMPLETED, nil)
81-
// Reset the TemplateMap, we don't prepopulate the map because
82-
loader.TemplateMap = map[string]Template{}
83109

110+
// Reset the runtimeLoader
111+
loader.runtimeLoader.Store(runtimeLoader)
84112
}()
113+
85114
// Resort the paths, make sure the revel path is the last path,
86115
// so anything can override it
87116
revelTemplatePath := filepath.Join(RevelPath, "templates")
@@ -94,8 +123,8 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
94123
}
95124
TRACE.Printf("Refreshing templates from %s", loader.paths)
96125

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

100129
for _, basePath := range loader.paths {
101130
// Walk only returns an error if the template loader is completely unusable
@@ -133,14 +162,14 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
133162
return nil
134163
}
135164

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

138167
// Store / report the first error encountered.
139-
if err != nil && loader.compileError == nil {
140-
loader.compileError, _ = err.(*Error)
141-
if nil == loader.compileError {
168+
if err != nil && runtimeLoader.compileError == nil {
169+
runtimeLoader.compileError, _ = err.(*Error)
170+
if nil == runtimeLoader.compileError {
142171
_, line, description := ParseTemplateError(err)
143-
loader.compileError = &Error{
172+
runtimeLoader.compileError = &Error{
144173
Title: "Template Compilation Error",
145174
Path: path,
146175
Description: description,
@@ -149,7 +178,7 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
149178
}
150179
}
151180
ERROR.Printf("Template compilation error (In %s around line %d):\n\t%s",
152-
path, loader.compileError.Line, err.Error())
181+
path, runtimeLoader.compileError.Line, err.Error())
153182
} else if nil != err { //&& strings.HasPrefix(templateName, "errors/") {
154183

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

174203
// If there was an error with the Funcs, set it and return immediately.
175204
if funcErr != nil {
176-
loader.compileError = funcErr.(*Error)
177-
return loader.compileError
205+
runtimeLoader.compileError = funcErr.(*Error)
206+
return runtimeLoader.compileError
178207
}
179208
}
180209

181210
// Note: compileError may or may not be set.
182-
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
183226
}
184227

185228
// Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order
186229
// reads the template file into memory, replaces namespace keys with module (if found
187-
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) {
188231
templateName := filepath.ToSlash(path[len(fullSrcDir)+1:])
189232
// Convert template names to use forward slashes, even on Windows.
190233
if os.PathSeparator == '\\' {
191234
templateName = strings.Replace(templateName, `\`, `/`, -1) // `
192235
}
193236

194237
// Check to see if template was found
195-
if place, found := loader.TemplatePaths[templateName]; found {
238+
if place, found := runtimeLoader.TemplatePaths[templateName]; found {
196239
TRACE.Println("Not Loading, template is already exists: ", templateName, "\r\n\told file:",
197240
place, "\r\n\tnew file:", path)
198241
return
@@ -205,25 +248,25 @@ func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath stri
205248
}
206249
// Parse template file and replace the "_RNS_|" in the template with the module name
207250
// allow for namespaces to be renamed "_RNS_(.*?)|"
208-
if module := ModuleFromPath(path, false);module != nil {
251+
if module := ModuleFromPath(path, false); module != nil {
209252
fileBytes = namespaceReplace(fileBytes, module)
210253
}
211254

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

215258
// Try to find a default engine for the file
216-
for _, engine := range loader.templatesAndEngineList {
259+
for _, engine := range runtimeLoader.templatesAndEngineList {
217260
if engine.Handles(baseTemplate) {
218-
_, err = loader.loadIntoEngine(engine, baseTemplate)
261+
_, err = runtimeLoader.loadIntoEngine(engine, baseTemplate)
219262
return
220263
}
221264
}
222265

223266
// Try all engines available
224267
var defaultError error
225-
for _, engine := range loader.templatesAndEngineList {
226-
if loaded, loaderr := loader.loadIntoEngine(engine, baseTemplate); loaded {
268+
for _, engine := range runtimeLoader.templatesAndEngineList {
269+
if loaded, loaderr := runtimeLoader.loadIntoEngine(engine, baseTemplate); loaded {
227270
return
228271
} else {
229272
TRACE.Printf("Engine '%s' unable to compile %s %s", engine.Name(), path, loaderr)
@@ -244,7 +287,14 @@ func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath stri
244287
return
245288
}
246289

247-
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+
248298
if loadedTemplate := engine.Lookup(baseTemplate.TemplateName); loadedTemplate != nil {
249299
// Duplicate template found for engine
250300
TRACE.Println("template already exists: ", baseTemplate.TemplateName, " in engine ", engine.Name(), "\r\n\told file:",
@@ -253,7 +303,10 @@ func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate
253303
return
254304
}
255305
if err = engine.ParseAndAdd(baseTemplate); err == nil {
256-
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
257310
TRACE.Printf("Engine '%s' compiled %s", engine.Name(), baseTemplate.FilePath)
258311
loaded = true
259312
} else {
@@ -262,20 +315,6 @@ func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate
262315
return
263316
}
264317

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

303-
// DEPRECATED Use TemplateLang, will be removed in future release
304-
func (loader *TemplateLoader) Template(name string) (tmpl Template, err error) {
305-
return loader.TemplateLang(name, "")
306-
}
307342
// Template returns the Template with the given name. The name is the template's path
308343
// relative to a template loader root.
309344
//
310345
// An Error is returned if there was any problem with any of the templates. (In
311346
// this case, if a template is returned, it may still be usable.)
312-
func (loader *TemplateLoader) TemplateLang(name, lang string) (tmpl Template, err error) {
313-
if loader.compileError != nil {
314-
return nil, loader.compileError
315-
}
316-
// Attempt to load a localized template first.
317-
if lang != "" {
318-
// Look up and return the template.
319-
tmpl = loader.templateLoad(name + "." + lang)
320-
}
321-
// Return non localized version
322-
if tmpl == nil {
323-
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
324350
}
325351

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

330358
return
331359
}
332-
func (loader *TemplateLoader) templateLoad(name string) (tmpl Template) {
333-
if t,found := loader.TemplateMap[name];!found && t != nil {
334-
tmpl = t
360+
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]
335373
} else {
374+
tmpl, found = runtimeLoader.templateMap[name]
375+
if found {
376+
return
377+
}
378+
}
379+
380+
if !found {
381+
// Neither name is found
336382
// Look up and return the template.
337-
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+
}
338388
if tmpl = engine.Lookup(name); tmpl != nil {
339-
loader.TemplateMap[name] = tmpl
389+
found = true
340390
break
341391
}
342392
}
393+
if !found {
394+
return
395+
}
396+
}
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
343416
}
417+
runtimeCopy := &templateRuntime{}
418+
*runtimeCopy = *newRuntimeLoader
419+
runtimeCopy.templateMap = newTemplateMap
420+
421+
// Set the atomic value
422+
runtimeLoader.loader.runtimeLoader.Store(runtimeCopy)
344423
return
345424
}
346425

347426
func (i *TemplateView) Location() string {
348427
return i.FilePath
349428
}
429+
350430
func (i *TemplateView) Content() (content []string) {
351431
if i.FileBytes != nil {
352432
// Parse the bytes
@@ -358,7 +438,7 @@ func (i *TemplateView) Content() (content []string) {
358438
}
359439
return nil
360440
}
441+
361442
func NewBaseTemplate(templateName, filePath, basePath string, fileBytes []byte) *TemplateView {
362443
return &TemplateView{TemplateName: templateName, FilePath: filePath, FileBytes: fileBytes, BasePath: basePath}
363444
}
364-

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/revel/revel/commit/d58f86e700c2de64a8fdc97a58292998a7699b3e

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy