@@ -30,8 +30,8 @@ type PluginInstance struct {
3030 l * sync.Mutex
3131 listener map [string ]func ([]byte )
3232
33- started bool // mark the instance as started
34- shutdown bool // mark the instance as shutdown
33+ started bool // mark the instance as started, it will be set to true when the first heartbeat is received
34+ shutdown bool // mark the instance as shutdown, it will be set to true when stdout reader is closed
3535
3636 // app config
3737 appConfig * app.Config
@@ -114,6 +114,17 @@ func (s *PluginInstance) Stop() {
114114
115115// StartStdout starts to read the stdout of the plugin
116116// and parse the stdout data to trigger corresponding listeners
117+ //
118+ // # Whenever the stdout reader is closed, subprocess will be killed and reaped
119+ //
120+ // At a high-level view of the architecture design
121+ // Stdout actually take responsibility of the existing of the plugin instance
122+ //
123+ // CLOSE STDOUT = KILL and REAP subprocess
124+ // CLOSE INSTANCE = CLOSE STDOUT
125+ //
126+ // In the above scope, the subprocess was killed by daemon,
127+ // Once the subprocess exists itself, STDOUT always close, which results in `CLOSE STDOUT`
117128func (s * PluginInstance ) StartStdout () {
118129 defer func () {
119130 // notify shutdown signal
@@ -163,10 +174,19 @@ func (s *PluginInstance) StartStdout() {
163174
164175 // once reader of stdout is closed, kill subprocess
165176 if err := s .cmd .Process .Kill (); err != nil {
177+ // no need to return here, just log the error, it's perhaps the process was exited already
178+ // and the kill command fails
166179 s .WalkNotifiers (func (notifier PluginInstanceNotifier ) {
167180 notifier .OnInstanceErrorLog (s , fmt .Errorf ("failed to kill subprocess: %s" , err .Error ()))
168181 })
169182 }
183+
184+ // collect subprocess, avoid zombie processes
185+ if _ , err := s .cmd .Process .Wait (); err != nil {
186+ s .WalkNotifiers (func (notifier PluginInstanceNotifier ) {
187+ notifier .OnInstanceErrorLog (s , fmt .Errorf ("failed to reap subprocess: %s" , err .Error ()))
188+ })
189+ }
170190}
171191
172192// handles stdout data and notify corresponding listeners
@@ -258,6 +278,11 @@ func (s *PluginInstance) Monitor() error {
258278
259279 // check status of plugin every 5 seconds
260280 for range ticker .C {
281+ if s .shutdown {
282+ // process was closed already, exit monitoring
283+ return nil
284+ }
285+
261286 // check heartbeat
262287 if time .Since (s .lastActiveAt ) > MAX_HEARTBEAT_INTERVAL {
263288 s .WalkNotifiers (func (notifier PluginInstanceNotifier ) {
@@ -276,6 +301,7 @@ func (s *PluginInstance) Monitor() error {
276301 s .Stop ()
277302 return ErrRuntimeNotActive
278303 }
304+
279305 if time .Since (s .lastActiveAt ) > MAX_HEARTBEAT_INTERVAL / 2 {
280306 // notify handlers
281307 s .WalkNotifiers (func (notifier PluginInstanceNotifier ) {
0 commit comments