dmitri.shuralyov.com/website/gido/...

Shutdown on SIGINT.

Add and propagate an application lifetime context. It's cancelled on
SIGINT.

Make logging more simple and minimal.
dmitshur committed 2 years ago commit 594305424f41022eeb7519991ea8ca267e3141ad
main.go
@@ -1,18 +1,20 @@
 // gido is the command that powers the https://goissues.org website.
 package main
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"io"
 	"io/ioutil"
 	"log"
 	"mime"
 	"net/http"
 	"net/url"
 	"os"
+	"os/signal"
 	"path"
 	"sort"
 	"strings"
 
 	"dmitri.shuralyov.com/website/gido/assets"
@@ -31,17 +33,25 @@ var (
 )
 
 func main() {
 	flag.Parse()
 
-	err := run()
+	ctx, cancel := context.WithCancel(context.Background())
+	go func() {
+		sigint := make(chan os.Signal, 1)
+		signal.Notify(sigint, os.Interrupt)
+		<-sigint
+		cancel()
+	}()
+
+	err := run(ctx)
 	if err != nil {
 		log.Fatalln(err)
 	}
 }
 
-func run() error {
+func run(ctx context.Context) error {
 	if err := mime.AddExtensionType(".woff2", "font/woff2"); err != nil {
 		return err
 	}
 
 	var analyticsHTML []byte
@@ -51,26 +61,35 @@ func run() error {
 		if err != nil {
 			return err
 		}
 	}
 
-	printServingAt(*httpFlag)
-	err := http.ListenAndServe(*httpFlag, top{&errorHandler{handler: (&handler{
+	server := &http.Server{Addr: *httpFlag, Handler: top{&errorHandler{handler: (&handler{
 		analyticsHTML: analyticsHTML,
 		fontsHandler:  httpgzip.FileServer(assets.Fonts, httpgzip.FileServerOptions{ServeError: httpgzip.Detailed}),
 		assetsHandler: httpgzip.FileServer(assets.Assets, httpgzip.FileServerOptions{ServeError: httpgzip.Detailed}),
-		s:             newService(),
-	}).ServeHTTP}})
-	return err
-}
+		s:             newService(ctx),
+	}).ServeHTTP}}}
 
-func printServingAt(addr string) {
-	hostPort := addr
-	if strings.HasPrefix(hostPort, ":") {
-		hostPort = "localhost" + hostPort
+	go func() {
+		<-ctx.Done()
+		err := server.Close()
+		if err != nil {
+			log.Println("server.Close:", err)
+		}
+	}()
+
+	log.Println("Starting HTTP server.")
+
+	err := server.ListenAndServe()
+	if err != http.ErrServerClosed {
+		log.Println("server.ListenAndServe:", err)
 	}
-	fmt.Printf("serving at http://%s/\n", hostPort)
+
+	log.Println("Ended HTTP server.")
+
+	return nil
 }
 
 // handler handles all goissues requests. It acts like a request multiplexer,
 // choosing from various endpoints and parsing the import path from URL.
 type handler struct {
service.go
@@ -29,11 +29,11 @@ type service struct {
 
 type pkgIssues struct {
 	Open, Closed []issues.Issue
 }
 
-func newService() *service {
+func newService(ctx context.Context) *service {
 	packageIssues := emptyPackages()
 
 	// Initialize list of packages sorted by import path, standard library first.
 	var packages []string
 	for p := range packageIssues {
@@ -51,11 +51,11 @@ func newService() *service {
 
 	s := &service{
 		PackageIssues: packageIssues,
 		Packages:      packages,
 	}
-	go s.poll()
+	go s.poll(ctx)
 	return s
 }
 
 func emptyPackages() map[string]*pkgIssues {
 	// Initialize places for issues, using existing packages
@@ -93,43 +93,41 @@ func isStandard(p string) bool {
 		p = p[:i]
 	}
 	return !strings.Contains(p, ".")
 }
 
-func (s *service) poll() {
-	corpus, repo, err := initCorpus()
+func (s *service) poll(ctx context.Context) {
+	corpus, repo, err := initCorpus(ctx)
 	if err != nil {
 		log.Fatalln("poll: initial initCorpus failed:", err)
 	}
 
 	for {
 		packageIssues := packageIssues(repo)
 		s.PackageIssuesMu.Lock()
 		s.PackageIssues = packageIssues
 		s.PackageIssuesMu.Unlock()
 		for {
-			started := time.Now()
-			updateError := corpus.Update(context.Background())
+			updateError := corpus.Update(ctx)
 			if updateError == maintner.ErrSplit {
 				log.Println("corpus.Update: Corpus out of sync. Re-fetching corpus.")
-				corpus, repo, err = initCorpus()
+				corpus, repo, err = initCorpus(ctx)
 				if err != nil {
 					log.Fatalln("poll: post-ErrSplit initCorpus failed:", err)
 				}
 			} else if updateError != nil {
 				log.Printf("corpus.Update: %v; sleeping 15s", updateError)
 				time.Sleep(15 * time.Second)
 				continue
 			}
-			log.Printf("got corpus update after %v", time.Since(started))
 			break
 		}
 	}
 }
 
-func initCorpus() (*maintner.Corpus, *maintner.GitHubRepo, error) {
-	corpus, err := godata.Get(context.Background())
+func initCorpus(ctx context.Context) (*maintner.Corpus, *maintner.GitHubRepo, error) {
+	corpus, err := godata.Get(ctx)
 	if err != nil {
 		return nil, nil, fmt.Errorf("godata.Get: %v", err)
 	}
 	repo := corpus.GitHub().Repo("golang", "go")
 	if repo == nil {