feat: basic RwMutex implementation
This commit is contained in:
72
README.md
72
README.md
@@ -1,2 +1,74 @@
|
|||||||
# custom-rwmutex
|
# custom-rwmutex
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
`CustomRwMutex` is a lightweight, type-safe wrapper around Go’s standard `sync.RWMutex` that provides safer, more ergonomic access patterns through handler functions. Instead of manually calling `Lock()`/`RLock()` and `Unlock()`/`RUnlock()`, you pass a function to be executed under the appropriate lock, ensuring proper resource management and reducing the risk of deadlocks or unlocked access.
|
||||||
|
|
||||||
|
This pattern enforces **scope-bound locking**, similar to a `defer`-based RAII pattern, making concurrent code more readable and less error-prone.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- ✅ **Thread-safe**: Built on `sync.RWMutex`, guarantees safe concurrent access.
|
||||||
|
- ✅ **Handler-based API**: Prevents manual lock/unlock mistakes.
|
||||||
|
- ✅ **Error propagation**: Functions passed in can return errors; they’re propagated up cleanly.
|
||||||
|
- ✅ **No external dependencies**: Pure Go, zero dependencies.
|
||||||
|
- ✅ **Minimal overhead**: Just a thin wrapper around standard library primitives.
|
||||||
|
|
||||||
|
### Real-World Example: Shared Config
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := &Config{Host: "localhost", Port: 8080}
|
||||||
|
mu := custom_rwmutex.NewCustomRwMutex()
|
||||||
|
|
||||||
|
// Update config (writer)
|
||||||
|
err := mu.WriteHandler(func() error {
|
||||||
|
config.Host = "api.example.com"
|
||||||
|
config.Port = 443
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read config (reader)
|
||||||
|
err = mu.ReadHandler(func() error {
|
||||||
|
fmt.Printf("Current config: %s:%d\n", config.Host, config.Port)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: Since `config` is captured in closures, ensure it’s not modified outside the lock context. For full safety, consider returning copies of data from `ReadHandler`.
|
||||||
|
|
||||||
|
## Why Use `CustomRwMutex`?
|
||||||
|
|
||||||
|
| Problem with `sync.RWMutex` | How `CustomRwMutex` Solves It |
|
||||||
|
|-----------------------------|-------------------------------|
|
||||||
|
| Manual `Lock()`/`Unlock()` can be forgotten or mismatched | Automatically handled via `defer` |
|
||||||
|
| Risk of leaving locks held due to early returns | `defer` guarantees unlock even on error |
|
||||||
|
| No built-in error handling for operations | Functions return errors; easily propagated |
|
||||||
|
| Verbose and error-prone patterns | Clean, functional-style API |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- ✅ **Keep handlers short and fast** — long operations under write locks block all readers and writers.
|
||||||
|
- ✅ **Avoid blocking calls** like `time.Sleep()` or HTTP requests in locked sections.
|
||||||
|
- ✅ **Return errors from handlers** — they’re preserved and returned from `ReadHandler`/`WriteHandler`.
|
||||||
|
- ✅ **Do not hold references to external data** outside the handler unless you are *certain* they are thread-safe or copied.
|
||||||
|
- ✅ **For heavy read workloads**, consider returning a copy of the data instead of exposing references:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var config Data
|
||||||
|
err := mu.ReadHandler(func() error {
|
||||||
|
result := config // copy the data
|
||||||
|
// do work on result
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
3
go.mod
Normal file
3
go.mod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module git.neurocipta.com/rogerferdinan/custom-rwmutex
|
||||||
|
|
||||||
|
go 1.24.5
|
||||||
31
mutex.go
Normal file
31
mutex.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package custom_rwmutex
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type CustomRwMutex struct {
|
||||||
|
mu *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomRwMutex() *CustomRwMutex {
|
||||||
|
return &CustomRwMutex{
|
||||||
|
mu: &sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwMu *CustomRwMutex) WriteHandler(fn func() error) error {
|
||||||
|
rwMu.mu.Lock()
|
||||||
|
defer rwMu.mu.Unlock()
|
||||||
|
if err := fn(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwMu *CustomRwMutex) ReadHandler(fn func() error) error {
|
||||||
|
rwMu.mu.RLock()
|
||||||
|
defer rwMu.mu.RUnlock()
|
||||||
|
if err := fn(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user