|
@@ -15,15 +15,20 @@
|
|
|
package lease
|
|
package lease
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "encoding/binary"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"sync"
|
|
"sync"
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
|
|
|
+ "github.com/coreos/etcd/lease/leasepb"
|
|
|
"github.com/coreos/etcd/pkg/idutil"
|
|
"github.com/coreos/etcd/pkg/idutil"
|
|
|
|
|
+ "github.com/coreos/etcd/storage/backend"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
var (
|
|
|
minLeaseTerm = 5 * time.Second
|
|
minLeaseTerm = 5 * time.Second
|
|
|
|
|
+
|
|
|
|
|
+ leaseBucketName = []byte("lease")
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// DeleteableRange defines an interface with DeleteRange method.
|
|
// DeleteableRange defines an interface with DeleteRange method.
|
|
@@ -37,7 +42,6 @@ type DeleteableRange interface {
|
|
|
|
|
|
|
|
// a lessor is the owner of leases. It can grant, revoke,
|
|
// a lessor is the owner of leases. It can grant, revoke,
|
|
|
// renew and modify leases for lessee.
|
|
// renew and modify leases for lessee.
|
|
|
-// TODO: persist lease on to stable backend for failure recovery.
|
|
|
|
|
// TODO: use clockwork for testability.
|
|
// TODO: use clockwork for testability.
|
|
|
type lessor struct {
|
|
type lessor struct {
|
|
|
mu sync.Mutex
|
|
mu sync.Mutex
|
|
@@ -47,49 +51,67 @@ type lessor struct {
|
|
|
// We want to make Grant, Revoke, and FindExpired all O(logN) and
|
|
// We want to make Grant, Revoke, and FindExpired all O(logN) and
|
|
|
// Renew O(1).
|
|
// Renew O(1).
|
|
|
// FindExpired and Renew should be the most frequent operations.
|
|
// FindExpired and Renew should be the most frequent operations.
|
|
|
- leaseMap map[uint64]*lease
|
|
|
|
|
|
|
+ leaseMap map[int64]*lease
|
|
|
|
|
|
|
|
// A DeleteableRange the lessor operates on.
|
|
// A DeleteableRange the lessor operates on.
|
|
|
// When a lease expires, the lessor will delete the
|
|
// When a lease expires, the lessor will delete the
|
|
|
// leased range (or key) from the DeleteableRange.
|
|
// leased range (or key) from the DeleteableRange.
|
|
|
dr DeleteableRange
|
|
dr DeleteableRange
|
|
|
|
|
|
|
|
|
|
+ // backend to persist leases. We only persist lease ID and expiry for now.
|
|
|
|
|
+ // The leased items can be recovered by iterating all the keys in kv.
|
|
|
|
|
+ b backend.Backend
|
|
|
|
|
+
|
|
|
idgen *idutil.Generator
|
|
idgen *idutil.Generator
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func NewLessor(lessorID uint8, dr DeleteableRange) *lessor {
|
|
|
|
|
- return &lessor{
|
|
|
|
|
- leaseMap: make(map[uint64]*lease),
|
|
|
|
|
|
|
+func NewLessor(lessorID uint8, b backend.Backend, dr DeleteableRange) *lessor {
|
|
|
|
|
+ l := &lessor{
|
|
|
|
|
+ leaseMap: make(map[int64]*lease),
|
|
|
|
|
+ b: b,
|
|
|
dr: dr,
|
|
dr: dr,
|
|
|
idgen: idutil.NewGenerator(lessorID, time.Now()),
|
|
idgen: idutil.NewGenerator(lessorID, time.Now()),
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ tx := l.b.BatchTx()
|
|
|
|
|
+ tx.Lock()
|
|
|
|
|
+ tx.UnsafeCreateBucket(leaseBucketName)
|
|
|
|
|
+ tx.Unlock()
|
|
|
|
|
+ l.b.ForceCommit()
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: recover from previous state in backend.
|
|
|
|
|
+
|
|
|
|
|
+ return l
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Grant grants a lease that expires at least at the given expiry
|
|
|
|
|
-// time.
|
|
|
|
|
-// TODO: when leassor is under highload, it should give out lease
|
|
|
|
|
-// with longer term to reduce renew load.
|
|
|
|
|
-func (le *lessor) Grant(expiry time.Time) *lease {
|
|
|
|
|
|
|
+// Grant grants a lease that expires at least after TTL seconds.
|
|
|
|
|
+// TODO: when lessor is under high load, it should give out lease
|
|
|
|
|
+// with longer TTL to reduce renew load.
|
|
|
|
|
+func (le *lessor) Grant(ttl int64) *lease {
|
|
|
|
|
+ // TODO: define max TTL
|
|
|
|
|
+ expiry := time.Now().Add(time.Duration(ttl) * time.Second)
|
|
|
expiry = minExpiry(time.Now(), expiry)
|
|
expiry = minExpiry(time.Now(), expiry)
|
|
|
|
|
|
|
|
- id := le.idgen.Next()
|
|
|
|
|
|
|
+ id := int64(le.idgen.Next())
|
|
|
|
|
|
|
|
le.mu.Lock()
|
|
le.mu.Lock()
|
|
|
defer le.mu.Unlock()
|
|
defer le.mu.Unlock()
|
|
|
|
|
|
|
|
- l := &lease{id: id, expiry: expiry, itemSet: make(map[leaseItem]struct{})}
|
|
|
|
|
|
|
+ l := &lease{id: id, ttl: ttl, expiry: expiry, itemSet: make(map[leaseItem]struct{})}
|
|
|
if _, ok := le.leaseMap[id]; ok {
|
|
if _, ok := le.leaseMap[id]; ok {
|
|
|
panic("lease: unexpected duplicate ID!")
|
|
panic("lease: unexpected duplicate ID!")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
le.leaseMap[id] = l
|
|
le.leaseMap[id] = l
|
|
|
|
|
+ l.persistTo(le.b)
|
|
|
|
|
+
|
|
|
return l
|
|
return l
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Revoke revokes a lease with given ID. The item attached to the
|
|
// Revoke revokes a lease with given ID. The item attached to the
|
|
|
// given lease will be removed. If the ID does not exist, an error
|
|
// given lease will be removed. If the ID does not exist, an error
|
|
|
// will be returned.
|
|
// will be returned.
|
|
|
-func (le *lessor) Revoke(id uint64) error {
|
|
|
|
|
|
|
+func (le *lessor) Revoke(id int64) error {
|
|
|
le.mu.Lock()
|
|
le.mu.Lock()
|
|
|
defer le.mu.Unlock()
|
|
defer le.mu.Unlock()
|
|
|
|
|
|
|
@@ -98,19 +120,20 @@ func (le *lessor) Revoke(id uint64) error {
|
|
|
return fmt.Errorf("lease: cannot find lease %x", id)
|
|
return fmt.Errorf("lease: cannot find lease %x", id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- delete(le.leaseMap, l.id)
|
|
|
|
|
-
|
|
|
|
|
for item := range l.itemSet {
|
|
for item := range l.itemSet {
|
|
|
- le.dr.DeleteRange([]byte(item.key), []byte(item.endRange))
|
|
|
|
|
|
|
+ le.dr.DeleteRange([]byte(item.key), nil)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ delete(le.leaseMap, l.id)
|
|
|
|
|
+ l.removeFrom(le.b)
|
|
|
|
|
+
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Renew renews an existing lease with at least the given expiry.
|
|
|
|
|
-// If the given lease does not exist or has expired, an error will
|
|
|
|
|
-// be returned.
|
|
|
|
|
-func (le *lessor) Renew(id uint64, expiry time.Time) error {
|
|
|
|
|
|
|
+// Renew renews an existing lease. If the given lease does not exist or
|
|
|
|
|
+// has expired, an error will be returned.
|
|
|
|
|
+// TODO: return new TTL?
|
|
|
|
|
+func (le *lessor) Renew(id int64) error {
|
|
|
le.mu.Lock()
|
|
le.mu.Lock()
|
|
|
defer le.mu.Unlock()
|
|
defer le.mu.Unlock()
|
|
|
|
|
|
|
@@ -119,6 +142,7 @@ func (le *lessor) Renew(id uint64, expiry time.Time) error {
|
|
|
return fmt.Errorf("lease: cannot find lease %x", id)
|
|
return fmt.Errorf("lease: cannot find lease %x", id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ expiry := time.Now().Add(time.Duration(l.ttl) * time.Second)
|
|
|
l.expiry = minExpiry(time.Now(), expiry)
|
|
l.expiry = minExpiry(time.Now(), expiry)
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
@@ -126,7 +150,7 @@ func (le *lessor) Renew(id uint64, expiry time.Time) error {
|
|
|
// Attach attaches items to the lease with given ID. When the lease
|
|
// Attach attaches items to the lease with given ID. When the lease
|
|
|
// expires, the attached items will be automatically removed.
|
|
// expires, the attached items will be automatically removed.
|
|
|
// If the given lease does not exist, an error will be returned.
|
|
// If the given lease does not exist, an error will be returned.
|
|
|
-func (le *lessor) Attach(id uint64, items []leaseItem) error {
|
|
|
|
|
|
|
+func (le *lessor) Attach(id int64, items []leaseItem) error {
|
|
|
le.mu.Lock()
|
|
le.mu.Lock()
|
|
|
defer le.mu.Unlock()
|
|
defer le.mu.Unlock()
|
|
|
|
|
|
|
@@ -161,7 +185,7 @@ func (le *lessor) findExpiredLeases() []*lease {
|
|
|
|
|
|
|
|
// get gets the lease with given id.
|
|
// get gets the lease with given id.
|
|
|
// get is a helper fucntion for testing, at least for now.
|
|
// get is a helper fucntion for testing, at least for now.
|
|
|
-func (le *lessor) get(id uint64) *lease {
|
|
|
|
|
|
|
+func (le *lessor) get(id int64) *lease {
|
|
|
le.mu.Lock()
|
|
le.mu.Lock()
|
|
|
defer le.mu.Unlock()
|
|
defer le.mu.Unlock()
|
|
|
|
|
|
|
@@ -169,16 +193,38 @@ func (le *lessor) get(id uint64) *lease {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
type lease struct {
|
|
type lease struct {
|
|
|
- id uint64
|
|
|
|
|
|
|
+ id int64
|
|
|
|
|
+ ttl int64 // time to live in seconds
|
|
|
|
|
|
|
|
itemSet map[leaseItem]struct{}
|
|
itemSet map[leaseItem]struct{}
|
|
|
// expiry time in unixnano
|
|
// expiry time in unixnano
|
|
|
expiry time.Time
|
|
expiry time.Time
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (l lease) persistTo(b backend.Backend) {
|
|
|
|
|
+ key := int64ToBytes(l.id)
|
|
|
|
|
+
|
|
|
|
|
+ lpb := leasepb.Lease{ID: l.id, TTL: int64(l.ttl)}
|
|
|
|
|
+ val, err := lpb.Marshal()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ panic("failed to marshal lease proto item")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ b.BatchTx().Lock()
|
|
|
|
|
+ b.BatchTx().UnsafePut(leaseBucketName, key, val)
|
|
|
|
|
+ b.BatchTx().Unlock()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (l lease) removeFrom(b backend.Backend) {
|
|
|
|
|
+ key := int64ToBytes(l.id)
|
|
|
|
|
+
|
|
|
|
|
+ b.BatchTx().Lock()
|
|
|
|
|
+ b.BatchTx().UnsafeDelete(leaseBucketName, key)
|
|
|
|
|
+ b.BatchTx().Unlock()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
type leaseItem struct {
|
|
type leaseItem struct {
|
|
|
- key string
|
|
|
|
|
- endRange string
|
|
|
|
|
|
|
+ key string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// minExpiry returns a minimal expiry. A minimal expiry is the larger on
|
|
// minExpiry returns a minimal expiry. A minimal expiry is the larger on
|
|
@@ -190,3 +236,9 @@ func minExpiry(now time.Time, expectedExpiry time.Time) time.Time {
|
|
|
}
|
|
}
|
|
|
return expectedExpiry
|
|
return expectedExpiry
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+func int64ToBytes(n int64) []byte {
|
|
|
|
|
+ bytes := make([]byte, 8)
|
|
|
|
|
+ binary.BigEndian.PutUint64(bytes, uint64(n))
|
|
|
|
|
+ return bytes
|
|
|
|
|
+}
|