From 4c7269ec2a8f576d284956ffe95d9427d3f2882f Mon Sep 17 00:00:00 2001 From: Kirill Danshin Date: Fri, 17 Aug 2018 15:32:15 +0300 Subject: [PATCH] Revert "Remove CoarseTime" This reverts commit f2ddaffc31d94ffb7ac4c0f9d19adf2fbc96bd71. --- client.go | 14 +++++++------- coarseTime.go | 28 ++++++++++++++++++++++++++++ coarseTime_test.go | 37 +++++++++++++++++++++++++++++++++++++ server.go | 21 +++++++++++++-------- workerpool.go | 2 +- 5 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 coarseTime.go create mode 100644 coarseTime_test.go diff --git a/client.go b/client.go index f74ad0cb0e..fc11102a08 100644 --- a/client.go +++ b/client.go @@ -1041,7 +1041,7 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) panic("BUG: resp cannot be nil") } - atomic.StoreUint32(&c.lastUseTime, uint32(time.Now().Unix()-startTimeUnix)) + atomic.StoreUint32(&c.lastUseTime, uint32(CoarseTimeNow().Unix()-startTimeUnix)) // Free up resources occupied by response before sending the request, // so the GC may reclaim these resources (e.g. response body). @@ -1057,7 +1057,7 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) // Optimization: update write deadline only if more than 25% // of the last write deadline exceeded. // See https://github.com/golang/go/issues/15133 for details. - currentTime := time.Now() + currentTime := CoarseTimeNow() if currentTime.Sub(cc.lastWriteDeadlineTime) > (c.WriteTimeout >> 2) { if err = conn.SetWriteDeadline(currentTime.Add(c.WriteTimeout)); err != nil { c.closeConn(cc) @@ -1101,7 +1101,7 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) // Optimization: update read deadline only if more than 25% // of the last read deadline exceeded. // See https://github.com/golang/go/issues/15133 for details. - currentTime := time.Now() + currentTime := CoarseTimeNow() if currentTime.Sub(cc.lastReadDeadlineTime) > (c.ReadTimeout >> 2) { if err = conn.SetReadDeadline(currentTime.Add(c.ReadTimeout)); err != nil { c.closeConn(cc) @@ -1276,7 +1276,7 @@ func acquireClientConn(conn net.Conn) *clientConn { } cc := v.(*clientConn) cc.c = conn - cc.createdTime = time.Now() + cc.createdTime = CoarseTimeNow() return cc } @@ -1288,7 +1288,7 @@ func releaseClientConn(cc *clientConn) { var clientConnPool sync.Pool func (c *HostClient) releaseConn(cc *clientConn) { - cc.lastUseTime = time.Now() + cc.lastUseTime = CoarseTimeNow() c.connsLock.Lock() c.conns = append(c.conns, cc) c.connsLock.Unlock() @@ -1991,7 +1991,7 @@ func (c *pipelineConnClient) writer(conn net.Conn, stopCh <-chan struct{}) error // Optimization: update write deadline only if more than 25% // of the last write deadline exceeded. // See https://github.com/golang/go/issues/15133 for details. - currentTime := time.Now() + currentTime := CoarseTimeNow() if currentTime.Sub(lastWriteDeadlineTime) > (writeTimeout >> 2) { if err = conn.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil { w.err = err @@ -2072,7 +2072,7 @@ func (c *pipelineConnClient) reader(conn net.Conn, stopCh <-chan struct{}) error // Optimization: update read deadline only if more than 25% // of the last read deadline exceeded. // See https://github.com/golang/go/issues/15133 for details. - currentTime := time.Now() + currentTime := CoarseTimeNow() if currentTime.Sub(lastReadDeadlineTime) > (readTimeout >> 2) { if err = conn.SetReadDeadline(currentTime.Add(readTimeout)); err != nil { w.err = err diff --git a/coarseTime.go b/coarseTime.go new file mode 100644 index 0000000000..03c14f39a8 --- /dev/null +++ b/coarseTime.go @@ -0,0 +1,28 @@ +package fasthttp + +import ( + "sync/atomic" + "time" +) + +// CoarseTimeNow returns the current time truncated to the nearest second. +// +// This is a faster alternative to time.Now(). +func CoarseTimeNow() time.Time { + tp := coarseTime.Load().(*time.Time) + return *tp +} + +func init() { + t := time.Now().Truncate(time.Second) + coarseTime.Store(&t) + go func() { + for { + time.Sleep(time.Second) + t := time.Now().Truncate(time.Second) + coarseTime.Store(&t) + } + }() +} + +var coarseTime atomic.Value diff --git a/coarseTime_test.go b/coarseTime_test.go new file mode 100644 index 0000000000..b2f2334ee3 --- /dev/null +++ b/coarseTime_test.go @@ -0,0 +1,37 @@ +package fasthttp + +import ( + "sync/atomic" + "testing" + "time" +) + +func BenchmarkCoarseTimeNow(b *testing.B) { + var zeroTimeCount uint64 + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + t := CoarseTimeNow() + if t.IsZero() { + atomic.AddUint64(&zeroTimeCount, 1) + } + } + }) + if zeroTimeCount > 0 { + b.Fatalf("zeroTimeCount must be zero") + } +} + +func BenchmarkTimeNow(b *testing.B) { + var zeroTimeCount uint64 + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + t := time.Now() + if t.IsZero() { + atomic.AddUint64(&zeroTimeCount, 1) + } + } + }) + if zeroTimeCount > 0 { + b.Fatalf("zeroTimeCount must be zero") + } +} diff --git a/server.go b/server.go index 4066d7b4b1..689d91e804 100644 --- a/server.go +++ b/server.go @@ -598,13 +598,18 @@ func (ctx *RequestCtx) ConnID() uint64 { return ctx.connID } -// Time returns RequestHandler call time. +// Time returns RequestHandler call time truncated to the nearest second. +// +// Call time.Now() at the beginning of RequestHandler in order to obtain +// precise RequestHandler call time. func (ctx *RequestCtx) Time() time.Time { return ctx.time } // ConnTime returns the time server starts serving the connection // the current request came from. +// +// The returned time is truncated to the nearest second. func (ctx *RequestCtx) ConnTime() time.Time { return ctx.connTime } @@ -1314,7 +1319,7 @@ func (s *Server) Serve(ln net.Listener) error { if time.Since(lastOverflowErrorTime) > time.Minute { s.logger().Printf("The incoming connection cannot be served, because %d concurrent connections are served. "+ "Try increasing Server.Concurrency", maxWorkersCount) - lastOverflowErrorTime = time.Now() + lastOverflowErrorTime = CoarseTimeNow() } // The current server reached concurrency limit, @@ -1356,7 +1361,7 @@ func acceptConn(s *Server, ln net.Listener, lastPerIPErrorTime *time.Time) (net. if time.Since(*lastPerIPErrorTime) > time.Minute { s.logger().Printf("The number of connections from %s exceeds MaxConnsPerIP=%d", getConnIP4(c), s.MaxConnsPerIP) - *lastPerIPErrorTime = time.Now() + *lastPerIPErrorTime = CoarseTimeNow() } continue } @@ -1471,7 +1476,7 @@ func (s *Server) serveConn(c net.Conn) error { serverName := s.getServerName() connRequestNum := uint64(0) connID := nextConnID() - currentTime := time.Now() + currentTime := CoarseTimeNow() connTime := currentTime maxRequestBodySize := s.MaxRequestBodySize if maxRequestBodySize <= 0 { @@ -1528,7 +1533,7 @@ func (s *Server) serveConn(c net.Conn) error { } } - currentTime = time.Now() + currentTime = CoarseTimeNow() ctx.lastReadDuration = currentTime.Sub(ctx.time) if err != nil { @@ -1669,7 +1674,7 @@ func (s *Server) serveConn(c net.Conn) error { break } - currentTime = time.Now() + currentTime = CoarseTimeNow() } if br != nil { @@ -1725,7 +1730,7 @@ func (s *Server) updateWriteDeadline(c net.Conn, ctx *RequestCtx, lastDeadlineTi // Optimization: update write deadline only if more than 25% // of the last write deadline exceeded. // See https://github.com/golang/go/issues/15133 for details. - currentTime := time.Now() + currentTime := CoarseTimeNow() if currentTime.Sub(lastDeadlineTime) > (writeTimeout >> 2) { if err := c.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil { panic(fmt.Sprintf("BUG: error in SetWriteDeadline(%s): %s", writeTimeout, err)) @@ -1908,7 +1913,7 @@ func (ctx *RequestCtx) Init2(conn net.Conn, logger Logger, reduceMemoryUsage boo ctx.connID = nextConnID() ctx.s = fakeServer ctx.connRequestNum = 0 - ctx.connTime = time.Now() + ctx.connTime = CoarseTimeNow() ctx.time = ctx.connTime keepBodyBuffer := !reduceMemoryUsage diff --git a/workerpool.go b/workerpool.go index 081ac65c39..cf602e0507 100644 --- a/workerpool.go +++ b/workerpool.go @@ -187,7 +187,7 @@ func (wp *workerPool) getCh() *workerChan { } func (wp *workerPool) release(ch *workerChan) bool { - ch.lastUseTime = time.Now() + ch.lastUseTime = CoarseTimeNow() wp.lock.Lock() if wp.mustStop { wp.lock.Unlock()