Ideas wanted: Rely on language features to enforce locking correctness
Looking at #1324, #1332, #1379 , it’s not absolutely required but boy it would be nice if we could have the programming language enforce locking invariants
- That locks that are supposed to be held are held
- That reads to struct fields only happen with a read lock held
- That writes to struct fields only happen with a write lock held.
At the thousands of lines of c/storage code, and about half a dozen locks involved, it’s only natural that some mistakes will happen and we could certainly use some help noticing them.
Sadly, Go is very weak for these purposes. So, this is an open-ended call for ideas.
- I have nothing at all for protecting access to struct fields. AFAICS we could make the struct completely private and wrap everything in getters/setters … written manually. Not worth it.
- For enforcing API invariants:
- Interfaces sort of work:
but if the three interfaces are implemented by the same struct, calls within that struct are no longer protected (e.g. a “reader” method can call a “writer” method no the same object)type unlockedStore interface { startReading() readableStore startWriting() writableStore } type readableStore interface { // readers Close() } // and writableStore similarly - “lock tokens” work:
but require all call sites to carry around an extra token value. That has zero or close-to-zero run-time cost, but the code is larger.type readToken struct {} // unique type, zero cost to pass around type writeToken struct {} // unique type, zero cost to pass around type store interface { // or just methods on a struct startReading() readToken stopReading(readToken) startWriting() writeToken stopWriting(writeToken) readSomething(readToken) writeSomething(writeToken, valueType) }
- Interfaces sort of work:
Is there something else that would work? Anything that would be worth doing?
- I have nothing at all for protecting access to struct fields
@vrothberg came up with
type stateful struct {
lock sync.Mutex
requiresLock struct {
field …
}
}
which is much better than nothing. A downside is that such a type can’t be created with a trivial struct initializer: either .requiresLock.field must be initialized after the struct is created, or requiresLock must become an out-of-line separately-defined named type — that’s probably still well worth the cost.