AppRunner: Shutdown on SIGTERM and SIGINT signals

Implement /quitquitquit handler

Change-Id: I0d11791bf3b2831b71548e2ca573e34ad854c561
diff --git a/apps/app-runner/main.go b/apps/app-runner/main.go
index 0449ca5..b11a977 100644
--- a/apps/app-runner/main.go
+++ b/apps/app-runner/main.go
@@ -7,8 +7,10 @@
 	"log"
 	"net"
 	"os"
+	"os/signal"
 	"path/filepath"
 	"strings"
+	"syscall"
 
 	"golang.org/x/crypto/ssh"
 
@@ -129,7 +131,16 @@
 		panic(err)
 	}
 	s := NewServer(*agentMode, *port, *appId, *service, id, *repoAddr, *branch, *rootDir, signer, *appDir, cmds, self, *managerAddr)
-	if err := s.Start(); err != nil {
-		log.Fatal(err)
-	}
+	go func() {
+		if err := s.Start(); err != nil {
+			log.Fatal(err)
+		} else {
+			log.Println("Done")
+			os.Exit(0)
+		}
+	}()
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	<-sigChan
+	s.Stop()
 }
diff --git a/apps/app-runner/server.go b/apps/app-runner/server.go
index 1f44481..51e3db9 100644
--- a/apps/app-runner/server.go
+++ b/apps/app-runner/server.go
@@ -2,7 +2,9 @@
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"net/http"
@@ -30,7 +32,8 @@
 }
 
 type Server struct {
-	l sync.Locker
+	l  sync.Locker
+	hs *http.Server
 	// TODO(gio): randomly generate string
 	runId       int
 	agentMode   bool
@@ -52,6 +55,7 @@
 	logM        io.Writer
 	currDir     string
 	status      *Status
+	stop        bool
 }
 
 func NewServer(agentMode bool, port int, appId, service, id, repoAddr, branch, rootDir string, signer ssh.Signer, appDir string, runCommands []Command, self string, manager string) *Server {
@@ -59,6 +63,7 @@
 	logM := io.MultiWriter(os.Stdout, logger)
 	return &Server{
 		l:           &sync.Mutex{},
+		hs:          nil,
 		runId:       0,
 		agentMode:   agentMode,
 		port:        port,
@@ -78,6 +83,7 @@
 		logM:        logM,
 		currDir:     "",
 		status:      nil,
+		stop:        false,
 	}
 }
 
@@ -85,13 +91,45 @@
 	http.HandleFunc("/update", s.handleUpdate)
 	http.HandleFunc("/ready", s.handleReady)
 	http.HandleFunc("/logs", s.handleLogs)
+	http.HandleFunc("/quitquitquit", s.handleQuit)
 	if s.managerAddr != "" && s.appId != "" {
 		go s.pingManager()
 	}
 	if err := s.run(); err != nil {
 		return err
 	}
-	return http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil)
+	hs := &http.Server{
+		Addr: fmt.Sprintf(":%d", s.port),
+	}
+	s.hs = hs
+	if err := s.hs.ListenAndServe(); err == nil || errors.Is(err, http.ErrServerClosed) {
+		return nil
+	} else {
+		return err
+	}
+}
+
+func (s *Server) Stop() {
+	fmt.Println("Stopping")
+	s.l.Lock()
+	defer s.l.Unlock()
+	s.stop = true
+	if err := s.kill(); err != nil {
+		fmt.Printf("Failed to stop last command: %s\n", err)
+	} else {
+		fmt.Println("Stopped last command")
+	}
+	if s.hs != nil {
+		if err := s.hs.Shutdown(context.Background()); err != nil {
+			fmt.Printf("Failed to stop web server: %s\n", err)
+		} else {
+			fmt.Println("Stopped web server")
+		}
+	}
+}
+
+func (s *Server) handleQuit(w http.ResponseWriter, r *http.Request) {
+	go s.Stop()
 }
 
 func (s *Server) handleLogs(w http.ResponseWriter, r *http.Request) {
@@ -286,8 +324,13 @@
 func (s *Server) pingManager() {
 	defer func() {
 		go func() {
-			time.Sleep(500 * time.Millisecond)
-			s.pingManager()
+			s.l.Lock()
+			defer s.l.Unlock()
+			// TODO(gio): Wait until all logs are sent over to the manager.
+			if !s.stop {
+				time.Sleep(500 * time.Millisecond)
+				s.pingManager()
+			}
 		}()
 	}()
 	logItems := s.logs.Items()