How to tell when a spawned process is idle

Viewed 130

I am launching an external program (adb shell) via node's spawn method. Then I am sending commands via childProc.stdin.write(input) and the shell returns stuff accordingly. My data callback gets called each time there is a new data chunk available. All good!

But since the connection stays open, I don't know when the external program is finished sending data. Is there a way to solve this? Isn't there some standard way of showing "that's the data you asked for, I am done returning"?

  static async execCommand2(command, args, input) {

    if(!this.adbShellProc || this.adbShellProc.killed){
      this.adbShellProc = spawn(command, args)
      this.adbShellProc.stdout.setEncoding('utf8')
    }

    const self = this

    let promise = new Promise((resolve, reject) => {
      
      log.debug('Shell.execCommand2:', command, 'args:', args)
      log.debug('send input: ', input)
      
      self.adbShellProc.stdin.write(input)
      self.adbShellProc.stdin.write('\n')
      
      self.adbShellProc.stdout.on('data', chunk => {
        // resolve(chunk)
        // PROBLEM: I can't resolve here, cause there might be more data coming in 
      })
      self.adbShellProc.on('close', code => {
        self.adbShellProc.stdin.end()
        self.adbShellProc = null
      })   
    })
    return promise
  }
3 Answers

I am sure your answer is in your question itself. You have added close event listener, so when process is ended, your control will be in close event(Just one modification is, close event will be of adbShellProc.stdout), so you can resolve/return data from there.

  static async execCommand2(command, args, input) {
   let finalOutcome = ''
    if(!this.adbShellProc || this.adbShellProc.killed){
      this.adbShellProc = spawn(command, args)
      this.adbShellProc.stdout.setEncoding('utf8')
    }

    const self = this

    let promise = new Promise((resolve, reject) => {
      
      log.debug('Shell.execCommand2:', command, 'args:', args)
      log.debug('send input: ', input)
      
      self.adbShellProc.stdin.write(input)
      self.adbShellProc.stdin.write('\n')
      
      self.adbShellProc.stdout.on('data', chunk => {
        // resolve(chunk)
        finalOutcome += data
        // PROBLEM: I can't resolve here, cause there might be more data coming in 
      })
      self.adbShellProc.stdout.on('close', code => {
        self.adbShellProc.stdin.end()
        self.adbShellProc = null
        resolve(finalOutcome)
      })   
    })
    return promise
  }

Also, it has another event end of stdout,

ls.stdout.on('end', (d) =>{
// You can resolve from here also
})

You can get more details here Child Process

No. There's no way of knowing if the output is final unless an exit code is returned, this is the linux way.

Fortunately, adb shell gives you a way of running one off commands using the adb shell without having to start it in interactive mode.

Instead of starting the shell and then sending the commands as input, make the whole thing a single command. Example:

adb shell am start -a android.intent.action.VIEW

Then the close event will give you a clear indication that the command has finished so you can resolve and you will also get the return code, so you can check if the command has succeeded or not.

There's no real benefit in starting adb shell in interactive mode, because the real processing is not happening in adb but in the actual server, which is adbd.

https://developer.android.com/studio/command-line/adb#shellcommands

static async execCommand2(command, args, input) {
    let finalOutcome = ''
    //append the input to the arguments and don't use this, all your commands are one offs and do not require a state check
    let adbShellProc = spawn(command, args+input)
    adbShellProc.stdout.setEncoding('utf8')
    let promise = new Promise((resolve, reject) => {
    log.debug('Shell.execCommand2:', command, 'args:', args+input)
    self.adbShellProc.stdout.on('data', chunk => {
        finalOutcome += data
    })
    self.adbShellProc.stdout.on('close', code => {
        //unix return code, 0 is good, not zero means error
        if(code != 0){
            reject(finalOutcome)
        }
        resolve(finalOutcome)
    })   
    })
    return promise
}

You can end your shell commands with ; echo some_marker and wait until some_marker appears in the stdout. (And you can also get the exit code with ; echo some_marker $?)

Related