Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Love for remote debugging #406

Merged
merged 27 commits into from
Jan 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ffb1648
Extract client to a separate class
deivid-rodriguez Dec 30, 2017
a367155
Document control port
deivid-rodriguez Dec 31, 2017
944e07b
Consistent port setting
deivid-rodriguez Dec 31, 2017
2711a69
Improve docs
deivid-rodriguez Dec 31, 2017
74b8488
Normalize argument name
deivid-rodriguez Dec 31, 2017
4a5f884
Only need mutexes if waiting for clients
deivid-rodriguez Dec 31, 2017
a835a22
Don't care about start_control return value
deivid-rodriguez Dec 31, 2017
3751bda
Remove unnecessary line
deivid-rodriguez Dec 31, 2017
2b08eee
Extract server to a separate class
deivid-rodriguez Dec 30, 2017
15befcb
Extract a method
deivid-rodriguez Dec 30, 2017
e7279ed
Fix remote debugging regression
deivid-rodriguez Dec 30, 2017
7497035
Don't crash when client disconnects
deivid-rodriguez Dec 30, 2017
cfc5cff
Clarify a comment
deivid-rodriguez Dec 31, 2017
3e8ad87
Disable all rubocop metrics
deivid-rodriguez Jan 8, 2018
f75e7ac
Shortcut for starting a remote debugger
deivid-rodriguez Dec 31, 2017
ceebb4d
Test server & control threads are ignored
deivid-rodriguez Dec 31, 2017
d49fe2c
Make sure byebug is started before interrupting it
deivid-rodriguez Dec 31, 2017
3dc1e79
Remove unnecessary `Byebug.start`
deivid-rodriguez Dec 31, 2017
9ea4837
Log commands in CI script
deivid-rodriguez Jan 3, 2018
cafc309
See if greater timeout works on Windows
deivid-rodriguez Jan 4, 2018
e38b585
remote_test.rb - windows - add -I ruby parameter, change fork to thread
MSP-Greg Jan 5, 2018
4ee6d45
Update interfaces/remote_interface.rb for Windows
MSP-Greg Jan 6, 2018
223ea91
Refactor
deivid-rodriguez Jan 8, 2018
0182286
Further refactor
deivid-rodriguez Jan 12, 2018
dd7548f
Increase and normalize timeouts
deivid-rodriguez Jan 12, 2018
8359dfe
Fix windows check
deivid-rodriguez Jan 12, 2018
62fbd43
Rescue the same stuff everywhere
deivid-rodriguez Jan 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,7 @@ Layout/ExtraSpacing:
Lint/InterpolationCheck:
Enabled: false

Metrics/AbcSize:
Enabled: false

Metrics/ClassLength:
Enabled: false

Metrics/CyclomaticComplexity:
Enabled: false

Metrics/MethodLength:
Enabled: false

Metrics/PerceivedComplexity:
Metrics:
Enabled: false

TrivialAccessors:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
* Show valid breakpoint locations when invalid location given (#393, @ko1).
* Ruby 2.5.0 support (#397, @yui-knk).
* Log host & port when launching byebug's client in remote mode.
* Some love & tests to remote debugging (#82).
* `remote_byebug` shortcut to start the most common case for remote debugging (#141).

## Fixed

* Properly ignore ruby fullpath executable when passed to byebug script (#419).
* Remote server crash when interrupting client (#141, #274).
* Control server thread being able to `interrupt` main thread only a single time. (#239).

## 9.1.0 - 2017-08-22

Expand Down
4 changes: 4 additions & 0 deletions bin/ci.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/usr/bin/env bash

set +x

bin/bundle install --jobs 3 --retry 3 --path .bundle/gems
bin/rake

set -x
16 changes: 14 additions & 2 deletions lib/byebug/attacher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
#
module Byebug
#
# Enters byebug right before (or right after if _before_ is false) return
# events occur. Before entering byebug the init script is read.
# Starts byebug, and stops at the first line of user's code.
#
def self.attach
require "byebug/core"
Expand All @@ -20,6 +19,13 @@ def self.attach

current_context.step_out(3, true)
end

def self.spawn(host = "localhost", port = nil)
require "byebug/core"

self.wait_connection = true
start_server(host, port || PORT)
end
end

#
Expand All @@ -32,5 +38,11 @@ def byebug
Byebug.attach
end

def remote_byebug(host = "localhost", port = nil)
Byebug.spawn(host, port)

Byebug.attach
end

alias debugger byebug
end
2 changes: 2 additions & 0 deletions lib/byebug/commands/interrupt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def self.short_description
end

def execute
Byebug.start

Byebug.thread_context(Thread.main).interrupt
end
end
Expand Down
26 changes: 18 additions & 8 deletions lib/byebug/interfaces/remote_interface.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,35 @@ def initialize(socket)

def read_command(prompt)
super("PROMPT #{prompt}")
rescue Errno::EPIPE, Errno::ECONNABORTED
"continue"
end

def confirm(prompt)
super("CONFIRM #{prompt}")
rescue Errno::EPIPE, Errno::ECONNABORTED
false
end

def print(message)
super(message)
rescue Errno::EPIPE, Errno::ECONNABORTED
nil
end

def puts(message)
super(message)
rescue Errno::EPIPE, Errno::ECONNABORTED
nil
end

def close
output.close
rescue IOError
errmsg("Error closing the interface...")
end

def readline(prompt)
output.puts(prompt)

result = input.gets
raise IOError unless result

result.chomp
puts(prompt)
(input.gets || "continue").chomp
end
end
end
1 change: 0 additions & 1 deletion lib/byebug/processors/command_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ def before_repl

def after_repl
interface.autosave
interface.close
end

#
Expand Down
6 changes: 6 additions & 0 deletions lib/byebug/processors/script_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ def repl
end
end

def after_repl
super

interface.close
end

#
# Prompt shown before reading a command.
#
Expand Down
101 changes: 39 additions & 62 deletions lib/byebug/remote.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

require "socket"
require "byebug/processors/control_processor"
require "byebug/remote/server"
require "byebug/remote/client"

#
# Remote debugging functionality.
#
# @todo Refactor & add tests
#
module Byebug
# Port number used for remote debugging
PORT = 8989 unless defined?(PORT)
Expand All @@ -17,8 +17,14 @@ class << self
attr_accessor :wait_connection

# The actual port that the server is started at
attr_accessor :actual_port
attr_reader :actual_control_port
def actual_port
server.actual_port
end

# The actual port that the control server is started at
def actual_control_port
control.actual_port
end

#
# Interrupts the current thread
Expand All @@ -28,81 +34,52 @@ def interrupt
end

#
# Starts a remote byebug
# Starts the remote server main thread
#
def start_server(host = nil, port = PORT)
return if @thread

Context.interface = nil
start

start_control(host, port.zero? ? 0 : port + 1)

mutex = Mutex.new
proceed = ConditionVariable.new

server = TCPServer.new(host, port)
self.actual_port = server.addr[1]

yield if block_given?

@thread = DebugThread.new do
while (session = server.accept)
Context.interface = RemoteInterface.new(session)
mutex.synchronize { proceed.signal } if wait_connection
end
end

mutex.synchronize { proceed.wait(mutex) } if wait_connection
server.start(host, port)
end

def start_control(host = nil, ctrl_port = PORT + 1)
return @actual_control_port if @control_thread
server = TCPServer.new(host, ctrl_port)
@actual_control_port = server.addr[1]

@control_thread = DebugThread.new do
while (session = server.accept)
context = Byebug.current_context
interface = RemoteInterface.new(session)

ControlProcessor.new(context, interface).process_commands
end
end

@actual_control_port
#
# Starts the remote server control thread
#
def start_control(host = nil, port = PORT + 1)
control.start(host, port)
end

#
# Connects to the remote byebug
#
def start_client(host = "localhost", port = PORT)
interface = LocalInterface.new
puts "Connecting to byebug server at #{host}:#{port}..."
socket = TCPSocket.new(host, port)
puts "Connected."

while (line = socket.gets)
case line
when /^PROMPT (.*)$/
input = interface.read_command(Regexp.last_match[1])
break unless input
socket.puts input
when /^CONFIRM (.*)$/
input = interface.readline(Regexp.last_match[1])
break unless input
socket.puts input
else
puts line
end
end

socket.close
client.start(host, port)
end

def parse_host_and_port(host_port_spec)
location = host_port_spec.split(":")
location[1] ? [location[0], location[1].to_i] : ["localhost", location[0]]
end

private

def client
@client ||= Remote::Client.new(Context.interface)
end

def server
@server ||= Remote::Server.new(wait_connection: wait_connection) do |s|
Context.interface = RemoteInterface.new(s)
end
end

def control
@control ||= Remote::Server.new(wait_connection: false) do |s|
context = Byebug.current_context
interface = RemoteInterface.new(s)

ControlProcessor.new(context, interface).process_commands
end
end
end
end
51 changes: 51 additions & 0 deletions lib/byebug/remote/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require "socket"

module Byebug
module Remote
#
# Client for remote debugging
#
class Client
attr_reader :interface

def initialize(interface)
@interface = interface
end

#
# Connects to the remote byebug
#
def start(host = "localhost", port = PORT)
socket = connect_at(host, port)

while (line = socket.gets)
case line
when /^PROMPT (.*)$/
input = interface.read_command(Regexp.last_match[1])
break unless input
socket.puts input
when /^CONFIRM (.*)$/
input = interface.readline(Regexp.last_match[1])
break unless input
socket.puts input
else
interface.puts line
end
end

socket.close
end

private

def connect_at(host, port)
interface.puts "Connecting to byebug server at #{host}:#{port}..."
socket = TCPSocket.new(host, port)
interface.puts "Connected."
socket
end
end
end
end
47 changes: 47 additions & 0 deletions lib/byebug/remote/server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require "socket"

module Byebug
module Remote
#
# Server for remote debugging
#
class Server
attr_reader :actual_port, :wait_connection

def initialize(wait_connection:, &block)
@thread = nil
@wait_connection = wait_connection
@main_loop = block
end

#
# Start the remote debugging server
#
def start(host, port)
return if @thread

if wait_connection
mutex = Mutex.new
proceed = ConditionVariable.new
end

server = TCPServer.new(host, port)
@actual_port = server.addr[1]

yield if block_given?

@thread = DebugThread.new do
while (session = server.accept)
@main_loop.call(session)

mutex.synchronize { proceed.signal } if wait_connection
end
end

mutex.synchronize { proceed.wait(mutex) } if wait_connection
end
end
end
end
Loading