Get subprocesses to stop

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the unix category.

Last Updated: 2022-05-26

I wrote a node script that was supposed to run end-to-end tests for an emulated Android app using a local node server as a backend.

As such, the testing script booted up (directly and through sub-commands):

Ideally I wanted a clean slate whenever this script ended or was interrupted. This wasn't happening with my first attempts: instead, processes were always left hanging around between runs.

I started debugging one of the problematic processes using the following approach:

$ react-native run-android &; watch -n 1 pstree -p $$

The idea is to put the problematic code (the react-native command) in the background with & and then repeatedly run pstree on the current shell to see what was happening.

The output was as follows:

\-+= 14112 jack /Applications/ --server login -fp jack                                     38:52
 \-+= 14113 root login -fp jack
   \-+= 14114 jack -zsh
     |-+= 59518 jack node /usr/local/bin/react-native run-android
     | \-+- 59547 jack /usr/bin/java -Xmx64m -Xms64m -Xdock:name=Gradle -Xdock:icon=/Users/jack/code/consulting/project_r_app/and
roid/media/gradle.icns -Dorg.gradle.appname=gradlew -classpath /Users/jack/code/consulting/project_r_app/android/gradle/wrapper/gradl
e-wrapper.jar org.gradle.wrapper.GradleWrapperMain app:installDebug -PreactNativeDevServerPort=8081
     |   \--- 59557 jack (sh)
     \-+=-59519 jack watch -n 1 pstree -p 14114
         \-+- 59551 jack pstree -p 14114


(One unexpected interesting thing about this approach was how much useful information for general debugging is contained within the process itself - i.e. I can see the installDebug was called on Gradle and that port 8081 was used)

Before going any further, let me supplement by listing all the relevant processes being executed. I use ps -f to get extra info (e.g. ppid). I also want this sorted based on the STIME (start time) field so I ran ps -f | sort -k5,2 to get:

501 59518 14114   0 10:28AM ttys001    0:01.29 node /usr/local/bin/react-native run-android
501 59519 14114   0 10:28AM ttys001    0:02.78 watch -n 1 pstree -p 14114
501 59547 59518   0 10:28AM ttys001    0:01.91 /usr/bin/java -Xmx64m -Xms64m -Xdock:name=Gradle -Xdock:icon=/Users/jack/code/consulting/project_r_app/android/media/gradle.icns -Dorg.gradle.appname=gradlew -classpath /Users/jack/code/consulting/project_r_app/android/gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain app:installDebug -PreactNativeDevServerPort=8081
501 59562   580   0 10:28AM ttys000    0:00.04 /Applications/ --server login -fp jack

# Space added by me

501 59567 59566   0 10:28AM ttys000    0:00.55 -zsh
501 60198 59567   0 10:28AM ttys000    0:00.01 /bin/bash /Users/jack/code/consulting/project_r_app/node_modules/react-native/scripts/launchPackager.command
501 60210 60198   0 10:28AM ttys000    0:04.38 node /Users/jack/code/consulting/project_r_app/node_modules/react-native/scripts/../cli.js start
501 60223 60210   0 10:28AM ttys000    0:00.07 /Users/jack/.nvm/versions/node/v12.9.1/bin/node /Users/jack/code/consulting/project_r_app/node_modules/jest-worker/build/workers/processChild.js
501 60224 60210   0 10:28AM ttys000    0:00.07 /Users/jack/.nvm/versions/node/v12.9.1/bin/node /Users/jack/code/consulting/project_r_app/node_modules/jest-worker/build/workers/processChild.js
501 60225 60210   0 10:28AM ttys000    0:00.08 /Users/jack/.nvm/versions/node/v12.9.1/bin/node /Users/jack/code/consulting/project_r_app/node_modules/jest-worker/build/workers/processChild.js

The main thing to notice here is that the nameless process 59567 has no parent pid.

Running pstree on this nameless process gives:

-+= 59567 jack -zsh
 \-+= 60198 jack /bin/bash /Users/jack/code/consulting/project_r_app/node_modules/react-native/scripts/launchPackager.command
   \-+- 60210 jack node /Users/jack/code/consulting/project_r_app/node_modules/react-native/scripts/../cli.js start
     |--- 60223 jack /Users/jack/.nvm/versions/node/v12.9.1/bin/node /Users/jack/code/consulting/project_r_app/node_modules/jest-worker/build/workers/processChild.js
     |--- 60224 jack /Users/jack/.nvm/versions/node/v12.9.1/bin/node /Users/jack/code/consulting/project_r_app/node_modules/jest-worker/build/workers/processChild.js
     \--- 60225 jack /Users/jack/.nvm/versions/node/v12.9.1/bin/node /Users/jack/code/consulting/project_r_app/node_modules/jest-worker/build/workers/processChild.js

i.e. it explains all the processes below the space in the call to ps -f above.

So a few questions remain:

On macos:

  if (process.platform === 'darwin') {
    try {
      return execa.sync(
        ['-a', terminal, launchPackagerScript],
    } catch (error) {
      return execa.sync('open', [launchPackagerScript], procConfig);
  $ open -a Terminal & echo $!

Another way is to search for the process by name with pgrep.

There is a really useful command for searching processes pgrep. This gives you the matching pid based on a partial match of the name. E.g. if the process nvim is running, I can do

$ pgrep nvim or $ pgrep im to get the process id of the process named nvim.

By default pgrep only matches against the initial command and not any of the arguments. Thus for the process /usr/bin/java -Xmx64m -Xms64m project_r it would not match. Instead I can include the arguments with the f flag: pgrep -f project_r

Or in my use-case here, with the following:

pgrep -f launchPackager.command