@@ -150,6 +150,9 @@ func main() {
150150 err = s .embedEmbedded ()
151151 } else {
152152 err = s .runScript ()
153+ if err != nil {
154+ err = errors .New ("running script failed to find compiled binary: " + err .Error ())
155+ }
153156 }
154157 if err != nil {
155158 _ , _ = fmt .Fprintln (os .Stderr , "error: " + err .Error ())
@@ -425,6 +428,12 @@ func (s *Script) compile() (err error) {
425428 if err != nil {
426429 return
427430 }
431+ s .waitForActiveBuilds ()
432+ // maybe it was built while we were waiting?
433+ outOfDate , err := s .targetOutOfDate ()
434+ if err == nil && ! outOfDate {
435+ return
436+ }
428437
429438 // use the default environment before adding our overrides, this allows GOPRIVATE etc. to be used in the build
430439 var env []string
@@ -474,6 +483,83 @@ func (s *Script) compile() (err error) {
474483 return
475484}
476485
486+ // isProcessRunning checks if a process with the given PID is still running
487+ func isProcessRunning (pid int ) bool {
488+ process , err := os .FindProcess (pid )
489+ if err != nil {
490+ return false
491+ }
492+ // On Unix, FindProcess always succeeds, so we need to send signal 0 to check if process exists
493+ err = process .Signal (syscall .Signal (0 ))
494+ return err == nil
495+ }
496+
497+ // hasActiveBuild checks if there are any active compilation processes for this script
498+ // by looking for PID directories in s.tmpDir and checking if those processes are still running
499+ // even if they are, we will continue if we are the lowest PID
500+ func (s * Script ) hasActiveBuild () bool {
501+ entries , err := os .ReadDir (s .tmpDir )
502+ if err != nil {
503+ return false // If we can't read the directory, assume no active builds
504+ }
505+
506+ currentPID := os .Getpid ()
507+ for _ , entry := range entries {
508+ if entry .IsDir () {
509+ // Check if the directory name is numeric (PID)
510+ if pid , err := strconv .Atoi (entry .Name ()); err == nil {
511+ // Skip our own PID
512+ if pid != currentPID && isProcessRunning (pid ) {
513+ if pid < currentPID {
514+ if s .debug {
515+ _ , _ = fmt .Fprintf (os .Stdout , "[%v] Detected lower active build process with PID %d\n " , currentPID , pid )
516+ }
517+ return true
518+ }
519+ }
520+ }
521+ }
522+ }
523+ return false
524+ }
525+
526+ // waitForActiveBuilds waits for any lower PID active builds to complete before proceeding
527+ // Returns true if it's safe to proceed, false if timeout occurred.
528+ func (s * Script ) waitForActiveBuilds () bool {
529+ maxRetries := 12 // Waits max approx 17s at 12 iterations. But may be called twice, so 34s total.
530+ waitTime := 100 * time .Millisecond
531+ maxWaitTime := 2 * time .Second
532+
533+ for i := 0 ; i < maxRetries ; i ++ {
534+ if ! s .hasActiveBuild () {
535+ return true
536+ }
537+
538+ if s .debug {
539+ _ , _ = fmt .Fprintf (os .Stderr , "Active build detected, waiting %v (attempt %d/%d)\n " , waitTime , i + 1 , maxRetries )
540+ }
541+
542+ time .Sleep (waitTime )
543+ outOfDate , err := s .targetOutOfDate ()
544+ if err == nil && ! outOfDate {
545+ _ , _ = fmt .Fprintf (os .Stderr , "Active build detected, escaping %v (attempt %d/%d)\n " , waitTime , i + 1 , maxRetries )
546+ return false
547+ }
548+
549+ // Exponential backoff, but cap at maxWaitTime
550+ waitTime *= 2
551+ if waitTime > maxWaitTime {
552+ waitTime = maxWaitTime
553+ }
554+ }
555+
556+ // Timeout occurred, but we'll proceed anyway
557+ if s .debug {
558+ _ , _ = fmt .Fprintf (os .Stderr , "Timeout waiting for active builds, proceeding anyway\n " )
559+ }
560+ return false
561+ }
562+
477563func touchFile (file string ) (err error ) {
478564 _ , err = os .Stat (file )
479565 if os .IsNotExist (err ) {
@@ -619,14 +705,23 @@ func (s *Script) runScript() (err error) {
619705 var outOfDate bool
620706 for i := 0 ; i < 5 ; i ++ {
621707 outOfDate , err = s .targetOutOfDate ()
622-
623708 if err != nil {
624709 return // can't find the source file - let's bail
625710 }
711+
626712 if outOfDate {
627- err = s .compile () // can't compile, well, it could be inconsistent source, let's bail
713+ // Wait for any active builds to complete before starting our own
714+ s .waitForActiveBuilds ()
715+ // maybe it was built while we were waiting?
716+ outOfDate , err = s .targetOutOfDate ()
628717 if err != nil {
629- return
718+ return // can't find the source file - let's bail
719+ }
720+ if outOfDate {
721+ err = s .compile () // can't compile, well, it could be inconsistent source, let's bail
722+ if err != nil {
723+ return
724+ }
630725 }
631726 }
632727 err = s .run ()
0 commit comments