Remove facebookgo/httpdown. (#3168)
Its purpose is now served by net/http's Shutdown().
This commit is contained in:
parent
600640294d
commit
071fc0120f
|
|
@ -85,18 +85,6 @@
|
||||||
"Comment": "v1.1.0",
|
"Comment": "v1.1.0",
|
||||||
"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
|
"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/facebookgo/clock",
|
|
||||||
"Rev": "600d898af40aa09a7a93ecb9265d87b0504b6f03"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/facebookgo/httpdown",
|
|
||||||
"Rev": "a3b1354551a26449fbe05f5d855937f6e7acbd71"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/facebookgo/stats",
|
|
||||||
"Rev": "31fb71caf5a4f04c9f8bb3fa8e7c2597ba6eb50a"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||||
"Comment": "v1.3-28-g3955978",
|
"Comment": "v1.3-28-g3955978",
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/facebookgo/httpdown"
|
|
||||||
|
|
||||||
"github.com/letsencrypt/boulder/cmd"
|
"github.com/letsencrypt/boulder/cmd"
|
||||||
"github.com/letsencrypt/boulder/core"
|
"github.com/letsencrypt/boulder/core"
|
||||||
"github.com/letsencrypt/boulder/features"
|
"github.com/letsencrypt/boulder/features"
|
||||||
|
|
@ -39,7 +38,6 @@ type config struct {
|
||||||
IssuerCacheDuration cmd.ConfigDuration
|
IssuerCacheDuration cmd.ConfigDuration
|
||||||
|
|
||||||
ShutdownStopTimeout cmd.ConfigDuration
|
ShutdownStopTimeout cmd.ConfigDuration
|
||||||
ShutdownKillTimeout cmd.ConfigDuration
|
|
||||||
|
|
||||||
SubscriberAgreementURL string
|
SubscriberAgreementURL string
|
||||||
|
|
||||||
|
|
@ -140,36 +138,40 @@ func main() {
|
||||||
Handler: wfe.Handler(),
|
Handler: wfe.Handler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
hd := &httpdown.HTTP{
|
go func() {
|
||||||
StopTimeout: c.WFE.ShutdownStopTimeout.Duration,
|
err := srv.ListenAndServe()
|
||||||
KillTimeout: c.WFE.ShutdownKillTimeout.Duration,
|
if err != nil && err != http.ErrServerClosed {
|
||||||
}
|
cmd.FailOnError(err, "Running HTTP server")
|
||||||
hdSrv, err := hd.ListenAndServe(srv)
|
}
|
||||||
cmd.FailOnError(err, "Error starting HTTP server")
|
}()
|
||||||
|
|
||||||
var hdTLSSrv httpdown.Server
|
var tlsSrv *http.Server
|
||||||
if c.WFE.TLSListenAddress != "" {
|
if c.WFE.TLSListenAddress != "" {
|
||||||
cer, err := tls.LoadX509KeyPair(c.WFE.ServerCertificatePath, c.WFE.ServerKeyPath)
|
tlsSrv = &http.Server{
|
||||||
cmd.FailOnError(err, "Couldn't read WFE server certificate or key")
|
Addr: c.WFE.TLSListenAddress,
|
||||||
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cer}}
|
Handler: wfe.Handler(),
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("TLS Server running, listening on %s...\n", c.WFE.TLSListenAddress))
|
|
||||||
TLSSrv := &http.Server{
|
|
||||||
Addr: c.WFE.TLSListenAddress,
|
|
||||||
Handler: wfe.Handler(),
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
}
|
}
|
||||||
hdTLSSrv, err = hd.ListenAndServe(TLSSrv)
|
go func() {
|
||||||
cmd.FailOnError(err, "Error starting TLS server")
|
err := tlsSrv.ListenAndServeTLS(c.WFE.ServerCertificatePath, c.WFE.ServerKeyPath)
|
||||||
|
cmd.FailOnError(err, "Error starting TLS server")
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
go cmd.CatchSignals(logger, func() {
|
go cmd.CatchSignals(logger, func() {
|
||||||
_ = hdSrv.Stop()
|
ctx, cancel := context.WithTimeout(context.Background(),
|
||||||
if hdTLSSrv != nil {
|
c.WFE.ShutdownStopTimeout.Duration)
|
||||||
_ = hdTLSSrv.Stop()
|
defer cancel()
|
||||||
|
_ = srv.Shutdown(ctx)
|
||||||
|
if tlsSrv != nil {
|
||||||
|
_ = tlsSrv.Shutdown(ctx)
|
||||||
}
|
}
|
||||||
|
done <- true
|
||||||
})
|
})
|
||||||
|
|
||||||
forever := make(chan struct{}, 1)
|
// https://godoc.org/net/http#Server.Shutdown:
|
||||||
<-forever
|
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
|
||||||
|
// immediately return ErrServerClosed. Make sure the program doesn't exit and
|
||||||
|
// waits instead for Shutdown to return.
|
||||||
|
<-done
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/facebookgo/httpdown"
|
|
||||||
|
|
||||||
"github.com/letsencrypt/boulder/cmd"
|
"github.com/letsencrypt/boulder/cmd"
|
||||||
"github.com/letsencrypt/boulder/core"
|
"github.com/letsencrypt/boulder/core"
|
||||||
"github.com/letsencrypt/boulder/features"
|
"github.com/letsencrypt/boulder/features"
|
||||||
|
|
@ -39,7 +38,6 @@ type config struct {
|
||||||
IssuerCacheDuration cmd.ConfigDuration
|
IssuerCacheDuration cmd.ConfigDuration
|
||||||
|
|
||||||
ShutdownStopTimeout cmd.ConfigDuration
|
ShutdownStopTimeout cmd.ConfigDuration
|
||||||
ShutdownKillTimeout cmd.ConfigDuration
|
|
||||||
|
|
||||||
SubscriberAgreementURL string
|
SubscriberAgreementURL string
|
||||||
|
|
||||||
|
|
@ -140,36 +138,39 @@ func main() {
|
||||||
Handler: wfe.Handler(),
|
Handler: wfe.Handler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
hd := &httpdown.HTTP{
|
go func() {
|
||||||
StopTimeout: c.WFE.ShutdownStopTimeout.Duration,
|
err := srv.ListenAndServe()
|
||||||
KillTimeout: c.WFE.ShutdownKillTimeout.Duration,
|
if err != nil && err != http.ErrServerClosed {
|
||||||
}
|
cmd.FailOnError(err, "Running HTTP server")
|
||||||
hdSrv, err := hd.ListenAndServe(srv)
|
}
|
||||||
cmd.FailOnError(err, "Error starting HTTP server")
|
}()
|
||||||
|
|
||||||
var hdTLSSrv httpdown.Server
|
var tlsSrv *http.Server
|
||||||
if c.WFE.TLSListenAddress != "" {
|
if c.WFE.TLSListenAddress != "" {
|
||||||
cer, err := tls.LoadX509KeyPair(c.WFE.ServerCertificatePath, c.WFE.ServerKeyPath)
|
tlsSrv = &http.Server{
|
||||||
cmd.FailOnError(err, "Couldn't read WFE server certificate or key")
|
Addr: c.WFE.TLSListenAddress,
|
||||||
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cer}}
|
Handler: wfe.Handler(),
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("TLS Server running, listening on %s...\n", c.WFE.TLSListenAddress))
|
|
||||||
TLSSrv := &http.Server{
|
|
||||||
Addr: c.WFE.TLSListenAddress,
|
|
||||||
Handler: wfe.Handler(),
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
}
|
}
|
||||||
hdTLSSrv, err = hd.ListenAndServe(TLSSrv)
|
go func() {
|
||||||
cmd.FailOnError(err, "Error starting TLS server")
|
err := tlsSrv.ListenAndServeTLS(c.WFE.ServerCertificatePath, c.WFE.ServerKeyPath)
|
||||||
|
cmd.FailOnError(err, "Error starting TLS server")
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
go cmd.CatchSignals(logger, func() {
|
go cmd.CatchSignals(logger, func() {
|
||||||
_ = hdSrv.Stop()
|
ctx, cancel := context.WithTimeout(context.Background(), c.WFE.ShutdownStopTimeout.Duration)
|
||||||
if hdTLSSrv != nil {
|
defer cancel()
|
||||||
_ = hdTLSSrv.Stop()
|
_ = srv.Shutdown(ctx)
|
||||||
|
if tlsSrv != nil {
|
||||||
|
_ = tlsSrv.Shutdown(ctx)
|
||||||
}
|
}
|
||||||
|
done <- true
|
||||||
})
|
})
|
||||||
|
|
||||||
forever := make(chan struct{}, 1)
|
// https://godoc.org/net/http#Server.Shutdown:
|
||||||
<-forever
|
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
|
||||||
|
// immediately return ErrServerClosed. Make sure the program doesn't exit and
|
||||||
|
// waits instead for Shutdown to return.
|
||||||
|
<-done
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
@ -14,7 +15,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cfocsp "github.com/cloudflare/cfssl/ocsp"
|
cfocsp "github.com/cloudflare/cfssl/ocsp"
|
||||||
"github.com/facebookgo/httpdown"
|
|
||||||
"golang.org/x/crypto/ocsp"
|
"golang.org/x/crypto/ocsp"
|
||||||
|
|
||||||
"github.com/letsencrypt/boulder/cmd"
|
"github.com/letsencrypt/boulder/cmd"
|
||||||
|
|
@ -138,8 +138,7 @@ type config struct {
|
||||||
// header. It is a time.Duration formatted string.
|
// header. It is a time.Duration formatted string.
|
||||||
MaxAge cmd.ConfigDuration
|
MaxAge cmd.ConfigDuration
|
||||||
|
|
||||||
ShutdownStopTimeout string
|
ShutdownStopTimeout cmd.ConfigDuration
|
||||||
ShutdownKillTimeout string
|
|
||||||
|
|
||||||
Features map[string]bool
|
Features map[string]bool
|
||||||
}
|
}
|
||||||
|
|
@ -204,27 +203,31 @@ as generated by Boulder's single-ocsp command.
|
||||||
cmd.FailOnError(err, "Couldn't load OCSP DB")
|
cmd.FailOnError(err, "Couldn't load OCSP DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownStopTimeout)
|
|
||||||
cmd.FailOnError(err, "Couldn't parse shutdown stop timeout")
|
|
||||||
killTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownKillTimeout)
|
|
||||||
cmd.FailOnError(err, "Couldn't parse shutdown kill timeout")
|
|
||||||
m := mux(scope, c.OCSPResponder.Path, source)
|
m := mux(scope, c.OCSPResponder.Path, source)
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: c.OCSPResponder.ListenAddress,
|
Addr: c.OCSPResponder.ListenAddress,
|
||||||
Handler: m,
|
Handler: m,
|
||||||
}
|
}
|
||||||
|
|
||||||
hd := &httpdown.HTTP{
|
done := make(chan bool)
|
||||||
StopTimeout: stopTimeout,
|
go cmd.CatchSignals(logger, func() {
|
||||||
KillTimeout: killTimeout,
|
ctx, cancel := context.WithTimeout(context.Background(),
|
||||||
|
c.OCSPResponder.ShutdownStopTimeout.Duration)
|
||||||
|
defer cancel()
|
||||||
|
_ = srv.Shutdown(ctx)
|
||||||
|
done <- true
|
||||||
|
})
|
||||||
|
|
||||||
|
err = srv.ListenAndServe()
|
||||||
|
if err != nil && err != http.ErrServerClosed {
|
||||||
|
cmd.FailOnError(err, "Running HTTP server")
|
||||||
}
|
}
|
||||||
hdSrv, err := hd.ListenAndServe(srv)
|
|
||||||
cmd.FailOnError(err, "Error starting HTTP server")
|
|
||||||
|
|
||||||
go cmd.CatchSignals(logger, func() { _ = hdSrv.Stop() })
|
// https://godoc.org/net/http#Server.Shutdown:
|
||||||
|
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
|
||||||
forever := make(chan struct{}, 1)
|
// immediately return ErrServerClosed. Make sure the program doesn't exit and
|
||||||
<-forever
|
// waits instead for Shutdown to return.
|
||||||
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
// ocspMux partially implements the interface defined for http.ServeMux but doesn't implement
|
// ocspMux partially implements the interface defined for http.ServeMux but doesn't implement
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
"listenAddress": "0.0.0.0:4002",
|
"listenAddress": "0.0.0.0:4002",
|
||||||
"maxAge": "10s",
|
"maxAge": "10s",
|
||||||
"shutdownStopTimeout": "10s",
|
"shutdownStopTimeout": "10s",
|
||||||
"shutdownKillTimeout": "1m",
|
|
||||||
"debugAddr": ":8005"
|
"debugAddr": ":8005"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
"indexCacheDuration": "24h",
|
"indexCacheDuration": "24h",
|
||||||
"issuerCacheDuration": "48h",
|
"issuerCacheDuration": "48h",
|
||||||
"shutdownStopTimeout": "10s",
|
"shutdownStopTimeout": "10s",
|
||||||
"shutdownKillTimeout": "1m",
|
|
||||||
"subscriberAgreementURL": "http://boulder:4000/terms/v1",
|
"subscriberAgreementURL": "http://boulder:4000/terms/v1",
|
||||||
"acceptRevocationReason": true,
|
"acceptRevocationReason": true,
|
||||||
"allowAuthzDeactivation": true,
|
"allowAuthzDeactivation": true,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
"indexCacheDuration": "24h",
|
"indexCacheDuration": "24h",
|
||||||
"issuerCacheDuration": "48h",
|
"issuerCacheDuration": "48h",
|
||||||
"shutdownStopTimeout": "10s",
|
"shutdownStopTimeout": "10s",
|
||||||
"shutdownKillTimeout": "1m",
|
|
||||||
"subscriberAgreementURL": "http://boulder:4001/terms/v1",
|
"subscriberAgreementURL": "http://boulder:4001/terms/v1",
|
||||||
"acceptRevocationReason": true,
|
"acceptRevocationReason": true,
|
||||||
"allowAuthzDeactivation": true,
|
"allowAuthzDeactivation": true,
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Ben Johnson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
clock [](https://drone.io/github.com/benbjohnson/clock/latest) [](https://coveralls.io/r/benbjohnson/clock?branch=master) [](https://godoc.org/github.com/benbjohnson/clock) 
|
|
||||||
=====
|
|
||||||
|
|
||||||
Clock is a small library for mocking time in Go. It provides an interface
|
|
||||||
around the standard library's [`time`][time] package so that the application
|
|
||||||
can use the realtime clock while tests can use the mock clock.
|
|
||||||
|
|
||||||
[time]: http://golang.org/pkg/time/
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Realtime Clock
|
|
||||||
|
|
||||||
Your application can maintain a `Clock` variable that will allow realtime and
|
|
||||||
mock clocks to be interchangable. For example, if you had an `Application` type:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/benbjohnson/clock"
|
|
||||||
|
|
||||||
type Application struct {
|
|
||||||
Clock clock.Clock
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You could initialize it to use the realtime clock like this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
var app Application
|
|
||||||
app.Clock = clock.New()
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Then all timers and time-related functionality should be performed from the
|
|
||||||
`Clock` variable.
|
|
||||||
|
|
||||||
|
|
||||||
### Mocking time
|
|
||||||
|
|
||||||
In your tests, you will want to use a `Mock` clock:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/benbjohnson/clock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestApplication_DoSomething(t *testing.T) {
|
|
||||||
mock := clock.NewMock()
|
|
||||||
app := Application{Clock: mock}
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now that you've initialized your application to use the mock clock, you can
|
|
||||||
adjust the time programmatically. The mock clock always starts from the Unix
|
|
||||||
epoch (midnight, Jan 1, 1970 UTC).
|
|
||||||
|
|
||||||
|
|
||||||
### Controlling time
|
|
||||||
|
|
||||||
The mock clock provides the same functions that the standard library's `time`
|
|
||||||
package provides. For example, to find the current time, you use the `Now()`
|
|
||||||
function:
|
|
||||||
|
|
||||||
```go
|
|
||||||
mock := clock.NewMock()
|
|
||||||
|
|
||||||
// Find the current time.
|
|
||||||
mock.Now().UTC() // 1970-01-01 00:00:00 +0000 UTC
|
|
||||||
|
|
||||||
// Move the clock forward.
|
|
||||||
mock.Add(2 * time.Hour)
|
|
||||||
|
|
||||||
// Check the time again. It's 2 hours later!
|
|
||||||
mock.Now().UTC() // 1970-01-01 02:00:00 +0000 UTC
|
|
||||||
```
|
|
||||||
|
|
||||||
Timers and Tickers are also controlled by this same mock clock. They will only
|
|
||||||
execute when the clock is moved forward:
|
|
||||||
|
|
||||||
```
|
|
||||||
mock := clock.NewMock()
|
|
||||||
count := 0
|
|
||||||
|
|
||||||
// Kick off a timer to increment every 1 mock second.
|
|
||||||
go func() {
|
|
||||||
ticker := clock.Ticker(1 * time.Second)
|
|
||||||
for {
|
|
||||||
<-ticker.C
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
runtime.Gosched()
|
|
||||||
|
|
||||||
// Move the clock forward 10 second.
|
|
||||||
mock.Add(10 * time.Second)
|
|
||||||
|
|
||||||
// This prints 10.
|
|
||||||
fmt.Println(count)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,363 +0,0 @@
|
||||||
package clock
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Clock represents an interface to the functions in the standard library time
|
|
||||||
// package. Two implementations are available in the clock package. The first
|
|
||||||
// is a real-time clock which simply wraps the time package's functions. The
|
|
||||||
// second is a mock clock which will only make forward progress when
|
|
||||||
// programmatically adjusted.
|
|
||||||
type Clock interface {
|
|
||||||
After(d time.Duration) <-chan time.Time
|
|
||||||
AfterFunc(d time.Duration, f func()) *Timer
|
|
||||||
Now() time.Time
|
|
||||||
Sleep(d time.Duration)
|
|
||||||
Tick(d time.Duration) <-chan time.Time
|
|
||||||
Ticker(d time.Duration) *Ticker
|
|
||||||
Timer(d time.Duration) *Timer
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns an instance of a real-time clock.
|
|
||||||
func New() Clock {
|
|
||||||
return &clock{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clock implements a real-time clock by simply wrapping the time package functions.
|
|
||||||
type clock struct{}
|
|
||||||
|
|
||||||
func (c *clock) After(d time.Duration) <-chan time.Time { return time.After(d) }
|
|
||||||
|
|
||||||
func (c *clock) AfterFunc(d time.Duration, f func()) *Timer {
|
|
||||||
return &Timer{timer: time.AfterFunc(d, f)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *clock) Now() time.Time { return time.Now() }
|
|
||||||
|
|
||||||
func (c *clock) Sleep(d time.Duration) { time.Sleep(d) }
|
|
||||||
|
|
||||||
func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) }
|
|
||||||
|
|
||||||
func (c *clock) Ticker(d time.Duration) *Ticker {
|
|
||||||
t := time.NewTicker(d)
|
|
||||||
return &Ticker{C: t.C, ticker: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *clock) Timer(d time.Duration) *Timer {
|
|
||||||
t := time.NewTimer(d)
|
|
||||||
return &Timer{C: t.C, timer: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock represents a mock clock that only moves forward programmically.
|
|
||||||
// It can be preferable to a real-time clock when testing time-based functionality.
|
|
||||||
type Mock struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
now time.Time // current time
|
|
||||||
timers clockTimers // tickers & timers
|
|
||||||
|
|
||||||
calls Calls
|
|
||||||
waiting []waiting
|
|
||||||
callsMutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMock returns an instance of a mock clock.
|
|
||||||
// The current time of the mock clock on initialization is the Unix epoch.
|
|
||||||
func NewMock() *Mock {
|
|
||||||
return &Mock{now: time.Unix(0, 0)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add moves the current time of the mock clock forward by the duration.
|
|
||||||
// This should only be called from a single goroutine at a time.
|
|
||||||
func (m *Mock) Add(d time.Duration) {
|
|
||||||
// Calculate the final current time.
|
|
||||||
t := m.now.Add(d)
|
|
||||||
|
|
||||||
// Continue to execute timers until there are no more before the new time.
|
|
||||||
for {
|
|
||||||
if !m.runNextTimer(t) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that we end with the new time.
|
|
||||||
m.mu.Lock()
|
|
||||||
m.now = t
|
|
||||||
m.mu.Unlock()
|
|
||||||
|
|
||||||
// Give a small buffer to make sure the other goroutines get handled.
|
|
||||||
gosched()
|
|
||||||
}
|
|
||||||
|
|
||||||
// runNextTimer executes the next timer in chronological order and moves the
|
|
||||||
// current time to the timer's next tick time. The next time is not executed if
|
|
||||||
// it's next time if after the max time. Returns true if a timer is executed.
|
|
||||||
func (m *Mock) runNextTimer(max time.Time) bool {
|
|
||||||
m.mu.Lock()
|
|
||||||
|
|
||||||
// Sort timers by time.
|
|
||||||
sort.Sort(m.timers)
|
|
||||||
|
|
||||||
// If we have no more timers then exit.
|
|
||||||
if len(m.timers) == 0 {
|
|
||||||
m.mu.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve next timer. Exit if next tick is after new time.
|
|
||||||
t := m.timers[0]
|
|
||||||
if t.Next().After(max) {
|
|
||||||
m.mu.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move "now" forward and unlock clock.
|
|
||||||
m.now = t.Next()
|
|
||||||
m.mu.Unlock()
|
|
||||||
|
|
||||||
// Execute timer.
|
|
||||||
t.Tick(m.now)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// After waits for the duration to elapse and then sends the current time on the returned channel.
|
|
||||||
func (m *Mock) After(d time.Duration) <-chan time.Time {
|
|
||||||
defer m.inc(&m.calls.After)
|
|
||||||
return m.Timer(d).C
|
|
||||||
}
|
|
||||||
|
|
||||||
// AfterFunc waits for the duration to elapse and then executes a function.
|
|
||||||
// A Timer is returned that can be stopped.
|
|
||||||
func (m *Mock) AfterFunc(d time.Duration, f func()) *Timer {
|
|
||||||
defer m.inc(&m.calls.AfterFunc)
|
|
||||||
t := m.Timer(d)
|
|
||||||
t.C = nil
|
|
||||||
t.fn = f
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now returns the current wall time on the mock clock.
|
|
||||||
func (m *Mock) Now() time.Time {
|
|
||||||
defer m.inc(&m.calls.Now)
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
return m.now
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep pauses the goroutine for the given duration on the mock clock.
|
|
||||||
// The clock must be moved forward in a separate goroutine.
|
|
||||||
func (m *Mock) Sleep(d time.Duration) {
|
|
||||||
defer m.inc(&m.calls.Sleep)
|
|
||||||
<-m.After(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tick is a convenience function for Ticker().
|
|
||||||
// It will return a ticker channel that cannot be stopped.
|
|
||||||
func (m *Mock) Tick(d time.Duration) <-chan time.Time {
|
|
||||||
defer m.inc(&m.calls.Tick)
|
|
||||||
return m.Ticker(d).C
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ticker creates a new instance of Ticker.
|
|
||||||
func (m *Mock) Ticker(d time.Duration) *Ticker {
|
|
||||||
defer m.inc(&m.calls.Ticker)
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
ch := make(chan time.Time)
|
|
||||||
t := &Ticker{
|
|
||||||
C: ch,
|
|
||||||
c: ch,
|
|
||||||
mock: m,
|
|
||||||
d: d,
|
|
||||||
next: m.now.Add(d),
|
|
||||||
}
|
|
||||||
m.timers = append(m.timers, (*internalTicker)(t))
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer creates a new instance of Timer.
|
|
||||||
func (m *Mock) Timer(d time.Duration) *Timer {
|
|
||||||
defer m.inc(&m.calls.Timer)
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
ch := make(chan time.Time)
|
|
||||||
t := &Timer{
|
|
||||||
C: ch,
|
|
||||||
c: ch,
|
|
||||||
mock: m,
|
|
||||||
next: m.now.Add(d),
|
|
||||||
}
|
|
||||||
m.timers = append(m.timers, (*internalTimer)(t))
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mock) removeClockTimer(t clockTimer) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
for i, timer := range m.timers {
|
|
||||||
if timer == t {
|
|
||||||
copy(m.timers[i:], m.timers[i+1:])
|
|
||||||
m.timers[len(m.timers)-1] = nil
|
|
||||||
m.timers = m.timers[:len(m.timers)-1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(m.timers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mock) inc(addr *uint32) {
|
|
||||||
m.callsMutex.Lock()
|
|
||||||
defer m.callsMutex.Unlock()
|
|
||||||
*addr++
|
|
||||||
var newWaiting []waiting
|
|
||||||
for _, w := range m.waiting {
|
|
||||||
if m.calls.atLeast(w.expected) {
|
|
||||||
close(w.done)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newWaiting = append(newWaiting, w)
|
|
||||||
}
|
|
||||||
m.waiting = newWaiting
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait waits for at least the relevant calls before returning. The expected
|
|
||||||
// Calls are always over the lifetime of the Mock. Values in the Calls struct
|
|
||||||
// are used as the minimum number of calls, this allows you to wait for only
|
|
||||||
// the calls you care about.
|
|
||||||
func (m *Mock) Wait(s Calls) {
|
|
||||||
m.callsMutex.Lock()
|
|
||||||
if m.calls.atLeast(s) {
|
|
||||||
m.callsMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
done := make(chan struct{})
|
|
||||||
m.waiting = append(m.waiting, waiting{expected: s, done: done})
|
|
||||||
m.callsMutex.Unlock()
|
|
||||||
<-done
|
|
||||||
}
|
|
||||||
|
|
||||||
// clockTimer represents an object with an associated start time.
|
|
||||||
type clockTimer interface {
|
|
||||||
Next() time.Time
|
|
||||||
Tick(time.Time)
|
|
||||||
}
|
|
||||||
|
|
||||||
// clockTimers represents a list of sortable timers.
|
|
||||||
type clockTimers []clockTimer
|
|
||||||
|
|
||||||
func (a clockTimers) Len() int { return len(a) }
|
|
||||||
func (a clockTimers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next()) }
|
|
||||||
|
|
||||||
// Timer represents a single event.
|
|
||||||
// The current time will be sent on C, unless the timer was created by AfterFunc.
|
|
||||||
type Timer struct {
|
|
||||||
C <-chan time.Time
|
|
||||||
c chan time.Time
|
|
||||||
timer *time.Timer // realtime impl, if set
|
|
||||||
next time.Time // next tick time
|
|
||||||
mock *Mock // mock clock, if set
|
|
||||||
fn func() // AfterFunc function, if set
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop turns off the ticker.
|
|
||||||
func (t *Timer) Stop() {
|
|
||||||
if t.timer != nil {
|
|
||||||
t.timer.Stop()
|
|
||||||
} else {
|
|
||||||
t.mock.removeClockTimer((*internalTimer)(t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type internalTimer Timer
|
|
||||||
|
|
||||||
func (t *internalTimer) Next() time.Time { return t.next }
|
|
||||||
func (t *internalTimer) Tick(now time.Time) {
|
|
||||||
if t.fn != nil {
|
|
||||||
t.fn()
|
|
||||||
} else {
|
|
||||||
t.c <- now
|
|
||||||
}
|
|
||||||
t.mock.removeClockTimer((*internalTimer)(t))
|
|
||||||
gosched()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ticker holds a channel that receives "ticks" at regular intervals.
|
|
||||||
type Ticker struct {
|
|
||||||
C <-chan time.Time
|
|
||||||
c chan time.Time
|
|
||||||
ticker *time.Ticker // realtime impl, if set
|
|
||||||
next time.Time // next tick time
|
|
||||||
mock *Mock // mock clock, if set
|
|
||||||
d time.Duration // time between ticks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop turns off the ticker.
|
|
||||||
func (t *Ticker) Stop() {
|
|
||||||
if t.ticker != nil {
|
|
||||||
t.ticker.Stop()
|
|
||||||
} else {
|
|
||||||
t.mock.removeClockTimer((*internalTicker)(t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type internalTicker Ticker
|
|
||||||
|
|
||||||
func (t *internalTicker) Next() time.Time { return t.next }
|
|
||||||
func (t *internalTicker) Tick(now time.Time) {
|
|
||||||
select {
|
|
||||||
case t.c <- now:
|
|
||||||
case <-time.After(1 * time.Millisecond):
|
|
||||||
}
|
|
||||||
t.next = now.Add(t.d)
|
|
||||||
gosched()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep momentarily so that other goroutines can process.
|
|
||||||
func gosched() { runtime.Gosched() }
|
|
||||||
|
|
||||||
// Calls keeps track of the count of calls for each of the methods on the Clock
|
|
||||||
// interface.
|
|
||||||
type Calls struct {
|
|
||||||
After uint32
|
|
||||||
AfterFunc uint32
|
|
||||||
Now uint32
|
|
||||||
Sleep uint32
|
|
||||||
Tick uint32
|
|
||||||
Ticker uint32
|
|
||||||
Timer uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// atLeast returns true if at least the number of calls in o have been made.
|
|
||||||
func (c Calls) atLeast(o Calls) bool {
|
|
||||||
if c.After < o.After {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if c.AfterFunc < o.AfterFunc {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if c.Now < o.Now {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if c.Sleep < o.Sleep {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if c.Tick < o.Tick {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if c.Ticker < o.Ticker {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if c.Timer < o.Timer {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type waiting struct {
|
|
||||||
expected Calls
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.6
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- go get -v golang.org/x/tools/cmd/vet
|
|
||||||
- go get -v golang.org/x/tools/cmd/cover
|
|
||||||
- go get -v github.com/golang/lint/golint
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go install -race -v std
|
|
||||||
- go get -race -t -v ./...
|
|
||||||
- go install -race -v ./...
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go vet ./...
|
|
||||||
- $HOME/gopath/bin/golint .
|
|
||||||
- go test -cpu=2 -race -v ./...
|
|
||||||
- go test -cpu=2 -covermode=atomic -coverprofile=coverage.txt ./
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
|
||||||
|
|
@ -1,376 +0,0 @@
|
||||||
// Package httpdown provides http.ConnState enabled graceful termination of
|
|
||||||
// http.Server.
|
|
||||||
package httpdown
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/facebookgo/clock"
|
|
||||||
"github.com/facebookgo/stats"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultStopTimeout = time.Minute
|
|
||||||
defaultKillTimeout = time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Server allows encapsulates the process of accepting new connections and
|
|
||||||
// serving them, and gracefully shutting down the listener without dropping
|
|
||||||
// active connections.
|
|
||||||
type Server interface {
|
|
||||||
// Wait waits for the serving loop to finish. This will happen when Stop is
|
|
||||||
// called, at which point it returns no error, or if there is an error in the
|
|
||||||
// serving loop. You must call Wait after calling Serve or ListenAndServe.
|
|
||||||
Wait() error
|
|
||||||
|
|
||||||
// Stop stops the listener. It will block until all connections have been
|
|
||||||
// closed.
|
|
||||||
Stop() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP defines the configuration for serving a http.Server. Multiple calls to
|
|
||||||
// Serve or ListenAndServe can be made on the same HTTP instance. The default
|
|
||||||
// timeouts of 1 minute each result in a maximum of 2 minutes before a Stop()
|
|
||||||
// returns.
|
|
||||||
type HTTP struct {
|
|
||||||
// StopTimeout is the duration before we begin force closing connections.
|
|
||||||
// Defaults to 1 minute.
|
|
||||||
StopTimeout time.Duration
|
|
||||||
|
|
||||||
// KillTimeout is the duration before which we completely give up and abort
|
|
||||||
// even though we still have connected clients. This is useful when a large
|
|
||||||
// number of client connections exist and closing them can take a long time.
|
|
||||||
// Note, this is in addition to the StopTimeout. Defaults to 1 minute.
|
|
||||||
KillTimeout time.Duration
|
|
||||||
|
|
||||||
// Stats is optional. If provided, it will be used to record various metrics.
|
|
||||||
Stats stats.Client
|
|
||||||
|
|
||||||
// Clock allows for testing timing related functionality. Do not specify this
|
|
||||||
// in production code.
|
|
||||||
Clock clock.Clock
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve provides the low-level API which is useful if you're creating your own
|
|
||||||
// net.Listener.
|
|
||||||
func (h HTTP) Serve(s *http.Server, l net.Listener) Server {
|
|
||||||
stopTimeout := h.StopTimeout
|
|
||||||
if stopTimeout == 0 {
|
|
||||||
stopTimeout = defaultStopTimeout
|
|
||||||
}
|
|
||||||
killTimeout := h.KillTimeout
|
|
||||||
if killTimeout == 0 {
|
|
||||||
killTimeout = defaultKillTimeout
|
|
||||||
}
|
|
||||||
klock := h.Clock
|
|
||||||
if klock == nil {
|
|
||||||
klock = clock.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
ss := &server{
|
|
||||||
stopTimeout: stopTimeout,
|
|
||||||
killTimeout: killTimeout,
|
|
||||||
stats: h.Stats,
|
|
||||||
clock: klock,
|
|
||||||
oldConnState: s.ConnState,
|
|
||||||
listener: l,
|
|
||||||
server: s,
|
|
||||||
serveDone: make(chan struct{}),
|
|
||||||
serveErr: make(chan error, 1),
|
|
||||||
new: make(chan net.Conn),
|
|
||||||
active: make(chan net.Conn),
|
|
||||||
idle: make(chan net.Conn),
|
|
||||||
closed: make(chan net.Conn),
|
|
||||||
stop: make(chan chan struct{}),
|
|
||||||
kill: make(chan chan struct{}),
|
|
||||||
}
|
|
||||||
s.ConnState = ss.connState
|
|
||||||
go ss.manage()
|
|
||||||
go ss.serve()
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe returns a Server for the given http.Server. It is equivalent
|
|
||||||
// to ListenAndServe from the standard library, but returns immediately.
|
|
||||||
// Requests will be accepted in a background goroutine. If the http.Server has
|
|
||||||
// a non-nil TLSConfig, a TLS enabled listener will be setup.
|
|
||||||
func (h HTTP) ListenAndServe(s *http.Server) (Server, error) {
|
|
||||||
addr := s.Addr
|
|
||||||
if addr == "" {
|
|
||||||
if s.TLSConfig == nil {
|
|
||||||
addr = ":http"
|
|
||||||
} else {
|
|
||||||
addr = ":https"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
stats.BumpSum(h.Stats, "listen.error", 1)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if s.TLSConfig != nil {
|
|
||||||
l = tls.NewListener(l, s.TLSConfig)
|
|
||||||
}
|
|
||||||
return h.Serve(s, l), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// server manages the serving process and allows for gracefully stopping it.
|
|
||||||
type server struct {
|
|
||||||
stopTimeout time.Duration
|
|
||||||
killTimeout time.Duration
|
|
||||||
stats stats.Client
|
|
||||||
clock clock.Clock
|
|
||||||
|
|
||||||
oldConnState func(net.Conn, http.ConnState)
|
|
||||||
server *http.Server
|
|
||||||
serveDone chan struct{}
|
|
||||||
serveErr chan error
|
|
||||||
listener net.Listener
|
|
||||||
|
|
||||||
new chan net.Conn
|
|
||||||
active chan net.Conn
|
|
||||||
idle chan net.Conn
|
|
||||||
closed chan net.Conn
|
|
||||||
stop chan chan struct{}
|
|
||||||
kill chan chan struct{}
|
|
||||||
|
|
||||||
stopOnce sync.Once
|
|
||||||
stopErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) connState(c net.Conn, cs http.ConnState) {
|
|
||||||
if s.oldConnState != nil {
|
|
||||||
s.oldConnState(c, cs)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cs {
|
|
||||||
case http.StateNew:
|
|
||||||
s.new <- c
|
|
||||||
case http.StateActive:
|
|
||||||
s.active <- c
|
|
||||||
case http.StateIdle:
|
|
||||||
s.idle <- c
|
|
||||||
case http.StateHijacked, http.StateClosed:
|
|
||||||
s.closed <- c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) manage() {
|
|
||||||
defer func() {
|
|
||||||
close(s.new)
|
|
||||||
close(s.active)
|
|
||||||
close(s.idle)
|
|
||||||
close(s.closed)
|
|
||||||
close(s.stop)
|
|
||||||
close(s.kill)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var stopDone chan struct{}
|
|
||||||
|
|
||||||
conns := map[net.Conn]http.ConnState{}
|
|
||||||
var countNew, countActive, countIdle float64
|
|
||||||
|
|
||||||
// decConn decrements the count associated with the current state of the
|
|
||||||
// given connection.
|
|
||||||
decConn := func(c net.Conn) {
|
|
||||||
switch conns[c] {
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("unknown existing connection: %s", c))
|
|
||||||
case http.StateNew:
|
|
||||||
countNew--
|
|
||||||
case http.StateActive:
|
|
||||||
countActive--
|
|
||||||
case http.StateIdle:
|
|
||||||
countIdle--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup a ticker to report various values every minute. if we don't have a
|
|
||||||
// Stats implementation provided, we Stop it so it never ticks.
|
|
||||||
statsTicker := s.clock.Ticker(time.Minute)
|
|
||||||
if s.stats == nil {
|
|
||||||
statsTicker.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-statsTicker.C:
|
|
||||||
// we'll only get here when s.stats is not nil
|
|
||||||
s.stats.BumpAvg("http-state.new", countNew)
|
|
||||||
s.stats.BumpAvg("http-state.active", countActive)
|
|
||||||
s.stats.BumpAvg("http-state.idle", countIdle)
|
|
||||||
s.stats.BumpAvg("http-state.total", countNew+countActive+countIdle)
|
|
||||||
case c := <-s.new:
|
|
||||||
conns[c] = http.StateNew
|
|
||||||
countNew++
|
|
||||||
case c := <-s.active:
|
|
||||||
decConn(c)
|
|
||||||
countActive++
|
|
||||||
|
|
||||||
conns[c] = http.StateActive
|
|
||||||
case c := <-s.idle:
|
|
||||||
decConn(c)
|
|
||||||
countIdle++
|
|
||||||
|
|
||||||
conns[c] = http.StateIdle
|
|
||||||
|
|
||||||
// if we're already stopping, close it
|
|
||||||
if stopDone != nil {
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
case c := <-s.closed:
|
|
||||||
stats.BumpSum(s.stats, "conn.closed", 1)
|
|
||||||
decConn(c)
|
|
||||||
delete(conns, c)
|
|
||||||
|
|
||||||
// if we're waiting to stop and are all empty, we just closed the last
|
|
||||||
// connection and we're done.
|
|
||||||
if stopDone != nil && len(conns) == 0 {
|
|
||||||
close(stopDone)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case stopDone = <-s.stop:
|
|
||||||
// if we're already all empty, we're already done
|
|
||||||
if len(conns) == 0 {
|
|
||||||
close(stopDone)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// close current idle connections right away
|
|
||||||
for c, cs := range conns {
|
|
||||||
if cs == http.StateIdle {
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// continue the loop and wait for all the ConnState updates which will
|
|
||||||
// eventually close(stopDone) and return from this goroutine.
|
|
||||||
|
|
||||||
case killDone := <-s.kill:
|
|
||||||
// force close all connections
|
|
||||||
stats.BumpSum(s.stats, "kill.conn.count", float64(len(conns)))
|
|
||||||
for c := range conns {
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't block the kill.
|
|
||||||
close(killDone)
|
|
||||||
|
|
||||||
// continue the loop and we wait for all the ConnState updates and will
|
|
||||||
// return from this goroutine when we're all done. otherwise we'll try to
|
|
||||||
// send those ConnState updates on closed channels.
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) serve() {
|
|
||||||
stats.BumpSum(s.stats, "serve", 1)
|
|
||||||
s.serveErr <- s.server.Serve(s.listener)
|
|
||||||
close(s.serveDone)
|
|
||||||
close(s.serveErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) Wait() error {
|
|
||||||
if err := <-s.serveErr; !isUseOfClosedError(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) Stop() error {
|
|
||||||
s.stopOnce.Do(func() {
|
|
||||||
defer stats.BumpTime(s.stats, "stop.time").End()
|
|
||||||
stats.BumpSum(s.stats, "stop", 1)
|
|
||||||
|
|
||||||
// first disable keep-alive for new connections
|
|
||||||
s.server.SetKeepAlivesEnabled(false)
|
|
||||||
|
|
||||||
// then close the listener so new connections can't connect come thru
|
|
||||||
closeErr := s.listener.Close()
|
|
||||||
<-s.serveDone
|
|
||||||
|
|
||||||
// then trigger the background goroutine to stop and wait for it
|
|
||||||
stopDone := make(chan struct{})
|
|
||||||
s.stop <- stopDone
|
|
||||||
|
|
||||||
// wait for stop
|
|
||||||
select {
|
|
||||||
case <-stopDone:
|
|
||||||
case <-s.clock.After(s.stopTimeout):
|
|
||||||
defer stats.BumpTime(s.stats, "kill.time").End()
|
|
||||||
stats.BumpSum(s.stats, "kill", 1)
|
|
||||||
|
|
||||||
// stop timed out, wait for kill
|
|
||||||
killDone := make(chan struct{})
|
|
||||||
s.kill <- killDone
|
|
||||||
select {
|
|
||||||
case <-killDone:
|
|
||||||
case <-s.clock.After(s.killTimeout):
|
|
||||||
// kill timed out, give up
|
|
||||||
stats.BumpSum(s.stats, "kill.timeout", 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if closeErr != nil && !isUseOfClosedError(closeErr) {
|
|
||||||
stats.BumpSum(s.stats, "listener.close.error", 1)
|
|
||||||
s.stopErr = closeErr
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return s.stopErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func isUseOfClosedError(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if opErr, ok := err.(*net.OpError); ok {
|
|
||||||
err = opErr.Err
|
|
||||||
}
|
|
||||||
return err.Error() == "use of closed network connection"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe is a convenience function to serve and wait for a SIGTERM
|
|
||||||
// or SIGINT before shutting down.
|
|
||||||
func ListenAndServe(s *http.Server, hd *HTTP) error {
|
|
||||||
if hd == nil {
|
|
||||||
hd = &HTTP{}
|
|
||||||
}
|
|
||||||
hs, err := hd.ListenAndServe(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
waiterr := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
defer close(waiterr)
|
|
||||||
waiterr <- hs.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
signals := make(chan os.Signal, 10)
|
|
||||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-waiterr:
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-signals:
|
|
||||||
signal.Stop(signals)
|
|
||||||
if err := hs.Stop(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := <-waiterr; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
BSD License
|
|
||||||
|
|
||||||
For httpdown software
|
|
||||||
|
|
||||||
Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
* Neither the name Facebook nor the names of its contributors may be used to
|
|
||||||
endorse or promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
Additional Grant of Patent Rights Version 2
|
|
||||||
|
|
||||||
"Software" means the httpdown software distributed by Facebook, Inc.
|
|
||||||
|
|
||||||
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
|
|
||||||
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
|
|
||||||
(subject to the termination provision below) license under any Necessary
|
|
||||||
Claims, to make, have made, use, sell, offer to sell, import, and otherwise
|
|
||||||
transfer the Software. For avoidance of doubt, no license is granted under
|
|
||||||
Facebook’s rights in any patent claims that are infringed by (i) modifications
|
|
||||||
to the Software made by you or any third party or (ii) the Software in
|
|
||||||
combination with any software or other technology.
|
|
||||||
|
|
||||||
The license granted hereunder will terminate, automatically and without notice,
|
|
||||||
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
|
|
||||||
directly or indirectly, or take a direct financial interest in, any Patent
|
|
||||||
Assertion: (i) against Facebook or any of its subsidiaries or corporate
|
|
||||||
affiliates, (ii) against any party if such Patent Assertion arises in whole or
|
|
||||||
in part from any software, technology, product or service of Facebook or any of
|
|
||||||
its subsidiaries or corporate affiliates, or (iii) against any party relating
|
|
||||||
to the Software. Notwithstanding the foregoing, if Facebook or any of its
|
|
||||||
subsidiaries or corporate affiliates files a lawsuit alleging patent
|
|
||||||
infringement against you in the first instance, and you respond by filing a
|
|
||||||
patent infringement counterclaim in that lawsuit against that party that is
|
|
||||||
unrelated to the Software, the license granted hereunder will not terminate
|
|
||||||
under section (i) of this paragraph due to such counterclaim.
|
|
||||||
|
|
||||||
A "Necessary Claim" is a claim of a patent owned by Facebook that is
|
|
||||||
necessarily infringed by the Software standing alone.
|
|
||||||
|
|
||||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
|
||||||
or contributory infringement or inducement to infringe any patent, including a
|
|
||||||
cross-claim or counterclaim.
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
httpdown [](https://travis-ci.org/facebookgo/httpdown)
|
|
||||||
========
|
|
||||||
|
|
||||||
Documentation: https://godoc.org/github.com/facebookgo/httpdown
|
|
||||||
|
|
||||||
Package httpdown provides a library that makes it easy to build a HTTP server
|
|
||||||
that can be shutdown gracefully (that is, without dropping any connections).
|
|
||||||
|
|
||||||
If you want graceful restart and not just graceful shutdown, look at the
|
|
||||||
[grace](https://github.com/facebookgo/grace) package which uses this package
|
|
||||||
underneath but also provides graceful restart.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Demo HTTP Server with graceful termination:
|
|
||||||
https://github.com/facebookgo/httpdown/blob/master/httpdown_example/main.go
|
|
||||||
|
|
||||||
1. Install the demo application
|
|
||||||
|
|
||||||
go get github.com/facebookgo/httpdown/httpdown_example
|
|
||||||
|
|
||||||
1. Start it in the first terminal
|
|
||||||
|
|
||||||
httpdown_example
|
|
||||||
|
|
||||||
This will output something like:
|
|
||||||
|
|
||||||
2014/11/18 21:57:50 serving on http://127.0.0.1:8080/ with pid 17
|
|
||||||
|
|
||||||
1. In a second terminal start a slow HTTP request
|
|
||||||
|
|
||||||
curl 'http://localhost:8080/?duration=20s'
|
|
||||||
|
|
||||||
1. In a third terminal trigger a graceful shutdown (using the pid from your output):
|
|
||||||
|
|
||||||
kill -TERM 17
|
|
||||||
|
|
||||||
This will demonstrate that the slow request was served before the server was
|
|
||||||
shutdown. You could also have used `Ctrl-C` instead of `kill` as the example
|
|
||||||
application triggers graceful shutdown on TERM or INT signals.
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- go get -v code.google.com/p/go.tools/cmd/vet
|
|
||||||
- go get -v github.com/golang/lint/golint
|
|
||||||
- go get -v code.google.com/p/go.tools/cmd/cover
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go install -race -v std
|
|
||||||
- go get -race -t -v ./...
|
|
||||||
- go install -race -v ./...
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go vet ./...
|
|
||||||
- $HOME/gopath/bin/golint .
|
|
||||||
- go test -cpu=2 -race -v ./...
|
|
||||||
- go test -cpu=2 -covermode=atomic ./...
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
BSD License
|
|
||||||
|
|
||||||
For stats software
|
|
||||||
|
|
||||||
Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
* Neither the name Facebook nor the names of its contributors may be used to
|
|
||||||
endorse or promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
Additional Grant of Patent Rights Version 2
|
|
||||||
|
|
||||||
"Software" means the stats software distributed by Facebook, Inc.
|
|
||||||
|
|
||||||
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
|
|
||||||
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
|
|
||||||
(subject to the termination provision below) license under any Necessary
|
|
||||||
Claims, to make, have made, use, sell, offer to sell, import, and otherwise
|
|
||||||
transfer the Software. For avoidance of doubt, no license is granted under
|
|
||||||
Facebook’s rights in any patent claims that are infringed by (i) modifications
|
|
||||||
to the Software made by you or any third party or (ii) the Software in
|
|
||||||
combination with any software or other technology.
|
|
||||||
|
|
||||||
The license granted hereunder will terminate, automatically and without notice,
|
|
||||||
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
|
|
||||||
directly or indirectly, or take a direct financial interest in, any Patent
|
|
||||||
Assertion: (i) against Facebook or any of its subsidiaries or corporate
|
|
||||||
affiliates, (ii) against any party if such Patent Assertion arises in whole or
|
|
||||||
in part from any software, technology, product or service of Facebook or any of
|
|
||||||
its subsidiaries or corporate affiliates, or (iii) against any party relating
|
|
||||||
to the Software. Notwithstanding the foregoing, if Facebook or any of its
|
|
||||||
subsidiaries or corporate affiliates files a lawsuit alleging patent
|
|
||||||
infringement against you in the first instance, and you respond by filing a
|
|
||||||
patent infringement counterclaim in that lawsuit against that party that is
|
|
||||||
unrelated to the Software, the license granted hereunder will not terminate
|
|
||||||
under section (i) of this paragraph due to such counterclaim.
|
|
||||||
|
|
||||||
A "Necessary Claim" is a claim of a patent owned by Facebook that is
|
|
||||||
necessarily infringed by the Software standing alone.
|
|
||||||
|
|
||||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
|
||||||
or contributory infringement or inducement to infringe any patent, including a
|
|
||||||
cross-claim or counterclaim.
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
stats [](https://travis-ci.org/facebookgo/stats)
|
|
||||||
=====
|
|
||||||
|
|
||||||
Documentation: https://godoc.org/github.com/facebookgo/stats
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
// Package stats defines a lightweight interface for collecting statistics. It
|
|
||||||
// doesn't provide an implementation, just the shared interface.
|
|
||||||
package stats
|
|
||||||
|
|
||||||
// Client provides methods to collection statistics.
|
|
||||||
type Client interface {
|
|
||||||
// BumpAvg bumps the average for the given key.
|
|
||||||
BumpAvg(key string, val float64)
|
|
||||||
|
|
||||||
// BumpSum bumps the sum for the given key.
|
|
||||||
BumpSum(key string, val float64)
|
|
||||||
|
|
||||||
// BumpHistogram bumps the histogram for the given key.
|
|
||||||
BumpHistogram(key string, val float64)
|
|
||||||
|
|
||||||
// BumpTime is a special version of BumpHistogram which is specialized for
|
|
||||||
// timers. Calling it starts the timer, and it returns a value on which End()
|
|
||||||
// can be called to indicate finishing the timer. A convenient way of
|
|
||||||
// recording the duration of a function is calling it like such at the top of
|
|
||||||
// the function:
|
|
||||||
//
|
|
||||||
// defer s.BumpTime("my.function").End()
|
|
||||||
BumpTime(key string) interface {
|
|
||||||
End()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefixClient adds multiple keys for the same value, with each prefix
|
|
||||||
// added to the key and calls the underlying client.
|
|
||||||
func PrefixClient(prefixes []string, client Client) Client {
|
|
||||||
return &prefixClient{
|
|
||||||
Prefixes: prefixes,
|
|
||||||
Client: client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type prefixClient struct {
|
|
||||||
Prefixes []string
|
|
||||||
Client Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *prefixClient) BumpAvg(key string, val float64) {
|
|
||||||
for _, prefix := range p.Prefixes {
|
|
||||||
p.Client.BumpAvg(prefix+key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *prefixClient) BumpSum(key string, val float64) {
|
|
||||||
for _, prefix := range p.Prefixes {
|
|
||||||
p.Client.BumpSum(prefix+key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *prefixClient) BumpHistogram(key string, val float64) {
|
|
||||||
for _, prefix := range p.Prefixes {
|
|
||||||
p.Client.BumpHistogram(prefix+key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *prefixClient) BumpTime(key string) interface {
|
|
||||||
End()
|
|
||||||
} {
|
|
||||||
var m multiEnder
|
|
||||||
for _, prefix := range p.Prefixes {
|
|
||||||
m = append(m, p.Client.BumpTime(prefix+key))
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiEnder combines many enders together.
|
|
||||||
type multiEnder []interface {
|
|
||||||
End()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m multiEnder) End() {
|
|
||||||
for _, e := range m {
|
|
||||||
e.End()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HookClient is useful for testing. It provides optional hooks for each
|
|
||||||
// expected method in the interface, which if provided will be called. If a
|
|
||||||
// hook is not provided, it will be ignored.
|
|
||||||
type HookClient struct {
|
|
||||||
BumpAvgHook func(key string, val float64)
|
|
||||||
BumpSumHook func(key string, val float64)
|
|
||||||
BumpHistogramHook func(key string, val float64)
|
|
||||||
BumpTimeHook func(key string) interface {
|
|
||||||
End()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BumpAvg will call BumpAvgHook if defined.
|
|
||||||
func (c *HookClient) BumpAvg(key string, val float64) {
|
|
||||||
if c.BumpAvgHook != nil {
|
|
||||||
c.BumpAvgHook(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BumpSum will call BumpSumHook if defined.
|
|
||||||
func (c *HookClient) BumpSum(key string, val float64) {
|
|
||||||
if c.BumpSumHook != nil {
|
|
||||||
c.BumpSumHook(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BumpHistogram will call BumpHistogramHook if defined.
|
|
||||||
func (c *HookClient) BumpHistogram(key string, val float64) {
|
|
||||||
if c.BumpHistogramHook != nil {
|
|
||||||
c.BumpHistogramHook(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BumpTime will call BumpTimeHook if defined.
|
|
||||||
func (c *HookClient) BumpTime(key string) interface {
|
|
||||||
End()
|
|
||||||
} {
|
|
||||||
if c.BumpTimeHook != nil {
|
|
||||||
return c.BumpTimeHook(key)
|
|
||||||
}
|
|
||||||
return NoOpEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
type noOpEnd struct{}
|
|
||||||
|
|
||||||
func (n noOpEnd) End() {}
|
|
||||||
|
|
||||||
// NoOpEnd provides a dummy value for use in tests as valid return value for
|
|
||||||
// BumpTime().
|
|
||||||
var NoOpEnd = noOpEnd{}
|
|
||||||
|
|
||||||
// BumpAvg calls BumpAvg on the Client if it isn't nil. This is useful when a
|
|
||||||
// component has an optional stats.Client.
|
|
||||||
func BumpAvg(c Client, key string, val float64) {
|
|
||||||
if c != nil {
|
|
||||||
c.BumpAvg(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BumpSum calls BumpSum on the Client if it isn't nil. This is useful when a
|
|
||||||
// component has an optional stats.Client.
|
|
||||||
func BumpSum(c Client, key string, val float64) {
|
|
||||||
if c != nil {
|
|
||||||
c.BumpSum(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BumpHistogram calls BumpHistogram on the Client if it isn't nil. This is
|
|
||||||
// useful when a component has an optional stats.Client.
|
|
||||||
func BumpHistogram(c Client, key string, val float64) {
|
|
||||||
if c != nil {
|
|
||||||
c.BumpHistogram(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BumpTime calls BumpTime on the Client if it isn't nil. If the Client is nil
|
|
||||||
// it still returns a valid return value which will be a no-op. This is useful
|
|
||||||
// when a component has an optional stats.Client.
|
|
||||||
func BumpTime(c Client, key string) interface {
|
|
||||||
End()
|
|
||||||
} {
|
|
||||||
if c != nil {
|
|
||||||
return c.BumpTime(key)
|
|
||||||
}
|
|
||||||
return NoOpEnd
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue