I have a struct with many fields (some of them are pointers to other structs as well) which are being continuosly updated in a separate goroutine. The same struct is accessed from go's http
template when a page is served.
Code example:
type SharedStruct struct {
Description string
Counter int
Status_ *Status
LastChecked time.Time
//other fields
}
var shared = &SharedStruct{}
go func() {
//..updates fields every 5 minutes
}()
go-http handler:
func someHandler(w http.ResponseWriter, r *http.Request) {
t.ExecuteTemplate(w, "page.html", shared)
}
and page.html
template:
...
Status: {{.Status_.StatusCode}}
Counter: {{.Counter}}
Last checked: {{.LastChecked.Format "2006-02-01 15:04:05"}}
So far everything works as expected, but I'm aware that bad things can happen without any synchronization. What is the preferred way to handle this properly?
The preferred way is the same as in any other cases.
Either use a mutex when the shared struct is read / updated:
var shared = &SharedStruct{}
var mux = &sync.RWMutex{}
func someHandler(w http.ResponseWriter, r *http.Request) {
mux.RLock()
defer mux.RUnlock()
t.ExecuteTemplate(w, "page.html", shared)
}
// Code that modifies shared:
mux.Lock()
shared.Counter++
mux.Unlock()
Or if the template execution takes long time, it may be protitable to make a copy of the shared
struct and pass the copy when executing the template, so that during template execution access to shared
is not blocked. Note that while making the copy you still have to use a mutex. Also if not only the pointers but the pointed values may change, you also have to make a copy of those:
func someHandler(w http.ResponseWriter, r *http.Request) {
mux.RLock()
shared2 := &SharedStruct{}
*shared2 = *shared
shared2.Status_ = new(Status)
*shared2.Status_ = *shared.Status_
mux.RUnlock()
t.ExecuteTemplate(w, "page.html", shared2)
}
If the template only uses a small subset of the shared
fields, it is enough to make only a copy of those of course.