package main import ( "context" "flag" "net/http" "sync" "time" "code.hackerspace.pl/hscloud/go/mirko" "github.com/golang/glog" "github.com/grpc-ecosystem/grpc-gateway/runtime" pb "code.hackerspace.pl/hscloud/personal/q3k/shipstuck/proto" ) type vessel struct { Speed float64 `json:"ss"` } // get retrieves the current status of the ship - returns true if stack, false // otherwise. func get(ctx context.Context) (shipState, error) { // 2021/03/29/ 17:23 UTC+2 it's been freed! return shipStateFreed, nil } type shipState string const ( shipStateUnknown shipState = "UNKNOWN" shipStateStuck shipState = "STUCK" shipStateFreed shipState = "FREED" shipStateTowed shipState = "TOWED" ) type service struct { lastStateMu sync.RWMutex lastState shipState lastStateTime time.Time } func (s *service) worker(ctx context.Context) { update := func() { state := shipStateUnknown // shitty back off, good enough. retries := 10 for { stuck, err := get(ctx) if err != nil { glog.Warningf("get: %v", err) if retries > 0 { time.Sleep(60 * time.Second) retries -= 1 } else { glog.Errorf("giving up on get") break } } else { state = stuck break } } glog.Infof("New state: %v", state) s.lastStateMu.Lock() s.lastState = state s.lastStateTime = time.Now() s.lastStateMu.Unlock() } update() ticker := time.NewTicker(15 * 60 * time.Second) for { select { case <-ctx.Done(): return case <-ticker.C: update() } } } func timeMust(t time.Time, err error) time.Time { if err != nil { panic(err) } return t } var ( timeStuck = timeMust(time.Parse( "At 15:04 Eastern European Time (-0700) on 2 January 2006", "At 07:40 Eastern European Time (+0200) on 23 March 2021", )) ) func (s *service) Status(ctx context.Context, req *pb.StatusRequest) (*pb.StatusResponse, error) { s.lastStateMu.RLock() state := s.lastState lastChecked := s.lastStateTime s.lastStateMu.RUnlock() res := &pb.StatusResponse{ LastChecked: lastChecked.UnixNano(), } switch state { case shipStateUnknown: res.Current = pb.StatusResponse_STUCKNESS_UNKNOWN case shipStateStuck: res.Current = pb.StatusResponse_STUCKNESS_STUCK res.Elapsed = time.Since(timeStuck).Nanoseconds() case shipStateFreed: res.Current = pb.StatusResponse_STUCKNESS_FREE case shipStateTowed: res.Current = pb.StatusResponse_STUCKNESS_TOWED res.Elapsed = time.Since(timeStuck).Nanoseconds() } return res, nil } var ( flagPublicAddress string ) func main() { flag.StringVar(&flagPublicAddress, "public_address", "127.0.0.1:8080", "Public HTTP/JSON listen address") flag.Parse() m := mirko.New() if err := m.Listen(); err != nil { glog.Exitf("Listen(): %v", err) } s := &service{} pb.RegisterShipStuckServer(m.GRPC(), s) publicMux := runtime.NewServeMux() publicSrv := http.Server{ Addr: flagPublicAddress, Handler: publicMux, } go func() { glog.Infof("REST listening on %s", flagPublicAddress) if err := publicSrv.ListenAndServe(); err != nil { glog.Exitf("public ListenAndServe: %v", err) } }() if err := pb.RegisterShipStuckHandlerServer(m.Context(), publicMux, s); err != nil { glog.Exitf("RegisterShipStuckHandlerSerever: %v", err) } go s.worker(m.Context()) if err := m.Serve(); err != nil { glog.Exitf("Serve(): %v", err) } <-m.Done() }