-1

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?

1
  • What's best is an opinion. Please edit your question to focus on objective metrics, or your question risks being closed as off-topic. Commented Dec 6, 2021 at 16:43

1 Answer 1

1

You can still use defer if you put the inner block into its own function:

func (s *Structure) Get(key interface{}, object interface{}) (found bool, err error ){

    s.RLock()
    failed, found, err:= func() {
      defer s.RUnlock()
         ref, err := s.Index.GetOneEquals(string(s.StructName), key)
         if err != nil {
            return true, false, err
         }
         // Do stuff
         return false, found, nil
    }()
    if failed {
      return found, err
    }
    // continue function, lock is unlocked
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.