From d40fe1021392da2c74bc6156ad2734071c615495 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Fri, 27 Nov 2020 17:26:39 -0700 Subject: rewrite CoP post to use new examples --- assets/component-oriented-design/v2/main.go | 122 +++++++--------------------- 1 file changed, 29 insertions(+), 93 deletions(-) (limited to 'assets/component-oriented-design/v2') diff --git a/assets/component-oriented-design/v2/main.go b/assets/component-oriented-design/v2/main.go index e9a1eae..fb5773c 100644 --- a/assets/component-oriented-design/v2/main.go +++ b/assets/component-oriented-design/v2/main.go @@ -1,9 +1,9 @@ package main import ( - "context" "encoding/json" "errors" + "flag" "fmt" "io" "io/ioutil" @@ -12,7 +12,6 @@ import ( "net" "net/http" "os" - "os/signal" "sort" "strconv" "sync" @@ -41,11 +40,7 @@ type scoreboard struct { scoresM map[string]int scoresLock sync.Mutex - // The cleanup method closes cleanupCh to signal to all scoreboard's running - // go-routines to clean themselves up, and cleanupWG is then used to wait - // for those goroutines to do so. - cleanupCh chan struct{} - cleanupWG sync.WaitGroup + pointsOnCorrect, pointsOnIncorrect int // this field will only be set in tests, and is used to synchronize with the // the for-select loop in saveLoop. @@ -55,7 +50,7 @@ type scoreboard struct { // newScoreboard initializes a scoreboard using scores saved in the given File // (which may be empty). The scoreboard will rewrite the save file with the // latest scores everytime saveTicker is written to. -func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger) (*scoreboard, error) { +func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger, pointsOnCorrect, pointsOnIncorrect int) (*scoreboard, error) { fileBody, err := ioutil.ReadAll(file) if err != nil { return nil, fmt.Errorf("reading saved scored: %w", err) @@ -69,36 +64,23 @@ func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger) (*scor } scoreboard := &scoreboard{ - file: file, - scoresM: scoresM, - cleanupCh: make(chan struct{}), - saveLoopWaitCh: make(chan struct{}), + file: file, + scoresM: scoresM, + pointsOnCorrect: pointsOnCorrect, + pointsOnIncorrect: pointsOnIncorrect, + saveLoopWaitCh: make(chan struct{}), } - scoreboard.cleanupWG.Add(1) - go func() { - scoreboard.saveLoop(saveTicker, logger) - scoreboard.cleanupWG.Done() - }() + go scoreboard.saveLoop(saveTicker, logger) return scoreboard, nil } -func (s *scoreboard) cleanup() error { - close(s.cleanupCh) - s.cleanupWG.Wait() - - if err := s.save(); err != nil { - return fmt.Errorf("saving scores during cleanup: %w", err) - } - return nil -} - func (s *scoreboard) guessedCorrect(name string) int { s.scoresLock.Lock() defer s.scoresLock.Unlock() - s.scoresM[name] += 1000 + s.scoresM[name] += s.pointsOnCorrect return s.scoresM[name] } @@ -106,7 +88,7 @@ func (s *scoreboard) guessedIncorrect(name string) int { s.scoresLock.Lock() defer s.scoresLock.Unlock() - s.scoresM[name] -= 1 + s.scoresM[name] += s.pointsOnIncorrect return s.scoresM[name] } @@ -141,8 +123,6 @@ func (s *scoreboard) saveLoop(ticker <-chan time.Time, logger Logger) { if err := s.save(); err != nil { logger.Printf("error saving scoreboard to file: %v", err) } - case <-s.cleanupCh: - return case <-s.saveLoopWaitCh: // test will unblock, nothing to do here. } @@ -295,90 +275,46 @@ func newHTTPServer(listener net.Listener, httpHandlers *httpHandlers, logger Log return server } -func (s *httpServer) cleanup() error { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - if err := s.httpServer.Shutdown(ctx); err != nil { - return fmt.Errorf("shutting down http server: %w", err) - } - return <-s.errCh -} - //////////////////////////////////////////////////////////////////////////////// // main -const ( - saveFilePath = "./save.json" - listenAddr = ":8888" - saveInterval = 5 * time.Second -) - func main() { + saveFilePath := flag.String("save-file", "./save.json", "File used to save scores") + listenAddr := flag.String("listen-addr", ":8888", "Address to listen for HTTP requests on") + saveInterval := flag.Duration("save-interval", 5*time.Second, "How often to resave scores") + pointsOnCorrect := flag.Int("points-on-correct", 1000, "Amount to change a user's score by upon a correct score") + pointsOnIncorrect := flag.Int("points-on-incorrect", -1, "Amount to change a user's score by upon an incorrect score") + flag.Parse() + logger := log.New(os.Stdout, "", log.LstdFlags) - logger.Printf("opening scoreboard save file %q", saveFilePath) - file, err := os.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0644) + logger.Printf("opening scoreboard save file %q", *saveFilePath) + file, err := os.OpenFile(*saveFilePath, os.O_RDWR|os.O_CREATE, 0644) if err != nil { - logger.Fatalf("failed to open file %q: %v", saveFilePath, err) + logger.Fatalf("failed to open file %q: %v", *saveFilePath, err) } - saveTicker := time.NewTicker(saveInterval) + saveTicker := time.NewTicker(*saveInterval) randSrc := rand.New(rand.NewSource(time.Now().UnixNano())) logger.Printf("initializing scoreboard") - scoreboard, err := newScoreboard(file, saveTicker.C, logger) + scoreboard, err := newScoreboard(file, saveTicker.C, logger, *pointsOnCorrect, *pointsOnIncorrect) if err != nil { logger.Fatalf("failed to initialize scoreboard: %v", err) } - logger.Printf("listening on %q", listenAddr) - listener, err := net.Listen("tcp", listenAddr) + logger.Printf("listening on %q", *listenAddr) + listener, err := net.Listen("tcp", *listenAddr) if err != nil { - logger.Fatalf("failed to listen on %q: %v", listenAddr, err) + logger.Fatalf("failed to listen on %q: %v", *listenAddr, err) } logger.Printf("setting up HTTP handlers") httpHandlers := newHTTPHandlers(scoreboard, randSrc, logger) logger.Printf("serving HTTP requests") - httpServer := newHTTPServer(listener, httpHandlers, logger) - - logger.Printf("initialization done, waiting for interrupt signal") - sigCh := make(chan os.Signal) - signal.Notify(sigCh, os.Interrupt) - <-sigCh - logger.Printf("interrupt signal received, cleaning up") - go func() { - <-sigCh - log.Fatalf("interrupt signal received again, forcing shutdown") - }() - - if err := httpServer.cleanup(); err != nil { - logger.Fatalf("cleaning up http server: %v", err) - } - - // NOTE go's builtin http server does not follow component property 5a, and - // instead closes the net.Listener given to it as a parameter when Shutdown - // is called. Because of that inconsistency this Close would error if it - // were called. - // - // While there are ways to work around this, it's instead highlighted in - // this example as an instance of a language making the component-oriented - // pattern more difficult. - // - //if err := listener.Close(); err != nil { - // logger.Fatalf("closing listener %q: %v", listenAddr, err) - //} - - if err := scoreboard.cleanup(); err != nil { - logger.Fatalf("cleaning up scoreboard: %v", err) - } - - saveTicker.Stop() - - if err := file.Close(); err != nil { - logger.Fatalf("closing file %q: %v", saveFilePath, err) - } + newHTTPServer(listener, httpHandlers, logger) - os.Stdout.Sync() + logger.Printf("initialization done") + select {} // block forever } -- cgit v1.2.3