code: @Override
    public void run() {
      synchronized (this) {
        started = true;
      }
      finished = false;
  
      final byte[] buf = new byte[bufferSize];
  
      try {
        int length;
        while (true) {
          waitForInput(is);
  
          if (finish || Thread.interrupted()) {
            break;
          }
  
          length = is.read(buf);
          if (length <= 0 || Thread.interrupted()) {
            break;
          }
          os.write(buf, 0, length);
          if (autoflush) {
            os.flush();
          }
          if (finish) { //NOSONAR
            break;
          }
        }
        // On completion, drain any available data (which might be the first data available for quick executions)
        if (finish) {
          while((length = is.available()) > 0) {
            if (Thread.interrupted()) {
              break;
            }
            length = is.read(buf, 0, Math.min(length, buf.length));
            if (length <= 0) {
              break;
            }
            os.write(buf, 0, length);
          }
        }
        os.flush();
      } catch (InterruptedException ie) {
        // likely PumpStreamHandler trying to stop us
      } catch (Exception e) {
        synchronized (this) {
          exception = e;
        }
      } finally {
        if (closeWhenExhausted) {
          Closeables.close(os);
        }
        finished = true;
        finish = false;
        synchronized (this) {
          notifyAll();
        }
      }
    }
docs: Copies data from the input stream to the output stream.
  
  Terminates as soon as the input stream is closed or an error occurs.