Ok, for some simplified setup, in this example we have three structs comp, agg and cache that look somewhat like this:
type comp struct {
id uint64
val float64
}
type agg struct {
id uint64
vals []*comp
}
type cache struct {
compMtx sync.RWMutex
comps map[uint64]*comp
aggMtx sync.RWMutex
aggs map[uint64]*agg
}
Where the cache has the following function to add new comp values, which in the case of the update does not seem to work:
func (c *cache) NewComp(cpNew *comp) {
compMtx.Lock()
defer compMtx.Unlock()
cpOld, ok := c.comps[cpNew.id]
if ok { // update
addr := &cpOld // this is of type **comp
*addr = cpNew
} else { // new value
c.comps[cpNew.id] = cpNew
}
}
The thought behind this method is that by changing where the pointer of the pointer points to, we can ensure that the comp pointers in agg.vals always point to the newest iteration of a given comp object.
As for the reason behind this approach, iterating over the entirety of an agg.vals array to find the index of the given comp object would a) be computationally expensive due to the (rather large) size of the array and would b) require agg to be locked via an internal sync.Mutex during the search to block different threads from accessing the object, both of which are undesirable. Furthermore assume that making agg.value into a map so as to facilitate easy updates is not a possibility.
Since NewComp as implemented above does however not work, my question would be whether there are any obvious mistakes in the function above, or if I made some fundamental error in my thinking here?
As this might be helpful, here also an example in which the update via pointers of pointers works as expected:
type wrks struct {
id uint64
val1 *comp
val2 *comp
}
func compute() *comp {...}
func updateComp(c **comp) {
*c = compute()
}
func processObj(o *obj) {
updateComp(&o.val1)
updateComp(&o.val2)
}
I fail to see the fundamental difference between the two, but I may have been staring at this for too long at this point.