code: def wait(self):
          
  
          self.log.debug("acquiring wait lock to wait for completion")
          # using the lock in a with-context blocks, which is what we want if
          # we're running wait()
          with self._wait_lock:
              self.log.debug("got wait lock")
              witnessed_end = False
  
              if self.exit_code is None:
                  self.log.debug("exit code not set, waiting on pid")
                  pid, exit_code = no_interrupt(os.waitpid, self.pid, 0) # blocks
                  self.exit_code = handle_process_exit_code(exit_code)
                  witnessed_end = True
  
              else:
                  self.log.debug("exit code already set (%d), no need to wait",
                          self.exit_code)
  
              self._quit_threads.set()
  
              # we may not have a thread for stdin, if the pipe has been connected
              # via _piped="direct"
              if self._input_thread:
                  self._input_thread.join()
  
              # wait, then signal to our output thread that the child process is
              # done, and we should have finished reading all the stdout/stderr
              # data that we can by now
              timer = threading.Timer(2.0, self._stop_output_event.set)
              timer.start()
  
              # wait for our stdout and stderr streamreaders to finish reading and
              # aggregating the process output
              self._output_thread.join()
              timer.cancel()
  
              self._background_thread.join()
  
              if witnessed_end:
                  self._process_just_ended()
  
              return self.exit_code
docs: waits for the process to complete, handles the exit code