summaryrefslogtreecommitdiff
path: root/assets/component-oriented-design/v2
diff options
context:
space:
mode:
authorBrian Picciano <mediocregopher@gmail.com>2020-11-27 17:26:39 -0700
committerBrian Picciano <mediocregopher@gmail.com>2020-11-28 17:54:17 -0700
commitd40fe1021392da2c74bc6156ad2734071c615495 (patch)
treef400aeca701756ec9c738f94bf99db1fe904a590 /assets/component-oriented-design/v2
parente346068f586b6011f3e665a552945e30dce12c25 (diff)
rewrite CoP post to use new examples
Diffstat (limited to 'assets/component-oriented-design/v2')
-rw-r--r--assets/component-oriented-design/v2/main.go122
1 files changed, 29 insertions, 93 deletions
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
}