I'm working on a database and I want to know the best way to solve this issue.
Basically, I would like to unlock the read mutex earlier in the function because the last bit is concurrent safe.
func (s *Structure) Get(key interface{}, object interface{}) (found bool, err error ){
s.RLock()
defer s.RUnlock()
// Concurrent-dangerous stuff begins
ref, err := s.Index.GetOneEquals(string(s.StructName), key)
if err != nil {
return false, err
}
path := ref.ToPath(s.StructName)
if (path == nil) {
return false, nil
}
value, err := dbdrivers.DB.Get(path)
if err != nil {
return false, err
}
// Concurrent-dangerous stuff ends
// RUnlock should technically be here
err = encoding.Marshaler.Unmarshal(value, object)
if err != nil {
return false, err
}
}
If I just add an s.RUnlocked at the bottom part (where it says RUnlocked should technically be here) while keeping the deferred statement, if I understand correctly, it will cause issues. As I understand it, RUnlock works via a counter. So if my program reaches beyond that point ("RUnlocked should technically be here"), it will call the s.RUnlocked and also the deferred s.RUnlocked as well when the function ends. So the counter will decrement two times which might cause a disaster.
Therefore, the only solution I can think of is this - which is begging for gotchas down the line because I need to think of everywhere the function can end:
func (s *Structure) Get(key interface{}, object interface{}) (found bool, err error ){
s.RLock()
// Concurrent-dangerous stuff begins
ref, err := s.Index.GetOneEquals(string(s.StructName), key)
if err != nil {
s.RUnlock() // <----
return false, err
}
path := ref.ToPath(s.StructName)
if (path == nil) {
s.RUnlock() // <----
return false, nil
}
value, err := dbdrivers.DB.Get(path)
if err != nil {
s.RUnlock() // <----
return false, err
}
// Concurrent-dangerous stuff ends
// RUnlock should technically be here
s.RUnlock() // <----
err = encoding.Marshaler.Unmarshal(value, object)
if err != nil {
return false, err
}
}
Is there a safer way to do this?