Skip to content

Commit

Permalink
lease: Use monotonic time in lease
Browse files Browse the repository at this point in the history
lease uses monotimer to calculate its expiration. In this way, changing system time won't affect in lease expiration.

FIX #6700
  • Loading branch information
fanminshi committed Nov 29, 2016
1 parent ec5f9bc commit 2ccf41b
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 11 deletions.
15 changes: 8 additions & 7 deletions lease/lessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/coreos/etcd/lease/leasepb"
"github.com/coreos/etcd/mvcc/backend"
"github.com/coreos/etcd/pkg/monotime"
)

const (
Expand All @@ -33,9 +34,9 @@ const (

var (
leaseBucketName = []byte("lease")
// do not use maxInt64 since it can overflow time which will add
// the offset of unix time (1970yr to seconds).
forever = time.Unix(math.MaxInt64>>1, 0)

monoTimer = monotime.New()
forever = time.Duration(1>>63 - 1)

ErrNotPrimary = errors.New("not a primary lessor")
ErrLeaseNotFound = errors.New("lease not found")
Expand Down Expand Up @@ -504,8 +505,8 @@ type Lease struct {
ttl int64 // time to live in seconds

itemSet map[LeaseItem]struct{}
// expiry time in unixnano
expiry time.Time
// expiry is time when lease should expire
expiry time.Duration
revokec chan struct{}
}

Expand Down Expand Up @@ -534,7 +535,7 @@ func (l *Lease) TTL() int64 {

// refresh refreshes the expiry of the lease.
func (l *Lease) refresh(extend time.Duration) {
l.expiry = time.Now().Add(extend + time.Second*time.Duration(l.ttl))
l.expiry = extend + monoTimer.SinceBegining() + time.Duration(l.ttl)*time.Second
}

// forever sets the expiry of lease to be forever.
Expand All @@ -551,7 +552,7 @@ func (l *Lease) Keys() []string {

// Remaining returns the remaining time of the lease.
func (l *Lease) Remaining() time.Duration {
return l.expiry.Sub(time.Now())
return l.expiry - monoTimer.SinceBegining()
}

type LeaseItem struct {
Expand Down
11 changes: 7 additions & 4 deletions lease/lessor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import (
"github.com/coreos/etcd/mvcc/backend"
)

const minLeaseTTL = int64(5)
const (
minLeaseTTL = int64(5)
minLeaseTTLDuration = time.Duration(minLeaseTTL) * time.Second
)

// TestLessorGrant ensures Lessor can grant wanted lease.
// The granted lease should have a unique ID with a term
Expand All @@ -48,8 +51,8 @@ func TestLessorGrant(t *testing.T) {
if !reflect.DeepEqual(gl, l) {
t.Errorf("lease = %v, want %v", gl, l)
}
if l.expiry.Sub(time.Now()) < time.Duration(minLeaseTTL)*time.Second-time.Second {
t.Errorf("term = %v, want at least %v", l.expiry.Sub(time.Now()), time.Duration(minLeaseTTL)*time.Second-time.Second)
if l.Remaining() < minLeaseTTLDuration-time.Second {
t.Errorf("term = %v, want at least %v", l.Remaining(), minLeaseTTLDuration-time.Second)
}

nl, err := le.Grant(1, 1)
Expand Down Expand Up @@ -152,7 +155,7 @@ func TestLessorRenew(t *testing.T) {
}

l = le.Lookup(l.ID)
if l.expiry.Sub(time.Now()) < 9*time.Second {
if l.Remaining() < 9*time.Second {
t.Errorf("failed to renew the lease")
}
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/monotime/issue15006.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (C) 2016 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// This file is intentionally empty.
// It's a workaround for https://github.com/golang/go/issues/15006
49 changes: 49 additions & 0 deletions pkg/monotime/monotime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package monotime

import (
"time"
)

type Timer interface {
// Since returns duration between two points of time
Since(uint64) time.Duration
// SinceBegining returns duration between now and when timer is created
SinceBegining() time.Duration
// Now returns the current time in nanoseconds
Now() uint64
}

func New() Timer {
return &monoTimer{
begin: Now(),
}
}

type monoTimer struct {
begin uint64
}

func (t *monoTimer) Now() uint64 {
return Now()
}

func (t *monoTimer) Since(start uint64) time.Duration {
return time.Duration(Now() - start)
}

func (t *monoTimer) SinceBegining() time.Duration {
return t.Since(t.begin)
}
24 changes: 24 additions & 0 deletions pkg/monotime/nanotime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (C) 2016 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// Package monotime provides a fast monotonic clock source.
package monotime

import (
_ "unsafe" // required to use //go:linkname
)

//go:noescape
//go:linkname nanotime runtime.nanotime
func nanotime() int64

// Now returns the current time in nanoseconds from a monotonic clock.
// The time returned is based on some arbitrary platform-specific point in the
// past. The time returned is guaranteed to increase monotonically at a
// constant rate, unlike time.Now() from the Go standard library, which may
// slow down, speed up, jump forward or backward, due to NTP activity or leap
// seconds.
func Now() uint64 {
return uint64(nanotime())
}
23 changes: 23 additions & 0 deletions pkg/monotime/nanotime_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (C) 2016 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// Package monotime provides a fast monotonic clock source.

package monotime

import (
"testing"
)

func TestNow(t *testing.T) {
for i := 0; i < 100; i++ {
t1 := Now()
t2 := Now()
// I honestly thought that we needed >= here, but in some environments
// two consecutive calls can return the same value!
if t1 > t2 {
t.Fatalf("t1=%d should have been less than or equal to t2=%d", t1, t2)
}
}
}

0 comments on commit 2ccf41b

Please sign in to comment.