From 37260f6436dfcb9f5fe01e6f2d4523191cb6ff86 Mon Sep 17 00:00:00 2001 From: fanmin shi Date: Mon, 28 Nov 2016 15:02:51 -0800 Subject: [PATCH] lease: Use monotonic time in lease lease uses monotimer to calculate its expiration. In this way, changing system time won't affect in lease expiration. FIX #6700 --- glide.lock | 2 +- glide.yaml | 1 - lease/lessor.go | 15 ++++++----- lease/lessor_test.go | 11 +++++--- pkg/monotime/issue15006.s | 6 +++++ pkg/monotime/monotime.go | 49 +++++++++++++++++++++++++++++++++++ pkg/monotime/nanotime.go | 24 +++++++++++++++++ pkg/monotime/nanotime_test.go | 22 ++++++++++++++++ 8 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 pkg/monotime/issue15006.s create mode 100644 pkg/monotime/monotime.go create mode 100644 pkg/monotime/nanotime.go create mode 100644 pkg/monotime/nanotime_test.go diff --git a/glide.lock b/glide.lock index 6826c914b547..91aa97489af2 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: 93d5ee1318c98aca7c4b42e057239806a565e5aadebdd79e260f95c206098358 -updated: 2016-11-11T09:49:50.684453902-08:00 +updated: 2016-11-28T16:36:16.962259253-08:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 diff --git a/glide.yaml b/glide.yaml index c5c0484fdc1d..bdba4b794ab6 100644 --- a/glide.yaml +++ b/glide.yaml @@ -105,4 +105,3 @@ import: version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506 subpackages: - assert - diff --git a/lease/lessor.go b/lease/lessor.go index 45ed881b6734..3cfbee3312b4 100644 --- a/lease/lessor.go +++ b/lease/lessor.go @@ -24,6 +24,7 @@ import ( "github.com/coreos/etcd/lease/leasepb" "github.com/coreos/etcd/mvcc/backend" + "github.com/coreos/etcd/pkg/monotime" ) const ( @@ -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") @@ -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{} } @@ -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. @@ -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 { diff --git a/lease/lessor_test.go b/lease/lessor_test.go index 4cc28a9db212..1907acd8f668 100644 --- a/lease/lessor_test.go +++ b/lease/lessor_test.go @@ -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 @@ -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) @@ -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") } } diff --git a/pkg/monotime/issue15006.s b/pkg/monotime/issue15006.s new file mode 100644 index 000000000000..c3132a1f0c8a --- /dev/null +++ b/pkg/monotime/issue15006.s @@ -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 \ No newline at end of file diff --git a/pkg/monotime/monotime.go b/pkg/monotime/monotime.go new file mode 100644 index 000000000000..6870a3569f93 --- /dev/null +++ b/pkg/monotime/monotime.go @@ -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) +} diff --git a/pkg/monotime/nanotime.go b/pkg/monotime/nanotime.go new file mode 100644 index 000000000000..efc1b92a606c --- /dev/null +++ b/pkg/monotime/nanotime.go @@ -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()) +} diff --git a/pkg/monotime/nanotime_test.go b/pkg/monotime/nanotime_test.go new file mode 100644 index 000000000000..bcfff68b7733 --- /dev/null +++ b/pkg/monotime/nanotime_test.go @@ -0,0 +1,22 @@ +// 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) + } + } +}