gddo-server: add a semaphore to limit concurrent graph requests

There should be an upper bound for the number of "dot" processes
that can be started concurrently in order to serve incoming HTTP
requests for the ?import-graph pages, to help avoid resource
exhaustion and server overload.

This change implements a limit of 10 such processes at once.

Change-Id: I65d01996461ae212bb84cfd8edfbe6fcec43c329
Reviewed-on: https://go-review.googlesource.com/c/gddo/+/234637
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gddo-server/main.go b/gddo-server/main.go
index 5154bf4..e9a9fb1 100644
--- a/gddo-server/main.go
+++ b/gddo-server/main.go
@@ -328,6 +328,15 @@
 		if pdoc.Name == "" {
 			return &httpError{status: http.StatusNotFound}
 		}
+
+		// Throttle ?import-graph requests.
+		select {
+		case s.importGraphSem <- struct{}{}:
+		default:
+			return &httpError{status: http.StatusTooManyRequests}
+		}
+		defer func() { <-s.importGraphSem }()
+
 		hide := database.ShowAllDeps
 		switch req.Form.Get("hide") {
 		case "1":
@@ -866,12 +875,16 @@
 	statusSVG http.Handler
 
 	root rootHandler
+
+	// A semaphore to limit concurrent ?import-graph requests.
+	importGraphSem chan struct{}
 }
 
 func newServer(ctx context.Context, v *viper.Viper) (*server, error) {
 	s := &server{
-		v:          v,
-		httpClient: newHTTPClient(v),
+		v:              v,
+		httpClient:     newHTTPClient(v),
+		importGraphSem: make(chan struct{}, 10),
 	}
 
 	var err error