diff --git a/lease/lessor.go b/lease/lessor.go index 45ed881b6734..314b663a8bc1 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,8 @@ 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) + + forever = monotime.Time(math.MaxInt64) ErrNotPrimary = errors.New("not a primary lessor") ErrLeaseNotFound = errors.New("lease not found") @@ -504,8 +504,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 monotime.Time revokec chan struct{} } @@ -534,7 +534,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 = monotime.Now().Add(extend + time.Duration(l.ttl)*time.Second) } // forever sets the expiry of lease to be forever. @@ -551,7 +551,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 time.Duration(l.expiry - monotime.Now()) } 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..45cebaf15bea --- /dev/null +++ b/pkg/monotime/monotime.go @@ -0,0 +1,26 @@ +// 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" +) + +// time represents a point in monotonic time +type Time uint64 + +func (t Time) Add(d time.Duration) Time { + return Time(uint64(t) + uint64(d.Nanoseconds())) +} diff --git a/pkg/monotime/nanotime.go b/pkg/monotime/nanotime.go new file mode 100644 index 000000000000..59b534163569 --- /dev/null +++ b/pkg/monotime/nanotime.go @@ -0,0 +1,25 @@ +// 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() Time { + return Time(nanotime()) +} diff --git a/pkg/monotime/nanotime_test.go b/pkg/monotime/nanotime_test.go new file mode 100644 index 000000000000..c22b328b1a73 --- /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 + +// 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) + } + } +}