@@ -36,6 +36,7 @@ import (
3636
3737 "cloud.google.com/go/compute/metadata"
3838 "github.com/bradfitz/gomemcache/memcache"
39+ "golang.org/x/playground/internal"
3940 "golang.org/x/playground/internal/gcpdial"
4041 "golang.org/x/playground/sandbox/sandboxtypes"
4142)
@@ -123,6 +124,11 @@ func (s *server) commandHandler(cachePrefix string, cmdFunc func(context.Context
123124 http .Error (w , http .StatusText (http .StatusInternalServerError ), http .StatusInternalServerError )
124125 return
125126 }
127+ if strings .Contains (resp .Errors , goBuildTimeoutError ) {
128+ // TODO(golang.org/issue/38052) - This should be a http.StatusBadRequest, but the UI requires a 200 to parse the response.
129+ s .writeResponse (w , resp , http .StatusOK )
130+ return
131+ }
126132 for _ , e := range nonCachingErrors {
127133 if strings .Contains (resp .Errors , e ) {
128134 s .log .Errorf ("cmdFunc compilation error: %q" , resp .Errors )
@@ -147,16 +153,21 @@ func (s *server) commandHandler(cachePrefix string, cmdFunc func(context.Context
147153 }
148154 }
149155
150- var buf bytes.Buffer
151- if err := json .NewEncoder (& buf ).Encode (resp ); err != nil {
152- s .log .Errorf ("error encoding response: %v" , err )
153- http .Error (w , http .StatusText (http .StatusInternalServerError ), http .StatusInternalServerError )
154- return
155- }
156- if _ , err := io .Copy (w , & buf ); err != nil {
157- s .log .Errorf ("io.Copy(w, &buf): %v" , err )
158- return
159- }
156+ s .writeResponse (w , resp , http .StatusOK )
157+ }
158+ }
159+
160+ func (s * server ) writeResponse (w http.ResponseWriter , resp * response , status int ) {
161+ var buf bytes.Buffer
162+ if err := json .NewEncoder (& buf ).Encode (resp ); err != nil {
163+ s .log .Errorf ("error encoding response: %v" , err )
164+ http .Error (w , http .StatusText (http .StatusInternalServerError ), http .StatusInternalServerError )
165+ return
166+ }
167+ w .WriteHeader (status )
168+ if _ , err := io .Copy (w , & buf ); err != nil {
169+ s .log .Errorf ("io.Copy(w, &buf): %v" , err )
170+ return
160171 }
161172}
162173
@@ -450,9 +461,7 @@ func sandboxBuild(ctx context.Context, tmpDir string, in []byte, vet bool) (*bui
450461 br .exePath = filepath .Join (tmpDir , "a.out" )
451462 goCache := filepath .Join (tmpDir , "gocache" )
452463
453- ctx , cancel := context .WithTimeout (ctx , maxCompileTime )
454- defer cancel ()
455- cmd := exec .CommandContext (ctx , "/usr/local/go-faketime/bin/go" , "build" , "-o" , br .exePath , "-tags=faketime" )
464+ cmd := exec .Command ("/usr/local/go-faketime/bin/go" , "build" , "-o" , br .exePath , "-tags=faketime" )
456465 cmd .Dir = tmpDir
457466 cmd .Env = []string {"GOOS=linux" , "GOARCH=amd64" , "GOROOT=/usr/local/go-faketime" }
458467 cmd .Env = append (cmd .Env , "GOCACHE=" + goCache )
@@ -472,25 +481,30 @@ func sandboxBuild(ctx context.Context, tmpDir string, in []byte, vet bool) (*bui
472481 }
473482 cmd .Args = append (cmd .Args , buildPkgArg )
474483 cmd .Env = append (cmd .Env , "GOPATH=" + br .goPath )
475- t0 := time .Now ()
476- if out , err := cmd .CombinedOutput (); err != nil {
477- if ctx .Err () == context .DeadlineExceeded {
478- log .Printf ("go build timed out after %v" , time .Since (t0 ))
479- return & buildResult {errorMessage : goBuildTimeoutError }, nil
480- }
481- if _ , ok := err .(* exec.ExitError ); ok {
482- // Return compile errors to the user.
484+ out := & bytes.Buffer {}
485+ cmd .Stderr , cmd .Stdout = out , out
483486
484- // Rewrite compiler errors to strip the tmpDir name.
485- br .errorMessage = strings .Replace (string (out ), tmpDir + "/" , "" , - 1 )
487+ if err := cmd .Start (); err != nil {
488+ return nil , fmt .Errorf ("error starting go build: %v" , err )
489+ }
490+ ctx , cancel := context .WithTimeout (ctx , maxCompileTime )
491+ defer cancel ()
492+ if err := internal .WaitOrStop (ctx , cmd , os .Interrupt , 250 * time .Millisecond ); err != nil {
493+ if errors .Is (err , context .DeadlineExceeded ) {
494+ br .errorMessage = fmt .Sprintln (goBuildTimeoutError )
495+ } else if ee := (* exec .ExitError )(nil ); ! errors .As (err , & ee ) {
496+ log .Printf ("error building program: %v" , err )
497+ return nil , fmt .Errorf ("error building go source: %v" , err )
498+ }
499+ // Return compile errors to the user.
500+ // Rewrite compiler errors to strip the tmpDir name.
501+ br .errorMessage = br .errorMessage + strings .Replace (string (out .Bytes ()), tmpDir + "/" , "" , - 1 )
486502
487- // "go build", invoked with a file name, puts this odd
488- // message before any compile errors; strip it.
489- br .errorMessage = strings .Replace (br .errorMessage , "# command-line-arguments\n " , "" , 1 )
503+ // "go build", invoked with a file name, puts this odd
504+ // message before any compile errors; strip it.
505+ br .errorMessage = strings .Replace (br .errorMessage , "# command-line-arguments\n " , "" , 1 )
490506
491- return br , nil
492- }
493- return nil , fmt .Errorf ("error building go source: %v" , err )
507+ return br , nil
494508 }
495509 const maxBinarySize = 100 << 20 // copied from sandbox backend; TODO: unify?
496510 if fi , err := os .Stat (br .exePath ); err != nil || fi .Size () == 0 || fi .Size () > maxBinarySize {
0 commit comments