├── .gitignore ├── service.go ├── go.mod ├── .github ├── workflows │ └── test.yaml └── FUNDING.yml ├── service_http.go ├── service_grpc.go ├── lifetime_test.go ├── README.md ├── lifetime.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea/ -------------------------------------------------------------------------------- /service.go: -------------------------------------------------------------------------------- 1 | package lifetime 2 | 3 | // Service defines a single service in an application. 4 | type Service interface { 5 | // Start will start the service. 6 | // This is a blocking call and should block for the lifetime of the service. 7 | // Returns an error which is treated as fatal. 8 | Start() error 9 | // Stop will stop the service. 10 | // Stop is not called if Start returned an error. 11 | Stop() 12 | } 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tomwright/lifetime 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/golang/protobuf v1.4.2 // indirect 7 | golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect 8 | golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect 9 | golang.org/x/text v0.3.3 // indirect 10 | google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 // indirect 11 | google.golang.org/grpc v1.31.0 // indirect 12 | google.golang.org/protobuf v1.25.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.13.x] 8 | platform: [ubuntu-latest] 9 | runs-on: ${{ matrix.platform }} 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v1 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - name: Checkout code 16 | uses: actions/checkout@v1 17 | - uses: actions/cache@v1 18 | with: 19 | path: ~/go/pkg/mod 20 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 21 | restore-keys: | 22 | ${{ runner.os }}-go- 23 | - name: Test 24 | run: go test -race ./... -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: TomWright # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /service_http.go: -------------------------------------------------------------------------------- 1 | package lifetime 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // NewHTTPService returns a service that will run listen and serve the given 8 | // HTTP server. 9 | func NewHTTPService(server *http.Server) Service { 10 | return &httpService{ 11 | server: server, 12 | } 13 | } 14 | 15 | // httpService is an implementation of Service that will listen and serve the given 16 | // HTTP server. 17 | type httpService struct { 18 | server *http.Server 19 | } 20 | 21 | // Start will start the service. 22 | // This is a blocking call and should block for the lifetime of the service. 23 | // Returns an error which is treated as fatal. 24 | func (service *httpService) Start() error { 25 | err := service.server.ListenAndServe() 26 | if err == nil { 27 | return nil 28 | } 29 | // ErrServerClosed is returned when we call service.Close() from Service.Stop 30 | // so we shouldn't treat it as a breaking error. 31 | if err == http.ErrServerClosed { 32 | return nil 33 | } 34 | return err 35 | } 36 | 37 | // Stop will stop the service. 38 | // Stop is not called if Start returned an error. 39 | func (service *httpService) Stop() { 40 | _ = service.server.Close() 41 | } 42 | -------------------------------------------------------------------------------- /service_grpc.go: -------------------------------------------------------------------------------- 1 | package lifetime 2 | 3 | import ( 4 | "fmt" 5 | "google.golang.org/grpc" 6 | "net" 7 | ) 8 | 9 | // NewGRPCService returns a service that will run listen and serve the given 10 | // GRPC server. 11 | func NewGRPCService(server *grpc.Server, listenAddress string) Service { 12 | return &grpcService{ 13 | server: server, 14 | listenAddress: listenAddress, 15 | } 16 | } 17 | 18 | // grpcService is an implementation of Service that will listen and serve the given 19 | // HTTP server. 20 | type grpcService struct { 21 | server *grpc.Server 22 | listenAddress string 23 | } 24 | 25 | // Start will start the service. 26 | // This is a blocking call and should block for the lifetime of the service. 27 | // Returns an error which is treated as fatal. 28 | func (service *grpcService) Start() error { 29 | lis, err := net.Listen("tcp", service.listenAddress) 30 | if err != nil { 31 | return fmt.Errorf("could not listen on tcp address: %w", err) 32 | } 33 | err = service.server.Serve(lis) 34 | if err == nil { 35 | return nil 36 | } 37 | // ErrServerStopped is returned when we call server.Close() from Service.Stop 38 | // so we shouldn't treat it as a breaking error. 39 | if err == grpc.ErrServerStopped { 40 | return nil 41 | } 42 | return err 43 | } 44 | 45 | // Stop will stop the service. 46 | // Stop is not called if Start returned an error. 47 | func (service *grpcService) Stop() { 48 | service.server.GracefulStop() 49 | } 50 | -------------------------------------------------------------------------------- /lifetime_test.go: -------------------------------------------------------------------------------- 1 | package lifetime_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/tomwright/lifetime" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type testService struct { 12 | name string 13 | stop bool 14 | stopMu sync.RWMutex 15 | startupDuration time.Duration 16 | shutdownDuration time.Duration 17 | } 18 | 19 | func (s *testService) Start() error { 20 | time.Sleep(s.startupDuration) 21 | fmt.Printf("%s: Started\n", s.name) 22 | for { 23 | s.stopMu.RLock() 24 | if s.stop { 25 | s.stopMu.RUnlock() 26 | break 27 | } 28 | s.stopMu.RUnlock() 29 | time.Sleep(time.Millisecond * 10) 30 | } 31 | return nil 32 | } 33 | 34 | func (s *testService) Stop() { 35 | s.stopMu.Lock() 36 | defer s.stopMu.Unlock() 37 | time.Sleep(s.shutdownDuration) 38 | fmt.Printf("%s: Stopped\n", s.name) 39 | s.stop = true 40 | } 41 | 42 | // ExampleLifetime shows a basic example of how you can use Lifetime. 43 | func ExampleLifetime() { 44 | // Create a lifetime and initialises it. 45 | lt := lifetime.New(context.Background()). 46 | Init() 47 | 48 | fmt.Printf("Starting services\n") 49 | 50 | // Service A takes 100ms to start up and 800ms to shutdown. 51 | serviceA := &testService{ 52 | name: "a", 53 | startupDuration: time.Millisecond * 100, 54 | shutdownDuration: time.Millisecond * 800, 55 | } 56 | // Service B takes 800ms to start up and 100ms to shutdown. 57 | serviceB := &testService{ 58 | name: "b", 59 | startupDuration: time.Millisecond * 800, 60 | shutdownDuration: time.Millisecond * 100, 61 | } 62 | 63 | // Start both services. 64 | lt.Start(serviceA) 65 | lt.Start(serviceB) 66 | 67 | // Wait some time and trigger an application shutdown. 68 | go func() { 69 | <-time.After(time.Millisecond * 1500) 70 | fmt.Printf("Shutting down\n") 71 | lt.Shutdown() 72 | }() 73 | 74 | // Wait for all services to stop. 75 | lt.Wait() 76 | 77 | fmt.Printf("Shutdown\n") 78 | 79 | // Output: 80 | // Starting services 81 | // a: Started 82 | // b: Started 83 | // Shutting down 84 | // b: Stopped 85 | // a: Stopped 86 | // Shutdown 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lifetime 2 | 3 | ![Test](https://github.com/TomWright/lifetime/workflows/Test/badge.svg) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/TomWright/lifetime)](https://goreportcard.com/report/github.com/TomWright/lifetime) 5 | [![Documentation](https://godoc.org/github.com/TomWright/lifetime?status.svg)](https://godoc.org/github.com/TomWright/lifetime) 6 | 7 | Lifetime is a basic package to help you manage the lifetime of an application with multiple routines running at once. 8 | 9 | The main benefit of this module is that it allows you to easily manage graceful shutdowns without most of the boilerplate code that goes along with it. 10 | 11 | ## Deprecated 12 | 13 | Please note that I am deprecating this package in favour of [grace](https://github.com/TomWright/grace). 14 | 15 | Grace has a simplified API and is split across different modules to reduce the number of forced dependencies. 16 | 17 | ## Installation 18 | ``` 19 | go get github.com/tomwright/lifetime 20 | ``` 21 | 22 | ## Usage 23 | 24 | Example usage can be found on godoc. 25 | 26 | ``` 27 | // Create and initialise the lifetime. 28 | lt := lifetime.New(context.Background()).Init() 29 | 30 | // Create HTTP server. 31 | mux := http.NewServeMux() 32 | mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 33 | writer.Write([]byte("Hello world")) 34 | }) 35 | server := &http.Server{ 36 | Addr: ":80", 37 | Handler: mux, 38 | } 39 | 40 | // Create HTTP service, giving it the HTTP server. 41 | service := lifetime.NewHTTPService(server) 42 | 43 | // Start the service. 44 | lt.Start(service) 45 | 46 | go func() { 47 | // At some point in time call lt.Shutdown. 48 | // lt.Shutdown would also be executed when a shutdown signal is received. 49 | time.Sleep(time.Second * 5) 50 | lt.Shutdown() 51 | }() 52 | 53 | // Wait for all services to shutdown 54 | lt.Wait() 55 | ``` 56 | 57 | ## Service 58 | 59 | A service is a single service within your application that can be started and stopped. 60 | 61 | ### Graceful shutdown 62 | A graceful shutdown causes all of the `Service.Stop` funcs to be executed causing all services to begin their graceful shutdown. 63 | 64 | You can use `lifetime.Wait` to wait for the services to be stopped. 65 | 66 | A graceful shutdown will be triggered when: 67 | - A server `Start` func returns an error. 68 | - A `syscall.SIGINT` or `syscall.SIGTERM` signal is received. 69 | - `lifetime.Shutdown` is called. 70 | 71 | ### Immediate shutdown 72 | An immediate shutdown uses `os.Exit` to immediately stop the application. 73 | 74 | This will occur when: 75 | - Multiple `syscall.SIGINT` or `syscall.SIGTERM` signals are received. 76 | - A `syscall.SIGKILL` signal is received. 77 | 78 | ### Services 79 | 80 | Some services are provided for you to use, but you can easily create your own services by implementing the `lifetime.Service` interface. 81 | 82 | #### HTTP Server 83 | 84 | ``` 85 | // Create HTTP server. 86 | mux := http.NewServeMux() 87 | mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 88 | writer.Write([]byte("Hello world")) 89 | }) 90 | server := &http.Server{ 91 | Addr: ":80", 92 | Handler: mux, 93 | } 94 | 95 | // Create HTTP service, giving it the HTTP server. 96 | service := lifetime.NewHTTPService(server) 97 | 98 | // Start the service. 99 | lt.Start(service) 100 | ``` 101 | 102 | #### GRPC Server 103 | 104 | ``` 105 | // Create and register GRPC server. 106 | var server *grpc.Server 107 | // Register GRPC server implementation... 108 | 109 | // Create GRPC service, giving it the GRPC server and a listen address. 110 | service := lifetime.NewGRPCService(server, ":9000") 111 | 112 | // Start the service. 113 | lt.Start(service) 114 | ``` 115 | -------------------------------------------------------------------------------- /lifetime.go: -------------------------------------------------------------------------------- 1 | package lifetime 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "sync" 10 | "syscall" 11 | ) 12 | 13 | var ( 14 | // ErrShutdownSignalReceived is used when a shutdown signal is received. 15 | // It will cause a graceful shutdown. 16 | ErrShutdownSignalReceived = errors.New("shutdown signal received") 17 | 18 | // ErrImmediateShutdownSignalReceived is used when a shutdown signal is received for the second time. 19 | // It will cause an immediate shutdown. 20 | ErrImmediateShutdownSignalReceived = errors.New("immediate shutdown signal received") 21 | ) 22 | 23 | // New returns a new Lifetime instance that can be used to control 24 | // the lifetime of an application. 25 | func New(ctx context.Context) *Lifetime { 26 | ctx, cancel := context.WithCancel(ctx) 27 | return &Lifetime{ 28 | ctx: ctx, 29 | cancelFunc: cancel, 30 | serviceWg: &sync.WaitGroup{}, 31 | errCh: make(chan error), 32 | } 33 | } 34 | 35 | // Lifetime contains some basic functionality you can use to control the lifetime of an application. 36 | type Lifetime struct { 37 | ctx context.Context 38 | cancelFunc context.CancelFunc 39 | serviceWg *sync.WaitGroup 40 | errCh chan error 41 | } 42 | 43 | // Init starts up the required routines for the lifetime instance to work as expected. 44 | func (lifetime *Lifetime) Init() *Lifetime { 45 | lifetime.handleErrors() 46 | lifetime.handleShutdownSignals() 47 | return lifetime 48 | } 49 | 50 | // Context returns a context that should be used throughout the runtime of the application. 51 | // When a shutdown of the application is triggered this context will be closed. 52 | func (lifetime *Lifetime) Context() context.Context { 53 | return lifetime.ctx 54 | } 55 | 56 | // Done returns a channel that's closed when all work should be stopped. 57 | // This is really just the Done channel of the lifetime context. 58 | func (lifetime *Lifetime) Done() <-chan struct{} { 59 | return lifetime.ctx.Done() 60 | } 61 | 62 | // Shutdown triggers a graceful shutdown of the application. 63 | func (lifetime *Lifetime) Shutdown() { 64 | lifetime.cancelFunc() 65 | } 66 | 67 | // Wait will block until all services registered with the Lifetime have finished execution. 68 | func (lifetime *Lifetime) Wait() { 69 | lifetime.serviceWg.Wait() 70 | } 71 | 72 | // Start will start the given service. 73 | // It also ensures that the service wait group is updated as expected. 74 | func (lifetime *Lifetime) Start(svc Service) { 75 | lifetime.serviceWg.Add(1) 76 | go lifetime.start(svc) 77 | } 78 | 79 | // start executes a service in a go routine. 80 | // It ensures that the service wait group is updated, and that the service Stop func is 81 | // executed when an application shutdown is triggered. 82 | func (lifetime *Lifetime) start(svc Service) { 83 | defer lifetime.serviceWg.Done() 84 | 85 | startErrs := make(chan error) 86 | startWg := &sync.WaitGroup{} 87 | 88 | startWg.Add(1) 89 | go func() { 90 | defer startWg.Done() 91 | err := svc.Start() 92 | if err != nil { 93 | startErrs <- err 94 | } 95 | }() 96 | 97 | select { 98 | case startErr := <-startErrs: 99 | // Something went wrong during start-up. 100 | // Report the error. 101 | lifetime.errCh <- startErr 102 | case <-lifetime.ctx.Done(): 103 | // The application wants us to shutdown. 104 | // Stop the service and wait for the start func to finish. 105 | svc.Stop() 106 | startWg.Wait() 107 | } 108 | } 109 | 110 | // handleShutdownSignals runs a go routine that listens for shutdown signals from the os 111 | // and sends an ErrShutdownSignalReceived to the error chan when the application is told to shutdown. 112 | func (lifetime *Lifetime) handleShutdownSignals() { 113 | signals := make(chan os.Signal, 1) 114 | 115 | signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL) 116 | 117 | go func() { 118 | count := 0 119 | for { 120 | sig := <-signals 121 | count++ 122 | if count > 1 || sig == syscall.SIGKILL { 123 | lifetime.errCh <- ErrImmediateShutdownSignalReceived 124 | continue 125 | } 126 | lifetime.errCh <- ErrShutdownSignalReceived 127 | } 128 | }() 129 | } 130 | 131 | // handleErrors starts a go routine that listens on the error channel and logs errors. 132 | func (lifetime *Lifetime) handleErrors() { 133 | go func() { 134 | for { 135 | err, ok := <-lifetime.errCh 136 | if !ok { 137 | lifetime.cancelFunc() 138 | return 139 | } 140 | 141 | if err == ErrImmediateShutdownSignalReceived { 142 | os.Exit(1) 143 | } 144 | 145 | log.Printf("lifetime error received: %s", err.Error()) 146 | 147 | lifetime.Shutdown() 148 | } 149 | }() 150 | } 151 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 6 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 7 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 8 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 9 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 10 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 11 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 12 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 14 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 15 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 16 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 17 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 18 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 19 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 20 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 21 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 22 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 23 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 24 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 25 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 26 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 27 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 30 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 31 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 32 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 33 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 34 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 35 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 36 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 37 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 38 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 39 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 40 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 41 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 42 | golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= 43 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 44 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 45 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 46 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 47 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 48 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 49 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 50 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 51 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= 54 | golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 55 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 56 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 57 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 58 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 59 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 60 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 61 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 62 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 63 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 64 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 65 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 66 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 67 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 68 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 69 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 70 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 71 | google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 h1:wboULUXGF3c5qdUnKp+6gLAccE6PRpa/czkYvQ4UXv8= 72 | google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 73 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 74 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 75 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 76 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 77 | google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= 78 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 79 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 80 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 81 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 82 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 83 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 84 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 85 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 86 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 87 | google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= 88 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 89 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 90 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 91 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 92 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 93 | --------------------------------------------------------------------------------