diff --git a/README.md b/README.md index 9b454bc..9cc1f6d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,74 @@ # 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 +}) +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1d1ddf5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.neurocipta.com/rogerferdinan/custom-rwmutex + +go 1.24.5 diff --git a/mutex.go b/mutex.go new file mode 100644 index 0000000..64fbd0a --- /dev/null +++ b/mutex.go @@ -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 +}