Skip to content

Commit dc7f456

Browse files
committed
server: add garbage collector and graceful shutdown
1 parent fe3e58f commit dc7f456

File tree

4 files changed

+85
-24
lines changed

4 files changed

+85
-24
lines changed

cmd/playground/main.go

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,42 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
56
"fmt"
7+
"net/http"
8+
"os"
9+
"sync"
10+
"time"
11+
612
"github.com/gorilla/mux"
13+
"github.com/x1unix/foundation/app"
714
"github.com/x1unix/go-playground/pkg/analyzer"
815
"github.com/x1unix/go-playground/pkg/compiler"
916
"github.com/x1unix/go-playground/pkg/compiler/storage"
1017
"github.com/x1unix/go-playground/pkg/langserver"
1118
"go.uber.org/zap"
12-
"log"
13-
"net/http"
14-
"os"
1519
)
1620

21+
type appArgs struct {
22+
packagesFile string
23+
addr string
24+
debug bool
25+
buildDir string
26+
cleanupInterval string
27+
}
28+
29+
func (a appArgs) getCleanDuration() (time.Duration, error) {
30+
return time.ParseDuration(a.cleanupInterval)
31+
}
32+
1733
func main() {
18-
var packagesFile string
19-
var addr string
20-
var debug bool
21-
var buildDir string
22-
flag.StringVar(&packagesFile, "f", "packages.json", "Path to packages index JSON file")
23-
flag.StringVar(&addr, "addr", ":8080", "TCP Listen address")
24-
flag.StringVar(&buildDir, "wasm-build-dir", os.TempDir(), "Directory for WASM builds")
25-
flag.BoolVar(&debug, "debug", false, "Enable debug mode")
34+
args := appArgs{}
35+
flag.StringVar(&args.packagesFile, "f", "packages.json", "Path to packages index JSON file")
36+
flag.StringVar(&args.addr, "addr", ":8080", "TCP Listen address")
37+
flag.StringVar(&args.buildDir, "wasm-build-dir", os.TempDir(), "Directory for WASM builds")
38+
flag.StringVar(&args.cleanupInterval, "clean-interval", "10m", "Build directory cleanup interval")
39+
flag.BoolVar(&args.debug, "debug", false, "Enable debug mode")
2640

2741
goRoot, ok := os.LookupEnv("GOROOT")
2842
if !ok {
@@ -31,9 +45,9 @@ func main() {
3145
}
3246

3347
flag.Parse()
34-
l := getLogger(debug)
48+
l := getLogger(args.debug)
3549
defer l.Sync()
36-
if err := start(packagesFile, addr, goRoot, buildDir, debug); err != nil {
50+
if err := start(goRoot, args); err != nil {
3751
l.Sugar().Fatal(err)
3852
}
3953
}
@@ -55,37 +69,78 @@ func getLogger(debug bool) (l *zap.Logger) {
5569
return l
5670
}
5771

58-
func start(packagesFile, addr, goRoot, buildDir string, debug bool) error {
72+
func start(goRoot string, args appArgs) error {
73+
cleanInterval, err := args.getCleanDuration()
74+
if err != nil {
75+
return fmt.Errorf("invalid cleanup interval parameter: %s", err)
76+
}
77+
5978
zap.S().Infof("GOROOT is %q", goRoot)
60-
zap.S().Infof("Packages file is %q", packagesFile)
79+
zap.S().Infof("Packages file is %q", args.packagesFile)
80+
zap.S().Infof("Cleanup interval is %s", cleanInterval.String())
6181
analyzer.SetRoot(goRoot)
62-
packages, err := analyzer.ReadPackagesFile(packagesFile)
82+
packages, err := analyzer.ReadPackagesFile(args.packagesFile)
6383
if err != nil {
64-
return fmt.Errorf("failed to read packages file %q: %s", packagesFile, err)
84+
return fmt.Errorf("failed to read packages file %q: %s", args.packagesFile, err)
6585
}
6686

67-
store, err := storage.NewLocalStorage(zap.S(), buildDir)
87+
store, err := storage.NewLocalStorage(zap.S(), args.buildDir)
6888
if err != nil {
6989
return err
7090
}
7191

92+
ctx, _ := app.GetApplicationContext()
93+
wg := &sync.WaitGroup{}
94+
go store.StartCleaner(ctx, cleanInterval, wg)
95+
7296
r := mux.NewRouter()
7397
langserver.New(packages, compiler.NewBuildService(zap.S(), store)).
7498
Mount(r.PathPrefix("/api").Subrouter())
7599
r.PathPrefix("/").Handler(langserver.SpaFileServer("./public"))
76100

77-
zap.S().Infof("Listening on %q", addr)
78-
79101
var handler http.Handler
80-
if debug {
102+
if args.debug {
81103
zap.S().Info("Debug mode enabled, CORS disabled")
82104
handler = langserver.NewCORSDisablerWrapper(r)
83105
} else {
84106
handler = r
85107
}
86108

87-
if err := http.ListenAndServe(addr, handler); err != nil {
88-
log.Fatal(err)
109+
server := &http.Server{
110+
Addr: args.addr,
111+
Handler: handler,
112+
ReadTimeout: 5 * time.Second,
113+
WriteTimeout: 10 * time.Second,
114+
IdleTimeout: 15 * time.Second,
115+
}
116+
117+
if err := startHttpServer(ctx, wg, server); err != nil {
118+
return err
119+
}
120+
121+
wg.Wait()
122+
return nil
123+
}
124+
125+
func startHttpServer(ctx context.Context, wg *sync.WaitGroup, server *http.Server) error {
126+
logger := zap.S()
127+
go func() {
128+
<-ctx.Done()
129+
logger.Info("Shutting down server...")
130+
shutdownCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
131+
defer cancel()
132+
defer wg.Done()
133+
server.SetKeepAlivesEnabled(false)
134+
if err := server.Shutdown(shutdownCtx); err != nil {
135+
logger.Errorf("Could not gracefully shutdown the server: %v\n", err)
136+
}
137+
return
138+
}()
139+
140+
wg.Add(1)
141+
logger.Infof("Listening on %q", server.Addr)
142+
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
143+
return fmt.Errorf("cannot start server on %q: %s", server.Addr, err)
89144
}
90145

91146
return nil

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/pkg/errors v0.8.1
88
github.com/stretchr/testify v1.4.0
99
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5
10+
github.com/x1unix/foundation v1.0.0
1011
go.uber.org/atomic v1.5.1 // indirect
1112
go.uber.org/multierr v1.4.0 // indirect
1213
go.uber.org/zap v1.13.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
2323
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
2424
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE=
2525
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
26+
github.com/x1unix/foundation v1.0.0 h1:tG0dG1sbiF9TGrjwns+wtX5feBprRD5iTvpmgQDnacA=
27+
github.com/x1unix/foundation v1.0.0/go.mod h1:y9E4igeUWi+njm4xCM48NItLhVH/Jj1KGE069I3J5Hc=
2628
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
2729
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
2830
go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM=

pkg/compiler/storage/local.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,17 @@ func (s LocalStorage) clean() error {
164164
return nil
165165
}
166166

167-
func (s LocalStorage) StartCleaner(ctx context.Context, interval time.Duration) {
167+
func (s LocalStorage) StartCleaner(ctx context.Context, interval time.Duration, wg *sync.WaitGroup) {
168168
s.gcRun.Set()
169169
s.log.Debug("cleaner worker starter")
170170
for {
171171
select {
172172
case <-ctx.Done():
173173
s.log.Debug("context done, cleaner worker stopped")
174174
s.gcRun.UnSet()
175+
if wg != nil {
176+
wg.Done()
177+
}
175178
return
176179
default:
177180
}

0 commit comments

Comments
 (0)