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_URL environment 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.DeadlineExceeded errors
- 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
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_URL environment variable
- Modifying the default DSN in the code
- Using connection parameters in the DSN string
Common Patterns
Pattern 1: Try 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
}
// Do work
Pattern 2: Wait for Lock (Blocking)
Use when work must be done eventually:
if err := lock.WaitAndLock(ctx); err != nil {
return err
}
// Do work
Pattern 3: 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
}
// 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