class DEBUGGER__::UI_ServerBase - debug: Ruby Standard Library Documentation

# class DEBUGGER__::UI_ServerBase

### Constants

TRAP_SIGNAL

  maybe Windows?

### Attributes

reader_thread[R]

### Public Class Methods

new() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 9
    def initialize
      @sock = @sock_for_fork = nil
      @accept_m = Mutex.new
      @accept_cv = ConditionVariable.new
      @client_addr = nil
      @q_msg = nil
      @q_ans = nil
      @unsent_messages = []
      @width = 80
      @repl = true
      @session = nil
    end

### Public Instance Methods

accept() { |sock_for_fork, already_connected: true| ... } click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 31
    def accept
      if @sock_for_fork
        begin
          yield @sock_for_fork, already_connected: true
        ensure
          @sock_for_fork.close
          @sock_for_fork = nil
        end
      end
    end

activate(session, on_fork: false) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 42
    def activate session, on_fork: false
      @session = session
      @reader_thread = Thread.new do
        # An error on this thread should break the system.
        Thread.current.abort_on_exception = true
        Thread.current.name = 'DEBUGGER__::Server::reader'

        accept do |server, already_connected: false|
          DEBUGGER__.warn "Connected."
          greeting_done = false
          @need_pause_at_first = true

          @accept_m.synchronize{
            @sock = server
            greeting
            greeting_done = true

            @accept_cv.signal

            # flush unsent messages
            @unsent_messages.each{|m|
              @sock.puts m
            } if @repl
            @unsent_messages.clear

            @q_msg = Queue.new
            @q_ans = Queue.new
          } unless already_connected

          setup_interrupt do
            pause if !already_connected && @need_pause_at_first
            process
          end

        rescue GreetingError => e
          DEBUGGER__.warn "GreetingError: #{e.message}"
          next
        rescue Terminate
          raise # should catch at outer scope
        rescue RetryConnection
          next
        rescue => e
          DEBUGGER__.warn "ReaderThreadError: #{e}"
          pp e.backtrace
        ensure
          DEBUGGER__.warn "Disconnected."
          cleanup_reader if greeting_done
        end # accept

      rescue Terminate
        # ignore
      end
    end

after_fork_parent() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 375
    def after_fork_parent
      # do nothing
    end

ask(prompt) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 311
    def ask prompt
      sock do |s|
        s.puts "ask #{Process.pid} #{prompt}"
        @q_ans.pop
      end
    end

check_cookie(c) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 105
    def check_cookie c
      cookie = CONFIG[:cookie]
      if cookie && cookie != c
        raise GreetingError, "Cookie mismatch (#{$2.inspect} was sent)"
      end
    end

cleanup_reader() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 96
    def cleanup_reader
      @sock.close if @sock
      @sock = nil
      @q_msg.close
      @q_msg = nil
      @q_ans.close
      @q_ans = nil
    end

deactivate() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 26
    def deactivate
      @reader_thread.raise Terminate
      @reader_thread.join
    end

greeting() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 130
    def greeting
      case g = @sock.gets
      when /^info cookie:\s+(.*)$/
        require 'etc'

        check_cookie $1
        @sock.puts "PID: #{Process.pid}, $0: #{$0}, session_name: #{CONFIG[:session_name]}"
        @sock.puts "debug #{VERSION} on #{RUBY_DESCRIPTION}"
        @sock.puts "uname: #{Etc.uname.inspect}"
        @sock.close
        raise GreetingError, "HEAD request"

      when /^version:\s+(\S+)\s+(.+)$/
        v, params = $1, $2

        # TODO: protocol version
        if v != VERSION
          @sock.puts msg = "out DEBUGGER: Incompatible version (server:#{VERSION} and client:#{$1})"
          raise GreetingError, msg
        end
        parse_option(params)

        session_name = CONFIG[:session_name]
        session_name_str = ", session_name:#{session_name}" if session_name
        puts "DEBUGGER (client): Connected. PID:#{Process.pid}, $0:#{$0}#{session_name_str}"
        puts "DEBUGGER (client): Type `Ctrl-C` to enter the debug console." unless @need_pause_at_first
        puts

      when /^Content-Length: (\d+)/
        require_relative 'server_dap'

        raise unless @sock.read(2) == "\r\n"
        self.extend(UI_DAP)
        @repl = false
        @need_pause_at_first = false
        dap_setup @sock.read($1.to_i)

      when /^GET\s\/json\sHTTP\/1.1/, /^GET\s\/json\/version\sHTTP\/1.1/, /^GET\s\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\sHTTP\/1.1/
        # The reason for not using @uuid here is @uuid is nil if users run debugger without `--open=chrome`.

        require_relative 'server_cdp'

        self.extend(UI_CDP)
        send_chrome_response g
      else
        raise GreetingError, "Unknown greeting message: #{g}"
      end
    end

parse_option(params) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 112
    def parse_option params
      case params.strip
      when /width:\s+(\d+)/
        @width = $1.to_i
        parse_option $~.post_match
      when /cookie:\s+(\S+)/
        check_cookie $1 if $1 != '-'
        parse_option $~.post_match
      when /nonstop: (true|false)/
        @need_pause_at_first = false if $1 == 'true'
        parse_option $~.post_match
      when /(.+):(.+)/
        raise GreetingError, "Unkown option: #{params}"
      else
        # OK
      end
    end

pause() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 363
    def pause
      # $stderr.puts "DEBUG: pause request"
      Process.kill(TRAP_SIGNAL, Process.pid)
    end

process() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 179
    def process
      while true
        DEBUGGER__.debug{ "sleep IO.select" }
        _r = IO.select([@sock])
        DEBUGGER__.debug{ "wakeup IO.select" }

        line = @session.process_group.sync do
          unless IO.select([@sock], nil, nil, 0)
            DEBUGGER__.debug{ "UI_Server can not read" }
            break :can_not_read
          end
          @sock.gets&.chomp.tap{|line|
            DEBUGGER__.debug{ "UI_Server received: #{line}" }
          }
        end

        return unless line
        next if line == :can_not_read

        case line
        when /\Apause/
          pause
        when /\Acommand (\d+) (\d+) ?(.+)/
          raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?

          if $1.to_i == Process.pid
            @width = $2.to_i
            @q_msg << $3
          else
            raise "pid:#{Process.pid} but get #{line}"
          end
        when /\Aanswer (\d+) (.*)/
          raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?

          if $1.to_i == Process.pid
            @q_ans << $2
          else
            raise "pid:#{Process.pid} but get #{line}"
          end
        else
          STDERR.puts "unsupported: #{line.inspect}"
          exit!
        end
      end
    end

puts(str = nil) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 318
    def puts str = nil
      case str
      when Array
        enum = str.each
      when String
        enum = str.each_line
      when nil
        enum = [''].each
      end

      sock skip: true do |s|
        enum.each do |line|
          msg = "out #{line.chomp}"
          if s
            s.puts msg
          else
            @unsent_messages << msg
          end
        end
      end
    end

quit(n, &_b) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 368
    def quit n, &_b
      # ignore n
      sock do |s|
        s.puts "quit"
      end
    end

readline(prompt) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 340
    def readline prompt
      input = (sock(skip: CONFIG[:skip_bp]) do |s|
        next unless s

        if @repl
          raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
          line = "input #{Process.pid}"
          DEBUGGER__.debug{ "send: #{line}" }
          s.puts line
        end
        sleep 0.01 until @q_msg
        @q_msg.pop.tap{|msg|
          DEBUGGER__.debug{ "readline: #{msg.inspect}" }
        }
      end || 'continue')

      if input.is_a?(String)
        input.strip
      else
        input
      end
    end

remote?() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 225
    def remote?
      true
    end

setup_interrupt() { || ... } click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 257
    def setup_interrupt
      prev_handler = trap(TRAP_SIGNAL) do
        # $stderr.puts "trapped SIGINT"
        ThreadClient.current.on_trap TRAP_SIGNAL

        case prev_handler
        when Proc
          prev_handler.call
        else
          # ignore
        end
      end

      if sigurg_overridden?(prev_handler)
        DEBUGGER__.warn "SIGURG handler is overridden by the debugger."
      end
      yield
    ensure
      trap(TRAP_SIGNAL, prev_handler)
    end

sigurg_overridden?(prev_handler) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 233
    def sigurg_overridden? prev_handler
      case prev_handler
      when "SYSTEM_DEFAULT", "DEFAULT"
        false
      when Proc
        if prev_handler.source_location[0] == __FILE__
          false
        else
          true
        end
      else
        true
      end
    end

sock(skip: false) { |nil| ... } click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 282
    def sock skip: false
      if s = @sock         # already connection
        # ok
      elsif skip == true   # skip process
        no_sock = true
        r = @accept_m.synchronize do
          if @sock
            no_sock = false
          else
            yield nil
          end
        end
        return r if no_sock
      else                 # wait for connection
        until s = @sock
          @accept_m.synchronize{
            unless @sock
              DEBUGGER__.warn "wait for debugger connection..."
              @accept_cv.wait(@accept_m)
            end
          }
        end
      end

      yield s
    rescue Errno::EPIPE
      # ignore
    end

vscode_setup(debug_port) click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 379
    def vscode_setup debug_port
      require_relative 'server_dap'
      UI_DAP.setup debug_port
    end

width() click to toggle source

    # File debug-1.10.0/lib/debug/server.rb, line 229
    def width
      @width
    end