ruby / debug

Debugging functionality for Ruby
BSD 2-Clause "Simplified" License
1.13k stars 126 forks source link

Remote debugging of `rake test` raises `Errno::EADDRINUSE` #368

Open suketa opened 2 years ago

suketa commented 2 years ago

Your environment

Describe the bug remote debugging with rake test raises Errno::EADDRINUSE

To Reproduce Rakefile:

require 'rake/testtask'

Rake::TestTask.new(:test) do |t|
  t.test_files = ['test_example.rb']
end

test_example.rb:

require 'minitest/autorun'

class TestExample < Minitest::Test
  def test_foo
    binding.break
    assert_equal(true, true)
  end
end

And run in console:

$ rdbg -O -p 12345 -c rake test

And connect from another console and type c:

$ rdbg -A 12345
[5, 14] in ~/.asdf/installs/ruby/3.0.2/bin/rake
     5| # The application 'rake' is installed as part of a gem, and
     6| # this file is here to facilitate running it.
     7| #
     8|
     9|
=>  10| version = ">= 0.a"
    11|
    12| str = ARGV.first
    13| if str
    14|   str = str.b[/\A_(.*)_\z/, 1]
=>#0    <main> at ~/.asdf/installs/ruby/3.0.2/bin/rake:10
(rdbg:remote) c    # continue command

Then Errno::EADDRINUSE was raised in server console.

$ rdbg -O -p 12345 -c rake test
DEBUGGER: Debugger can attach via TCP/IP (127.0.0.1:12345)
DEBUGGER: wait for debugger connection...
DEBUGGER: Connected.
/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:131: warning: assigned but unused variable - r
DEBUGGER: wait for debugger connection...
#<Thread:0x0000562eb6c48160@DEBUGGER__::Server::reader /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:43 run> terminated with exception (report_on_exception is true):
/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:201:in `bind': Address already in use - bind(2) for 127.0.0.1:12345 (Errno::EADDRINUSE)
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:201:in `listen'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:765:in `block in tcp_server_sockets'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:227:in `each'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:227:in `foreach'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:763:in `tcp_server_sockets'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:338:in `accept'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:48:in `block in activate'
["DEBUGGER Exception: /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/thread_client.rb:967",
 #<Errno::EADDRINUSE: Address already in use - bind(2) for 127.0.0.1:12345>,
 ["/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:201:in `bind'",
  "/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:201:in `listen'",
  "/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:765:in `block in tcp_server_sockets'",
  "/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:227:in `each'",
  "/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:227:in `foreach'",
  "/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:763:in `tcp_server_sockets'",
  "/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:338:in `accept'",
  "/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:48:in `block in activate'"]]
#<Thread:0x0000562eb6c3ff60@DEBUGGER__::SESSION@server /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/session.rb:144 aborting> terminated with exception (report_on_exception is true):
/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:201:in `bind': Address already in use - bind(2) for 127.0.0.1:12345 (Errno::EADDRINUSE)
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:201:in `listen'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:765:in `block in tcp_server_sockets'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:227:in `each'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:227:in `foreach'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:763:in `tcp_server_sockets'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:338:in `accept'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:48:in `block in activate'
/home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:201:in `bind': Address already in use - bind(2) for 127.0.0.1:12345 (Errno::EADDRINUSE)
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:201:in `listen'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:765:in `block in tcp_server_sockets'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:227:in `each'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:227:in `foreach'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/3.0.0/socket.rb:763:in `tcp_server_sockets'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:338:in `accept'
        from /home/suke/.asdf/installs/ruby/3.0.2/lib/ruby/gems/3.0.0/gems/debug-1.3.4/lib/debug/server.rb:48:in `block in activate'
rake aborted!
Command failed with status (1)

Tasks: TOP => test
(See full trace by running task with --trace)
DEBUGGER: Disconnected.

Expected behavior

Not raise Errno::EADDRINUSE and debugger stops at binding.break line in test_example.rb.

ko1 commented 2 years ago

I'm not sure how the rake test invokes the test process, but I think the following scenario.

(1) Running rake with debugg port == 12345 (2) Create another process for the test (test worker) and tries to open the debug port 12345, but it's already opened at (1).

 6966 pts/0    S+     0:00 ruby /home/ko1/.rbenv/versions/2.7.3/bin/rake test
 6994 pts/0    S+     0:00 sh -c /home/ko1/.rbenv/versions/2.7.3/bin/ruby -w -I"lib" /home/ko1/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rake-13.0.6/lib/rake/rake_test_loader.rb "test_example.rb"
 6995 pts/0    Sl+    0:00 /home/ko1/.rbenv/versions/2.7.3/bin/ruby -w -Ilib /home/ko1/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rake-13.0.6/lib/rake/rake_test_loader.rb test_example.rb

On my environment the ps log shows 2 ruby processes are created and confirms (2).

Now I have no idea how to solve it... Hmm.

Two possible solutions:

(1) use port 0. It assigns free port randomly. (2) Disable debugger at the beginning of Rakefile (not implemented yet).

ko1 commented 2 years ago

It is big change but

(3) introduce remote proxy server for remote debuggees

It seems orthodox method, but it needs a time.

ko1 commented 2 years ago

Now I don't have a good solution on it.

rubensa commented 2 years ago

Think I found a similar problem. See: https://github.com/connorshea/vscode-ruby-test-adapter/issues/92#issuecomment-1191477785

ko1 commented 2 years ago

Wow rdbg is used by another project ;p

gerymate commented 1 week ago

I think using a port range for the debugger to try can be a good compromise. For example a port range can be reliably open on a container. I'd leave the task of deciding which debuggee is which to the user, it can be relative easily done based on the internal state of the debuggees.