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 4 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 {