@@ -19,6 +19,7 @@ import (
1919 "fmt"
2020 "os"
2121 "os/exec"
22+ "os/signal"
2223 "regexp"
2324 "strconv"
2425 "strings"
@@ -308,7 +309,7 @@ func setupEnvironmentVars() error {
308309 return err
309310}
310311
311- func killProcess (theProcessType ProcessType ) error {
312+ func killProcess (theProcessType ProcessType , checkAttempts int ) error {
312313 var processPid int
313314 var err error
314315 if theProcessType == server {
@@ -319,10 +320,24 @@ func killProcess(theProcessType ProcessType) error {
319320 ControllerDebug .log ("Attempting to kill pid: " , processPid )
320321
321322 if processPid != 0 {
323+ // Check to see if the process is still alive to avoid unncessary kill steps
324+ if cmps .processes [theProcessType ].Signal (syscall .Signal (0 )) != nil {
325+ ControllerDebug .log ("No such process for pid: " , processPid )
326+ err = nil
327+ } else {
322328
323- ControllerDebug .log ("Killing pid: " , processPid )
324- err = syscall .Kill (- processPid , syscall .SIGINT )
325-
329+ ControllerDebug .log ("Killing pid: " , processPid )
330+ err = syscall .Kill (- processPid , syscall .SIGINT )
331+ // If checkAttempts speified check and wait to make sure process was killed.
332+ for i := 0 ; i < checkAttempts ; i ++ {
333+ ControllerDebug .log ("Process check " , theProcessType , i )
334+ if cmps .processes [theProcessType ].Signal (syscall .Signal (0 )) != nil {
335+ break //process is dead
336+ } else {
337+ time .Sleep (2 * time .Second )
338+ }
339+ }
340+ }
326341 cmps .processes [theProcessType ] = nil
327342 cmps .pids [theProcessType ] = 0
328343 if err != nil {
@@ -445,7 +460,6 @@ func runWatcher(fileChangeCommand string, dirs []string, killServer bool) error
445460
446461 case err := <- w .Error :
447462 ControllerWarning .log ("An error occured in the file watcher " , err )
448-
449463 case <- w .Closed :
450464 ControllerDebug .log ("The file watcher is now closed" )
451465 return
@@ -526,19 +540,20 @@ func runCommands(commandString string, theProcessType ProcessType, killServer bo
526540 // This is a watcher
527541 if killServer {
528542 ControllerDebug .log ("APPSODY_RUN/DEBUG/TEST_ON_KILL is true, attempting to kill the corresponding process." )
529- err = killProcess (server )
543+ err = killProcess (server , 0 )
530544 if err != nil {
531545 // do nothing we continue after kill errors
532546 ControllerWarning .log ("The attempt to kill the process received an error " , err )
533547 }
534548 }
535549 ControllerDebug .log ("Killing the APPSODY_RUN/DEBUG/TEST_ON_CHANGE process." )
536550
537- err = killProcess (fileWatcher )
551+ err = killProcess (fileWatcher , 0 )
538552 if err != nil {
539553 // do nothing we continue after kill errors
540554 ControllerWarning .log ("Killing the the APPSODY_RUN/DEBUG/TEST_ON_CHANGE process received error " , err )
541555 }
556+ go reapChildProcesses (2 )
542557
543558 commandToUse := commandString
544559 processTypeToUse := fileWatcher
@@ -695,6 +710,33 @@ func main() {
695710
696711 go runCommands (startCommand , server , false , false )
697712 }
713+
714+ c := make (chan os.Signal , 1 )
715+ signal .Notify (c , os .Interrupt , syscall .SIGTERM , syscall .SIGINT )
716+ go func () {
717+ <- c
718+ cmps .mu .Lock ()
719+ defer cmps .mu .Unlock ()
720+ ControllerDebug .log ("Inside signal handler for controller" )
721+ ControllerDebug .log ("Killing the ON_CHANGE process" )
722+ // In practice either the fileWatcher or server process will be alive, not both
723+ err := killProcess (fileWatcher , 2 ) // we give 2 *2 seconds to kill the filewatcher/ON_CHANGE process
724+ if err != nil {
725+ ControllerError .log ("Received error during signal handler killing ON_CHANGE process" , err )
726+ }
727+ ControllerDebug .log ("Killing the server process" )
728+ err = killProcess (server , 2 ) // we give 2 *2 seconds to kill the server process
729+ if err != nil {
730+ ControllerError .log ("Received error during signal handler killing the RUN/TEST/DEBUG process" , err )
731+ }
732+ // 5 * 1 second waiting for reaping of child processes
733+ // This call is allowed to complete by the fact that the docker stop allows 10 seconds for processeing
734+ // prior to the sig kill
735+ go reapChildProcesses (5 ) //run separately to make sure that we don't block
736+
737+ ControllerDebug .log ("Done processing controller signal handler." )
738+ }()
739+
698740 if fileChangeCommand != "" && ! disableWatcher {
699741
700742 err = runWatcher (fileChangeCommand , dirs , stopWatchServerOnChange )
@@ -709,3 +751,43 @@ func main() {
709751 }
710752
711753}
754+
755+ func reapChildProcesses (maxLimit int ) {
756+ countLimit := 0
757+
758+ for {
759+
760+ var wstatus syscall.WaitStatus
761+ //WNOHANG means return if there are no child processes to wait for
762+ //This command will wait for processes that hae been reassigned
763+ // to pid 1 after the server or fileWatcher/ON_CHANGE process is terminated
764+ pid , err := syscall .Wait4 (- 1 , & wstatus , syscall .WNOHANG , nil )
765+ ControllerDebug .log ("Reaper pid/err is: " , pid , err )
766+ // If it is 0 that means no process was waiting atm, we will sleep and give a little more time
767+ if pid == 0 && countLimit < maxLimit && err == nil {
768+ ControllerDebug .log ("Reaper sleeping 1 second: " , pid )
769+ time .Sleep (1 * time .Second )
770+ countLimit ++
771+ }
772+
773+ if syscall .EINTR == err {
774+ // A Signal Interupt occured and we should stop processing
775+ ControllerDebug .log ("Signal Interrupt: " , err )
776+ break
777+ }
778+ //This value means no child processes left waiting.
779+ if syscall .ECHILD == err {
780+ ControllerDebug .log ("No more child processes: " , err )
781+ break
782+ }
783+
784+ ControllerDebug .log ("Max limit count: " , countLimit )
785+
786+ if countLimit >= maxLimit {
787+ ControllerDebug .log ("Max limit reached: " , maxLimit )
788+ break
789+ }
790+
791+ }
792+
793+ }
0 commit comments