# Lease Locker

This package provides a generic lease-lock interface for distributed workloads.
It currently ships with a database-backed implementation based on GORM.

## What It Solves

- Only one replica can hold a lock for the same business key at a time.
- Other replicas are blocked while the lease is still valid.
- The lock can be released explicitly after work is done.
- If not released, other replicas can acquire it after lease expiration.

The lock key is fully defined by the caller (for example: task name, tenant ID, resource ID, or any composed key).

## Data Model

The package exposes a GORM model:

- `lease.Lease` (table: `distributed_leases`)

The upper-layer service is responsible for migration, for example:

```go
if err := provider.WriteDB(ctx).AutoMigrate(&lease.Lease{}); err != nil {
    return err
}
```

## API

- `type Locker interface { Acquire / Renew / Release }`
- `NewLocker(provider db.Provider) (Locker, error)`
- `NewDBLocker(provider db.Provider) (*DBLocker, error)`
- `Acquire(ctx, key, owner, ttl) (bool, error)`
- `Renew(ctx, key, owner, ttl) (bool, error)`
- `Release(ctx, key, owner) (bool, error)`

## Usage Example

```go
locker, err := lease.NewLocker(provider)
if err != nil {
    return err
}

key := "backup-sync:shared-pool:12345"
owner := os.Getenv("POD_NAME")
if owner == "" {
    owner = os.Getenv("HOSTNAME")
}

acquired, err := locker.Acquire(ctx, key, owner, 2*time.Minute)
if err != nil {
    return err
}
if !acquired {
    // Another replica is holding a valid lease.
    return nil
}

// Optional: release explicitly.
defer func() {
    _, _ = locker.Release(context.Background(), key, owner)
}()

// Do protected work here.
```

## Semantics

- `Acquire` succeeds when:
  - the key does not exist, or
  - the caller already owns the key, or
  - the existing lease is expired.
- `Renew` succeeds only when the same owner still holds a non-expired lease.
- `Release` succeeds only when the caller is the current owner.
