def run(&block)
      sync do
        raise Shells::AlreadyRunning if running?
        self.run_flag = true
      end

      begin
        run_hook :on_before_run

        debug 'Connecting...'
        connect

        debug 'Starting output buffering...'
        buffer_output

        debug 'Starting session thread...'
        self.session_thread = Thread.start(self) do |sh|
          begin
            begin
              debug 'Executing setup...'
              sh.instance_eval { setup }
              debug 'Executing block...'
              block.call sh
            ensure
              debug 'Executing teardown...'
              sh.instance_eval { teardown }
            end
          rescue Shells::QuitNow
            # just exit the session.
          rescue =>e
            # if the exception is handled by the hook no further processing is required, otherwise we store the exception
            # to propagate it in the main thread.
            unless sh.run_hook(:on_exception, e) == :break
              sh.sync { sh.instance_eval { self.session_exception = e } }
            end
          end
        end

        # process the input buffer while the thread is alive and the shell is active.
        debug 'Entering IO loop...'
        io_loop do
          if active?
            begin
              if session_thread.status    # not dead
                # process input from the session.
                unless wait_for_output
                  inp = next_input
                  if inp
                    send_data inp
                    self.wait_for_output = (options[:unbuffered_input] == :echo)
                  end
                end

                # continue running the IO loop
                true
              elsif session_exception
                # propagate the exception.
                raise session_exception.class, session_exception.message, session_exception.backtrace
              else
                # the thread has exited, but no exception exists.
                # regardless, the IO loop should now exit.
                false
              end
            rescue IOError
              if ignore_io_error
                # we were (sort of) expecting the IO error, so just tell the IO loop to exit.
                false
              else
                raise
              end
            end
          else
            # the shell session is no longer active, tell the IO loop to exit.
            false
          end
        end
      rescue
        # when an error occurs, try to disconnect, but ignore any further errors.
        begin
          debug 'Disconnecting...'
          disconnect
        rescue
          # ignore
        end
        raise
      else
        # when no error occurs, try to disconnect and propagate any errors (unless we are ignoring IO errors).
        begin
          debug 'Disconnecting...'
          disconnect
        rescue IOError
          raise unless ignore_io_error
        end
      ensure
        # cleanup
        run_hook :on_after_run
        self.run_flag = false
      end

      self
    end
def run(&block)
      sync do
        raise Shells::AlreadyRunning if running?
        self.run_flag = true
      end

      begin
        run_hook :on_before_run

        debug 'Connecting...'
        connect

        debug 'Starting output buffering...'
        buffer_output

        debug 'Starting session thread...'
        self.session_thread = Thread.start(self) do |sh|
          begin
            begin
              debug 'Executing setup...'
              sh.instance_eval { setup }
              debug 'Executing block...'
              block.call sh
            ensure
              debug 'Executing teardown...'
              sh.instance_eval { teardown }
            end
          rescue Shells::QuitNow
            # just exit the session.
          rescue =>e
            # if the exception is handled by the hook no further processing is required, otherwise we store the exception
            # to propagate it in the main thread.
            unless sh.run_hook(:on_exception, e) == :break
              sh.sync { sh.instance_eval { self.session_exception = e } }
            end
          end
        end

        # process the input buffer while the thread is alive and the shell is active.
        debug 'Entering IO loop...'
        io_loop do
          if active?
            begin
              if session_thread.status    # not dead
                # process input from the session.
                unless wait_for_output
                  inp = next_input
                  if inp
                    send_data inp
                    self.wait_for_output = (options[:unbuffered_input] == :echo)
                  end
                end

                # continue running the IO loop
                true
              elsif session_exception
                # propagate the exception.
                raise session_exception.class, session_exception.message, session_exception.backtrace
              else
                # the thread has exited, but no exception exists.
                # regardless, the IO loop should now exit.
                false
              end
            rescue IOError
              if ignore_io_error
                # we were (sort of) expecting the IO error, so just tell the IO loop to exit.
                false
              else
                raise
              end
            end
          else
            # the shell session is no longer active, tell the IO loop to exit.
            false
          end
        end
      rescue
        # when an error occurs, try to disconnect, but ignore any further errors.
        begin
          debug 'Disconnecting...'
          disconnect
        rescue
          # ignore
        end
        raise
      else
        # when no error occurs, try to disconnect and propagate any errors (unless we are ignoring IO errors).
        begin
          debug 'Disconnecting...'
          disconnect
        rescue IOError
          raise unless ignore_io_error
        end
      ensure
        # cleanup
        run_hook :on_after_run
        self.run_flag = false
      end

      self
    end
Runs a shell session.

 The block provided will be run asynchronously with the shell.

 Returns the shell instance.