How to upsert into concurrent map while iterating over regular map? - dictionary

I need to have a map of string as key and unique (no dupes) int64 array as value so I decided to use something like below so that value can act as a set.
var customerCatalog = make(map[string]map[int64]bool)
Above map is populated with some data in it. Now I am trying to populate my concurrent map in golang by reading above regular customerCatalog map but I am getting error:
for k, v := range customerCatalog {
r.customerCatalog.Upsert(k, v, func(exists bool, valueInMap interface{}, newValue interface{}) interface{} {
typedNewValue := newValue.([]int64)
if !exists {
return typedNewValue
}
typedValueInMap := valueInMap.([]int64)
return append(typedValueInMap, typedNewValue...)
})
}
This is the error I am getting. I am using upsert method as shown here
panic: interface conversion: interface {} is map[int64]bool, not []int64
What is wrong I am doing?

I believe a minimal, reproducible, example of your issue would be as follows (playground):
conMap := cmap.New()
v := map[int64]bool{}
updateItemFn := func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
_ = newValue.([]int64)
return nil
}
conMap.Upsert("foo", v, updateItemFn)
Note: I have stripped out the loop etc because that is irrelevant to the panic. However you should note that because the loop iterates over a map[string]map[int64]boolthe type of v will be map[int64]bool.
The Upsert function looks up the key in the map and then passes it and the value you passed in to the function.
So your function is receiving a map[int64]bool and the first thing it does is to assert that this is a []int64 (which will fail because it's not). To fix this you need to convert the map[int64]bool into a []int64. This could be done before calling the Upsert or within your implementation of UpsertCb as shown here (playground):
conMap := cmap.New()
conMap.Set("foo", []int64{5, 6})
v := map[int64]bool{
1: true,
}
updateItemFn := func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
m := newValue.(map[int64]bool)
a := make([]int64, 0, len(m))
for k := range m {
a = append(a, k)
}
if valueInMap == nil { // New value!
return a
} else {
typedValueInMap := valueInMap.([]int64)
return append(typedValueInMap, a...)
}
return a
}
conMap.Upsert("foo", v, updateItemFn)
fmt.Println(conMap.Get("foo"))
The above has been kept simple to demonstrate the point; in reality you may want to add all of the values into a map so as to avoid duplicates.

Related

Race condition while writing and reading from the map

Following up on old post here.
I am iterating over flatProduct.Catalogs slice and populating my productCatalog concurrent map in golang. I am using upsert method so that I can add only unique productID's into my productCatalog map.
Below code is called by multiple go routines in parallel that is why I am using concurrent map here to populate data into it. This code runs in background to populate data in the concurrent map every 30 seconds.
var productRows []ClientProduct
err = json.Unmarshal(byteSlice, &productRows)
if err != nil {
return err
}
for i := range productRows {
flatProduct, err := r.Convert(spn, productRows[i])
if err != nil {
return err
}
if flatProduct.StatusCode == definitions.DONE {
continue
}
r.products.Set(strconv.Itoa(flatProduct.ProductId, 10), flatProduct)
for _, catalogId := range flatProduct.Catalogs {
catalogValue := strconv.FormatInt(int64(catalogId), 10)
r.productCatalog.Upsert(catalogValue, flatProduct.ProductId, func(exists bool, valueInMap interface{}, newValue interface{}) interface{} {
productID := newValue.(int64)
if valueInMap == nil {
return map[int64]struct{}{productID: {}}
}
oldIDs := valueInMap.(map[int64]struct{})
// value is irrelevant, no need to check if key exists
// I think problem is here
oldIDs[productID] = struct{}{}
return oldIDs
})
}
}
And below are my getters in the same class where above code is there. These getters are used by main application threads to get data from the map or get the whole map.
func (r *clientRepository) GetProductMap() *cmap.ConcurrentMap {
return r.products
}
func (r *clientRepository) GetProductCatalogMap() *cmap.ConcurrentMap {
return r.productCatalog
}
func (r *clientRepository) GetProductData(pid string) *definitions.FlatProduct {
pd, ok := r.products.Get(pid)
if ok {
return pd.(*definitions.FlatProduct)
}
return nil
}
This is how I am reading data from this productCatalog cmap but my system is crashing on the below range statement -
// get productCatalog map which was populated above
catalogProductMap := clientRepo.GetProductCatalogMap()
productIds, ok := catalogProductMap.Get("211")
data, _ := productIds.(map[int64]struct{})
// I get panic here after sometime
for _, pid := range data {
...
}
Error I am getting as - fatal error: concurrent map iteration and map write.
I think issue is r.productCatalog is a concurrentmap, but oldIDs[productID] is a normal map which is causing issues while I am iterating in the for loop above.
How can I fix this race issue I am seeing? One way I can think of is making oldIDs[productID] as concurrent map but if I do that approach then my memory increase by a lot and eventually goes OOM. Below is what I have tried which works and it solves the race condition but it increases the memory by a lot which is not what I want -
r.productCatalog.Upsert(catalogValue, flatProduct.ProductId, func(exists bool, valueInMap interface{}, newValue interface{}) interface{} {
productID := newValue.(int64)
if valueInMap == nil {
// return map[int64]struct{}{productID: {}}
return cmap.New()
}
// oldIDs := valueInMap.(map[int64]struct{})
oldIDs := valueInMap.(cmap.ConcurrentMap)
// value is irrelevant, no need to check if key exists
// oldIDs[productID] = struct{}{}
oldIDs.Set(strconv.FormatInt(productID, 10), struct{}{})
return oldIDs
})
Any other approach I can do which doesn't increase memory and also fixes the race condition I am seeing?
Note
I am still using v1 version of cmap without generics and it deals with strings as keys.
Rather than a plain map[int64]struct{} type, you could define a struct which holds the map and a mutex to control the access to the map:
type myMap struct{
m sync.Mutex
data map[int64]struct{}
}
func (m *myMap) Add(productID int64) {
m.m.Lock()
defer m.m.Unlock()
m.data[productID] = struct{}{}
}
func (m *myMap) List() []int64 {
m.m.Lock()
defer m.m.Unlock()
var res []int64
for id := range m.data {
res = append(res, id)
}
// sort slice if you need
return res
}
With the sample implementation above, you would have to be careful to store *myMap pointers (as opposed to plain myMap structs) in your cmap.ConcurrentMap structure.

Initializing Slice of type Struct in Golang

I have the following structure in Golang
type mystruct struct {
Name string
Power int
}
My purpose is to write a function that takes as input a slice of type *mystuct and returns a slice of type int containing the "Power" property taken from the input slice.
my code is presented below:
package main
import (
"fmt"
)
func main() {
obj := make([]*mystruct, 15)
for i, s := range obj {
s.Power = i
}
fmt.Println(extractpowers(obj))
}
func extractpowers(obj []*mystruct) []int {
powers := make([]int, len(obj))
for i, s := range obj {
powers[i] = s.Power
}
return powers
}
My issue is that the obj := make([]*mystruct, 15) creates a slices of 15 *mystruc pointers initialized to nil; which causes the code within the for loop to raise a panic of type "invalid memory or nil pointer dereference".
My question is what is the proper and fastest way to initialize the slice; (the equivalent of var lst = new List(Of mystruct) in .net)
Regards.
Use a composite literal and take its address in the loop:
for i := range obj {
obj[i] = &mystruct{Power: i}
}
Try it on the Go Playground.

How to set an interface{} parameter by reference?

I have a function that has a parameter with the type interface{}. This parameter represents my template data. So on each page it stores different data types (mostly structs). I want to append some data to this parameter's data, but it's an interface{} type and I can't do it.
This is what I tried:
func LoadTemplate(templateData interface) {
appendCustomData(&templateData)
... //other functionality that is not relevant
}
func appendCustomData(dst interface{}) {
// ValueOf to enter reflect-land
dstPtrValue := reflect.ValueOf(dst)
// need the type to create a value
dstPtrType := dstPtrValue.Type()
// *T -> T, crashes if not a ptr
dstType := dstPtrType.Elem()
// the *dst in *dst = zero
dstValue := reflect.Indirect(dstPtrValue)
// the zero in *dst = zero
zeroValue := reflect.Zero(dstType)
// the = in *dst = 0
v := reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS")
if v.IsValid() {
v = reflect.ValueOf("new header css value")
}
reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS").Set(reflect.ValueOf(v))
//dstValue.Set(zeroValue)
fmt.Println("new dstValue: ", dstValue)
}
I can successfully get the "HeaderCSS" value. But I can't replace it with another value. What am I doing wrong?
My templateData looks like this:
I have a generic struct:
type TemplateData struct {
FooterJS template.HTML
HeaderJS template.HTML
HeaderCSS template.HTML
//and some more
}
and I have another struct, such as:
type pageStruct struct {
TemplateData //extends the previous struct
Form template.HTML
// and some other maps/string
}
I send this second struct as templateData argument.
Right now I get this error:
"reflect.Value.Set using unaddressable value" at the line: reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS").Set(reflect.ValueOf(v))
The code from above is inspired from this answer: https://stackoverflow.com/a/26824071/1564840
I want to be able to append/edit values from this interface. Any idea how can I do it? Thanks.
Don't pass a pointer to interface. Instead the interface{} value should contain the pointer. And simply just hand over this interface{} value:
func LoadTemplate(templateData interface) {
appendCustomData(templateData)
... //other functionality that is not relevant
}
Even if you can't use a more concrete type than interface{} (because you must allow multiple types), you can still use type assertion, it will be "super" easy:
func appendCustomData(d interface{}) {
if ps, ok := d.(*pageStruct); ok {
ps.TemplateData.HeaderCSS += "+new"
}
}
Try this one on the Go Playground.
If you must or want to use reflection, this is how appendCustomData() can be implemented:
type Data struct {
Name string
Age int
Marks []int
}
func appendCustomData(d interface{}) {
v := reflect.ValueOf(d).Elem()
if f := v.FieldByName("Name"); f.IsValid() {
f.SetString(f.Interface().(string) + "2")
}
if f := v.FieldByName("Age"); f.IsValid() {
f.SetInt(f.Int() + 2)
}
if f := v.FieldByName("Marks"); f.IsValid() {
f.Set(reflect.ValueOf(append(f.Interface().([]int), 2)))
}
if f := v.FieldByName("Invalid"); f.IsValid() {
f.Set(reflect.ValueOf(append(f.Interface().([]int), 2)))
}
}
Testing it:
d := &Data{
Name: "Bob",
Age: 22,
Marks: []int{5, 4, 3},
}
fmt.Printf("%+v\n", d)
appendCustomData(d)
fmt.Printf("%+v\n", d)
Output (try it on the Go Playground):
&{Name:Bob Age:22 Marks:[5 4 3]}
&{Name:Bob2 Age:24 Marks:[5 4 3 2]}
Update:
To answer your edited question: there is no difference when the value passed is a struct that embeds another struct. But the value wrapped in the interface{} still must be a pointer.
Example appendCustomData() that appends content to pageStruct.TemplateData.HeaderCSS:
func appendCustomData(d interface{}) {
v := reflect.ValueOf(d).Elem()
if f := v.FieldByName("TemplateData"); f.IsValid() {
if f = f.FieldByName("HeaderCSS"); f.IsValid() {
f.Set(reflect.ValueOf(f.Interface().(template.HTML) + "+new"))
}
}
}
Testing it:
ps := &pageStruct{
TemplateData: TemplateData{
HeaderCSS: template.HTML("old"),
},
}
fmt.Printf("%+v\n", ps)
appendCustomData(ps)
fmt.Printf("%+v\n", ps)
Output (try it on the Go Playground):
&{TemplateData:{FooterJS: HeaderJS: HeaderCSS:old} Form:}
&{TemplateData:{FooterJS: HeaderJS: HeaderCSS:old+new} Form:}

Passing values to interface{}

Short
The following code does not exactly do what expected:
https://play.golang.org/p/sO4w4I_Lle
I assume that I mess up some pointer/reference stuff as usual, however I expect my...
func unmarshalJSON(in []byte, s interface{}) error
... and encoding/jsons...
func Unmarshal(data []byte, v interface{}) error
...to behave the same way (eg. update the referenced passed as second argument).
Long
The example above is a minimal reproducer that does not make much sense. This is in order to make it work on the playground. However, an less minimal example that does make sense is this:
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
func unmarshalYAML(in []byte, s interface{}) error {
var result map[interface{}]interface{}
err := yaml.Unmarshal(in, &result)
s = cleanUpInterfaceMap(result)
// s is printed as expected
fmt.Println(s) // map[aoeu:[test aoeu] oaeu:[map[mahl:aoec tase:aoeu]]]
return err
}
func cleanUpInterfaceArray(in []interface{}) []interface{} {
out := make([]interface{}, len(in))
for i, v := range in {
out[i] = cleanUpMapValue(v)
}
return out
}
func cleanUpInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
out := make(map[string]interface{})
for k, v := range in {
out[fmt.Sprintf("%v", k)] = cleanUpMapValue(v)
}
return out
}
func cleanUpMapValue(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return cleanUpInterfaceArray(v)
case map[interface{}]interface{}:
return cleanUpInterfaceMap(v)
case string:
return v
default:
return fmt.Sprintf("%v", v)
}
}
func main() {
s := make(map[string]interface{})
b := []byte(`---
aoeu:
- test
- aoeu
oaeu:
- { tase: aoeu, mahl: aoec}
`)
err := unmarshalYAML(b, &s)
if err != nil {
panic(err)
}
// s is still an empty map
fmt.Println(s) // map[]
}
The idea is to unmarshal YAML to map[string]interface{} (instead of map[interface{}]interface{}) is order to allow to serialize to JSON (where identifiers need to be strings). The unmarshalYAML function should provide the same func signture as yaml.Unmarshal...
Using Type assertion
Inside your unmarshalJSON() function the parameter s behaves like a local variable. When you assign something to it:
s = result
It will only change the value of the local variable.
Since you want it to work with changing the value of a *map[string]interface{} and that is what you pass to it, you could use a simple type assertion to obtain the map pointer from it, and pass this pointer to json.Unmarshal():
func unmarshalJSON(in []byte, s interface{}) error {
if m, ok := s.(*map[string]interface{}); !ok {
return errors.New("Expecting *map[string]interface{}")
} else {
return json.Unmarshal(in, m)
}
}
Try your modified, working example on the Go Playground.
Just passing it along
Also note that however this is completely unnecessary as json.Unmarshal() is also defined to take the destination as a value of type interface{}, the same thing you have. So you don't even have to do anything just pass it along:
func unmarshalJSON(in []byte, s interface{}) error {
return json.Unmarshal(in, s)
}
Try this on the Go Playground.
With a variable of function type
As an interesting thing note that the signature of your unmarshalJSON() and the library function json.Unmarshal() is identical:
// Yours:
func unmarshalJSON(in []byte, s interface{}) error
// json package
func Unmarshal(data []byte, v interface{}) error
This means there is another option, that is you could use a variable named unmarshalJSON of a function type, and just simply assign the function value json.Unmarshal:
var unmarshalJSON func([]byte, interface{}) error = json.Unmarshal
Now you have a variable unmarshalJSON which is of function type, and you can call it as if it would be a function:
err := unmarshalJSON(b, &s)
Try this function value on the Go Playground.
Now on to your unmarshalYAML() function
In your unmarshalYAML() you do the same mistake:
s = cleanUpInterfaceMap(result)
This will only change the value of your local s variable (parameter), and it will not "populate" the map (pointer) passed to unmarshalYAML().
Use the type assertion technique detailed above to obtain the pointer from the s interface{} argument, and once you have that, you can change the pointed object (the "outside" map).
func unmarshalYAML(in []byte, s interface{}) error {
var dest *map[string]interface{}
var ok bool
if dest, ok = s.(*map[string]interface{}); !ok {
return errors.New("Expecting *map[string]interface{}")
}
var result map[interface{}]interface{}
if err := yaml.Unmarshal(in, &result); err != nil {
return err
}
m := cleanUpInterfaceMap(result)
// m holds the results, dest is the pointer that was passed to us,
// we can just set the pointed object (map):
*dest = m
return nil
}

Reflecting The Fields of a Empty's Slices Underyling Type?

I have the following query builder function:
func CreateQuery(t interface{}, where string) {
var b bytes.Buffer
b.WriteString("SELECT ")
s := reflect.ValueOf(t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField() - 1; i++ {
b.WriteString(fmt.Sprintf("%s, ", typeOfT.Field(i).Name))
}
//Last one has no Comma
b.WriteString(fmt.Sprintf("%s ", typeOfT.Field(s.NumField() - 1).Name))
b.WriteString(fmt.Sprintf("FROM %s ", typeOfT.Name()))
b.WriteString(where)
fmt.Println(b.String())
}
There works fine when called as follows:
var dst FooStruct
CreateQuery(&dst, "")
But the following raises a "call of reflect.Value.NumField on slice Value" Panic:
var dst []FooStruct
CreateQuery(&dst, "")
How I can I make the function print the fields of a slice's underlying struct type? It seems like I want the inverse of reflect's SliceOf function.
You can only call NumField or Field methods on a reflect.Type representing a struct (i.e. t.Kind() == reflect.Struct).
If you have a slice type, you can access the contained type via the Elem method, which returns another reflect.Type. If the slice contains a struct, then you can call NumField/Field on this type.
You can iterate over the slice, calling CreateQuery for every query:
func CreateQueries(t interface{}, where string) {
v := reflect.ValueOf(t)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() == reflect.Array || v.Kind() == reflect.Slice {
for i := 0; i < v.Len(); i++ {
CreateQuery(v.Index(i).Interface(), where)
}
}
}
Using reflect.Value.Index you can access each field separately, calling .Interface() on the
value yields the interface{} type representation of that value, making it suitable to put
it in your CreateQuery function (which expects a interface{} value).

Resources