README
ΒΆ
π‘ go-pglock Examples
This directory contains practical examples demonstrating various use cases for go-pglock.
π Prerequisites
- π PostgreSQL running and accessible
- ποΈ Database created (default:
pglock) - π§ Set
DATABASE_URLenvironment variable (optional)
# Using docker-compose from the project root
docker-compose up -d
# Or set custom database URL
export DATABASE_URL='postgres://user:pass@localhost:5432/dbname?sslmode=disable'
π Running Examples
π° Basic Lock Usage
Demonstrates the fundamental lock acquire and release pattern.
cd examples/basic
go run main.go
What it demonstrates:
- π Creating a lock
- β
Acquiring a lock with
Lock()(non-blocking) - βοΈ Performing work in a critical section
- π Releasing the lock
Try it: Run two instances in different terminals to see one acquire the lock while the other is blocked.
π· Concurrent Workers
Shows how multiple workers coordinate access to a shared resource.
cd examples/workers
go run main.go
What it demonstrates:
- π Multiple goroutines competing for the same lock
- β³
WaitAndLock()blocking behavior - π Sequential execution enforced by the lock
- π§Ή Proper cleanup in concurrent scenarios
Expected output:
- βΈοΈ Workers start and wait for the lock
- π Workers execute one at a time (serialized)
- β±οΈ Each worker holds the lock for ~1 second
π Leader Election
Implements a simple leader election mechanism.
cd examples/leader-election
go run main.go
What it demonstrates:
- π³οΈ Leader election pattern
- β Only one instance becomes the leader
- π Leader performing periodic tasks
- π₯ Followers waiting/skipping work
Use cases:
- π Distributed systems where only one instance should perform certain tasks
- π Active-passive failover scenarios
- π€ Coordinating cluster-wide operations
β±οΈ Timeout Handling
Shows how to use context timeouts with lock operations.
cd examples/timeout
go run main.go
What it demonstrates:
- β° Creating locks with timeout contexts
- β οΈ Handling
context.DeadlineExceedederrors - ποΈ Different timeout values and their effects
- π§Ή Proper cleanup when timeouts occur
Expected output:
- β First attempt fails due to short timeout
- β Second attempt succeeds with longer timeout
βοΈ Task Processing
Demonstrates distributed task processing with lock-based coordination.
cd examples/task-processing
go run main.go
What it demonstrates:
- π·οΈ Using task IDs to generate lock IDs
- π« Preventing duplicate task execution
- #οΈβ£ Hash-based lock ID generation
- β οΈ Handling already-processing scenarios
Use cases:
- π¬ Job queues in distributed systems
- β»οΈ Idempotent task processing
- π Preventing race conditions in task execution
π Read-Write Locks
Demonstrates shared (read) and exclusive (write) locks for concurrent reader scenarios.
cd examples/read-write-locks
go run main.go
What it demonstrates:
- π₯ Shared locks (
RLock()) allowing multiple concurrent readers - π Exclusive locks (
Lock()) blocking all other operations - π How readers and writers interact
- π Three scenarios: multiple readers, writer blocking readers, readers blocking writer
Use cases:
- π Read-heavy workloads with occasional writes
- πΎ Cache management with multiple readers
- βοΈ Configuration management
- π Report viewing with generation locks
- ποΈ Database record access coordination
Expected output:
- β Scenario 1: Multiple readers acquire locks simultaneously
- β³ Scenario 2: Writer blocks readers until write completes
- βΈοΈ Scenario 3: Readers block writer until all reads complete
π Running Multiple Instances
To see the distributed locking in action, run the same example in multiple terminal windows:
# π» Terminal 1
cd examples/basic
go run main.go
# π» Terminal 2 (run simultaneously)
cd examples/basic
go run main.go
You'll see that only one instance acquires the lock while others wait or skip.
π οΈ Customizing Examples
All examples use the same database connection pattern:
dsn := os.Getenv("DATABASE_URL")
if dsn == "" {
dsn = "postgres://test:test@localhost:5432/pglock?sslmode=disable"
}
You can customize the connection by:
- π§ Setting the
DATABASE_URLenvironment variable - βοΈ Modifying the default DSN in the code
- π Using connection parameters in the DSN string
π‘ Common Patterns
Pattern 1: Try Exclusive Lock (Non-blocking)
Use when you want to skip work if another instance is already doing it:
acquired, err := lock.Lock(ctx)
if !acquired {
return // βοΈ Skip work
}
defer lock.Unlock(ctx)
// β
Do work
Pattern 2: Try Shared Lock (Non-blocking)
Use when you want to read data but skip if a writer is active:
acquired, err := lock.RLock(ctx)
if !acquired {
return // βοΈ Skip reading, writer is active
}
defer lock.RUnlock(ctx)
// π Read data (multiple readers can do this concurrently)
Pattern 3: Wait for Exclusive Lock (Blocking)
Use when work must be done eventually:
if err := lock.WaitAndLock(ctx); err != nil {
return err
}
defer lock.Unlock(ctx)
// β
Do work
Pattern 4: Wait for Shared Lock (Blocking)
Use when you must read data eventually:
if err := lock.WaitAndRLock(ctx); err != nil {
return err
}
defer lock.RUnlock(ctx)
// π Read data
Pattern 5: Lock with Timeout
Use when you want to wait but not indefinitely:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := lock.WaitAndLock(ctx); err != nil {
// β οΈ Handle timeout or other errors
}
defer lock.Unlock(context.Background())
// β
Do work
ποΈ Building Examples
To build all examples:
# From the examples directory
for dir in */; do
echo "Building ${dir%/}..."
(cd "$dir" && go build -o "../../bin/${dir%/}" .)
done
This creates binaries in the bin/ directory.
π§ Troubleshooting
β "DATABASE_URL environment variable not set"
Either set the environment variable or use the default connection string in the code.
β οΈ "pq: database does not exist"
Create the database:
CREATE DATABASE pglock;
π "connection refused"
Make sure PostgreSQL is running:
docker-compose up -d
π Lock Never Released
Check that:
- β
defer lock.Close()is present - π« Program doesn't panic before cleanup
- β±οΈ Context isn't cancelled prematurely
π Further Reading
- π PostgreSQL Advisory Locks Documentation
- π Main README for more examples and patterns
- π API Documentation