readapt 0.8.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a783dd879a972485872f13d31e299c02b223a3bea89c3be764f9b21448a5fd7
4
- data.tar.gz: 52776a0cfc2befe9215b2da2f828c9d82f7c8858fcad29ff4c052aef4fc70cc5
3
+ metadata.gz: 901cb61ade9e11ed52cdfdd77c8a33ab45e8147633104172bb421e48bc4cf8ab
4
+ data.tar.gz: 46f354915b3c929de58ad7d675793f154ce871eea70536eba5629dd9d639cafe
5
5
  SHA512:
6
- metadata.gz: be0687dddd12bcee0d2529262f3bae3f859da3c3d17fe3fdebd1b3631f47cd51ad2b09493ea1f6172af74dae41c0eae4ee91918d5eb89da7314620271ada51b5
7
- data.tar.gz: 5152ece20452deea044bd6864e7f8092d2cb0c9d39bc8d89e15c3f5c9782cf67d35db9c5ca3f5cd81496724c839dd3d2dc98a2efde32258ca094c9a59c57de21
6
+ metadata.gz: 8cbb171010dee79d13a39c11f620ce98b3a53fe1ba7ae70b37ceebdace363d7e52a2bb73c405e9835a92741d23478667901ddefa6e7a142c5df97cd5613256d2
7
+ data.tar.gz: c1c394e97e64a67bdd196babc0b67caa9f18e29cac03aca88cca6955ba27f6048db48f32645cf9b6ded3807d0169f2ecbe5684fa1ce23538a1760ca2ff1bd962
data/.travis.yml CHANGED
@@ -7,6 +7,7 @@ rvm:
7
7
  - 2.4
8
8
  - 2.5
9
9
  - 2.6
10
+ - 2.7
10
11
  matrix:
11
12
  include:
12
13
  - rvm: 2.6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 1.0.0 - February 9, 2020
2
+ - Refactor server and target process communication
3
+ - STDIO server support
4
+ - Fix zombie process bugs
5
+ - Sort variables alphabetically
6
+
1
7
  # 0.8.1 - November 14, 2019
2
8
  - Header name conflict in MacOS (#4)
3
9
 
data/lib/readapt.rb CHANGED
@@ -11,8 +11,10 @@ require 'readapt/finder'
11
11
  require 'readapt/debugger'
12
12
  require 'readapt/message'
13
13
  require 'readapt/variable'
14
+ require 'readapt/data_reader'
15
+ require 'readapt/server'
14
16
  require 'readapt/adapter'
17
+ require 'readapt/input'
18
+ require 'readapt/output'
19
+ require 'readapt/error'
15
20
  require 'readapt/shell'
16
-
17
- module Readapt
18
- end
@@ -12,6 +12,22 @@ module Readapt
12
12
  @@debugger = debugger
13
13
  end
14
14
 
15
+ def self.procid= pid
16
+ @@procid = pid
17
+ end
18
+
19
+ def procid
20
+ @@procid
21
+ end
22
+
23
+ def open_message
24
+ @@open_message ||= "<readapt-#{procid}>"
25
+ end
26
+
27
+ def close_message
28
+ @@close_message ||= "</readapt-#{procid}>"
29
+ end
30
+
15
31
  def format result
16
32
  write_line result.to_protocol.to_json
17
33
  end
@@ -39,8 +55,9 @@ module Readapt
39
55
  }
40
56
  obj[:body] = data unless data.nil?
41
57
  json = obj.to_json
42
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
58
+ envelope = "#{open_message}Content-Length: #{json.bytesize}\r\n\r\n#{json}#{close_message}"
43
59
  write envelope
60
+ write "#{open_message}__TERMINATE__#{close_message}" if event == 'terminated'
44
61
  end
45
62
 
46
63
  private
@@ -48,7 +65,6 @@ module Readapt
48
65
  # @param data [Hash]
49
66
  # @return [void]
50
67
  def process data
51
- # @todo Better solution than nil frames
52
68
  message = Message.process(data, @@debugger)
53
69
  if data['seq']
54
70
  json = {
@@ -59,80 +75,24 @@ module Readapt
59
75
  body: message.body
60
76
  }.to_json
61
77
  envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
62
- write envelope
63
- close if data['command'] == 'disconnect'
78
+ write "#{open_message}#{envelope}#{close_message}"
79
+ if data['command'] == 'disconnect'
80
+ @@debugger.disconnect
81
+ # @todo It does not appear necessary to close the adapter after
82
+ # disconnecting the debugger.
83
+ # close
84
+ end
64
85
  return unless data['command'] == 'initialize'
65
86
  json = {
66
87
  type: 'event',
67
88
  event: 'initialized'
68
89
  }.to_json
69
90
  envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
70
- write envelope
91
+ write "#{open_message}#{envelope}#{close_message}"
71
92
  end
72
93
  rescue RuntimeError => e
73
94
  STDERR.puts "[#{e.class}] #{e.message}"
74
95
  STDERR.puts e.backtrace.join
75
96
  end
76
97
  end
77
-
78
- class DataReader
79
- def initialize
80
- @in_header = true
81
- @content_length = 0
82
- @buffer = String.new
83
- end
84
-
85
- # Declare a block to be executed for each message received from the
86
- # client.
87
- #
88
- # @yieldparam [Hash] The message received from the client
89
- def set_message_handler &block
90
- @message_handler = block
91
- end
92
-
93
- # Process raw data received from the client. The data will be parsed
94
- # into messages based on the JSON-RPC protocol. Each message will be
95
- # passed to the block declared via set_message_handler. Incomplete data
96
- # will be buffered and subsequent data will be appended to the buffer.
97
- #
98
- # @param data [String]
99
- def receive data
100
- data.each_char do |char|
101
- @buffer.concat char
102
- if @in_header
103
- prepare_to_parse_message if @buffer.end_with?("\r\n\r\n")
104
- else
105
- parse_message_from_buffer if @buffer.bytesize == @content_length
106
- end
107
- end
108
- end
109
-
110
- private
111
-
112
- def prepare_to_parse_message
113
- @in_header = false
114
- @buffer.each_line do |line|
115
- parts = line.split(':').map(&:strip)
116
- if parts[0] == 'Content-Length'
117
- @content_length = parts[1].to_i
118
- break
119
- end
120
- end
121
- @buffer.clear
122
- end
123
-
124
- def parse_message_from_buffer
125
- begin
126
- msg = JSON.parse(@buffer)
127
- @message_handler.call msg unless @message_handler.nil?
128
- rescue JSON::ParserError => e
129
- Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}"
130
- Solargraph::Logging.logger.debug "Buffer: #{@buffer}"
131
- ensure
132
- @buffer.clear
133
- @in_header = true
134
- @content_length = 0
135
- end
136
- end
137
- end
138
98
  end
@@ -0,0 +1,62 @@
1
+ module Readapt
2
+ class DataReader
3
+ def initialize
4
+ @in_header = true
5
+ @content_length = 0
6
+ @buffer = String.new
7
+ end
8
+
9
+ # Declare a block to be executed for each message received from the
10
+ # client.
11
+ #
12
+ # @yieldparam [Hash] The message received from the client
13
+ def set_message_handler &block
14
+ @message_handler = block
15
+ end
16
+
17
+ # Process raw data received from the client. The data will be parsed
18
+ # into messages based on the JSON-RPC protocol. Each message will be
19
+ # passed to the block declared via set_message_handler. Incomplete data
20
+ # will be buffered and subsequent data will be appended to the buffer.
21
+ #
22
+ # @param data [String]
23
+ def receive data
24
+ data.each_char do |char|
25
+ @buffer.concat char
26
+ if @in_header
27
+ prepare_to_parse_message if @buffer.end_with?("\r\n\r\n")
28
+ else
29
+ parse_message_from_buffer if @buffer.bytesize == @content_length
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def prepare_to_parse_message
37
+ @in_header = false
38
+ @buffer.each_line do |line|
39
+ parts = line.split(':').map(&:strip)
40
+ if parts[0] == 'Content-Length'
41
+ @content_length = parts[1].to_i
42
+ break
43
+ end
44
+ end
45
+ @buffer.clear
46
+ end
47
+
48
+ def parse_message_from_buffer
49
+ begin
50
+ msg = JSON.parse(@buffer)
51
+ @message_handler.call msg unless @message_handler.nil?
52
+ rescue JSON::ParserError => e
53
+ Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}"
54
+ Solargraph::Logging.logger.debug "Buffer: #{@buffer}"
55
+ ensure
56
+ @buffer.clear
57
+ @in_header = true
58
+ @content_length = 0
59
+ end
60
+ end
61
+ end
62
+ end
@@ -13,7 +13,7 @@ module Readapt
13
13
 
14
14
  attr_reader :file
15
15
 
16
- def initialize machine = Machine.new
16
+ def initialize
17
17
  @stack = []
18
18
  @frames = {}
19
19
  @running = false
@@ -22,7 +22,6 @@ module Readapt
22
22
  @config = {}
23
23
  @original_argv = ARGV.clone
24
24
  @original_prog = $0
25
- @machine = machine
26
25
  @breakpoints = {}
27
26
  end
28
27
 
@@ -57,13 +56,14 @@ module Readapt
57
56
 
58
57
  def start
59
58
  ::Thread.new do
59
+ set_program_args
60
60
  run { load @file }
61
+ set_original_args
61
62
  end
62
63
  end
63
64
 
64
65
  def run
65
66
  # raise RuntimeError, 'Debugger is already running' if @running
66
- set_program_args
67
67
  @running = true
68
68
  send_event('process', {
69
69
  name: @file
@@ -75,14 +75,11 @@ module Readapt
75
75
  rescue StandardError => e
76
76
  STDERR.puts "[#{e.class}] #{e.message}"
77
77
  STDERR.puts e.backtrace.join("\n")
78
- rescue SystemExit
79
- # Ignore
80
78
  ensure
81
79
  Monitor.stop
82
80
  @running = false
83
- set_original_args
84
- STDOUT.flush
85
- STDERR.flush
81
+ STDOUT.flush #unless STDOUT.closed?
82
+ STDERR.flush #unless STDERR.closed?
86
83
  changed
87
84
  send_event 'terminated', nil
88
85
  end
@@ -103,7 +100,7 @@ module Readapt
103
100
  end
104
101
 
105
102
  def clear_breakpoints source
106
- @breakpoints.delete_if { |key, value|
103
+ @breakpoints.delete_if { |_key, value|
107
104
  value.source == source
108
105
  }
109
106
  end
@@ -211,7 +208,6 @@ module Readapt
211
208
  end
212
209
 
213
210
  def shutdown
214
- @machine.stop
215
211
  exit
216
212
  end
217
213
 
@@ -0,0 +1,63 @@
1
+ require 'securerandom'
2
+
3
+ module Readapt
4
+ module Error
5
+ class << self
6
+ attr_accessor :adapter
7
+ end
8
+
9
+ def opening
10
+ @buffer = ''
11
+ end
12
+
13
+ def receiving data
14
+ output = ''
15
+ data.each_char do |char|
16
+ @buffer += char
17
+ if open_message.start_with?(@buffer) || @buffer.start_with?(open_message)
18
+ if @buffer.end_with?(close_message)
19
+ msg = @buffer[open_message.length..-(close_message.length+1)]
20
+ exit if msg == '__TERMINATE__'
21
+ Error.adapter.write msg
22
+ @buffer.clear
23
+ end
24
+ else
25
+ output += @buffer
26
+ @buffer.clear
27
+ end
28
+ end
29
+ return if output.empty?
30
+ send_event('output', {
31
+ output: output,
32
+ category: 'stderr'
33
+ })
34
+ end
35
+
36
+ def send_event event, data
37
+ obj = {
38
+ type: 'event',
39
+ event: event
40
+ }
41
+ obj[:body] = data unless data.nil?
42
+ json = obj.to_json
43
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
44
+ Error.adapter.write envelope
45
+ end
46
+
47
+ def self.procid= pid
48
+ @@procid = pid
49
+ end
50
+
51
+ def procid
52
+ @@procid
53
+ end
54
+
55
+ def open_message
56
+ @@open_message ||= "<readapt-#{procid}>"
57
+ end
58
+
59
+ def close_message
60
+ @@close_message ||= "</readapt-#{procid}>"
61
+ end
62
+ end
63
+ end
data/lib/readapt/frame.rb CHANGED
@@ -22,7 +22,7 @@ module Readapt
22
22
  def locals
23
23
  return [] if frame_binding.nil?
24
24
  result = []
25
- frame_binding.local_variables.each do |sym|
25
+ frame_binding.local_variables.sort.each do |sym|
26
26
  var = frame_binding.local_variable_get(sym)
27
27
  result.push Variable.new(sym, var)
28
28
  end
@@ -0,0 +1,7 @@
1
+ module Readapt
2
+ module Input
3
+ def receiving data
4
+ print data
5
+ end
6
+ end
7
+ end
@@ -4,10 +4,9 @@ module Readapt
4
4
  module Message
5
5
  class Disconnect < Base
6
6
  def run
7
- # HACK: Wait a moment to make sure the output is flushed
8
- # @todo Find a better way
9
- sleep 1
10
- debugger.disconnect
7
+ # The message only sets an empty body to acknowledge that the request
8
+ # was received. The adapter handles the actual disconnection process.
9
+ set_body({})
11
10
  end
12
11
  end
13
12
  end
@@ -27,10 +27,10 @@ module Readapt
27
27
  result.push Variable.new("[#{idx}]", itm)
28
28
  end
29
29
  else
30
- obj.instance_variables.each do |iv|
30
+ obj.instance_variables.sort.each do |iv|
31
31
  result.push Variable.new(iv, obj.instance_variable_get(iv))
32
32
  end
33
- obj.class.class_variables.each do |cv|
33
+ obj.class.class_variables.sort.each do |cv|
34
34
  result.push Variable.new(cv, obj.class.class_variable_get(cv))
35
35
  end
36
36
  end
@@ -0,0 +1,25 @@
1
+ module Readapt
2
+ module Output
3
+ class << self
4
+ attr_accessor :adapter
5
+ end
6
+
7
+ def receiving data
8
+ send_event('output', {
9
+ output: data,
10
+ category: 'stdout'
11
+ })
12
+ end
13
+
14
+ def send_event event, data
15
+ obj = {
16
+ type: 'event',
17
+ event: event
18
+ }
19
+ obj[:body] = data unless data.nil?
20
+ json = obj.to_json
21
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
22
+ Output.adapter.write envelope
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'securerandom'
2
+ require 'stringio'
3
+
4
+ module Readapt
5
+ module Server
6
+ class << self
7
+ attr_accessor :target_in
8
+ attr_accessor :target_pid
9
+ end
10
+
11
+ def opening
12
+ Error.adapter = self
13
+ Output.adapter = self
14
+ end
15
+
16
+ def receiving data
17
+ Server.target_in.syswrite data
18
+ rescue Errno::EPIPE, IOError
19
+ close
20
+ end
21
+ end
22
+ end
data/lib/readapt/shell.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  require 'thor'
2
2
  require 'backport'
3
+ require 'open3'
4
+ require 'securerandom'
5
+ require 'socket'
3
6
 
4
7
  module Readapt
5
8
  class Shell < Thor
@@ -10,31 +13,91 @@ module Readapt
10
13
  puts Readapt::VERSION
11
14
  end
12
15
 
13
- desc 'serve', 'Run a DAP server'
16
+ desc 'socket', 'Run a DAP server over TCP'
14
17
  option :host, type: :string, aliases: :h, description: 'The server host', default: '127.0.0.1'
15
18
  option :port, type: :numeric, aliases: :p, description: 'The server port', default: 1234
16
- def serve
19
+ def socket
20
+ machine = Backport::Machine.new
21
+ machine.run do
22
+ prepare_machine machine
23
+ server = Backport::Server::Tcpip.new(host: options[:host], port: options[:port], adapter: Readapt::Server)
24
+ machine.prepare server
25
+ STDERR.puts "Readapt Debugger #{Readapt::VERSION} is listening HOST=#{options[:host]} PORT=#{options[:port]} PID=#{Process.pid}"
26
+ end
27
+ end
28
+ map serve: :socket
29
+
30
+ desc 'stdio', 'Run a DAP server over STDIO'
31
+ def stdio
32
+ machine = Backport::Machine.new
33
+ machine.run do
34
+ prepare_machine machine
35
+ server = Backport::Server::Stdio.new(adapter: Readapt::Server)
36
+ machine.prepare server
37
+ end
38
+ end
39
+
40
+ desc 'target [PROCID]', 'Run a target process'
41
+ def target procid = nil
42
+ STDIN.binmode
43
+ STDOUT.binmode
44
+ STDERR.binmode
17
45
  STDOUT.sync = true
18
46
  STDERR.sync = true
47
+ Readapt::Adapter.procid = procid
19
48
  machine = Backport::Machine.new
49
+ Signal.trap("INT") do
50
+ graceful_shutdown machine
51
+ end
52
+ Signal.trap("TERM") do
53
+ graceful_shutdown machine
54
+ end
20
55
  machine.run do
21
- Signal.trap("INT") do
22
- graceful_shutdown
23
- end
24
- Signal.trap("TERM") do
25
- graceful_shutdown
26
- end
27
- debugger = Readapt::Debugger.new(machine)
56
+ debugger = Readapt::Debugger.new
28
57
  Readapt::Adapter.host debugger
29
- machine.prepare Backport::Server::Tcpip.new(host: options[:host], port: options[:port], adapter: Readapt::Adapter)
30
- STDERR.puts "Readapt Debugger #{Readapt::VERSION} is listening HOST=#{options[:host]} PORT=#{options[:port]} PID=#{Process.pid}"
58
+ machine.prepare Backport::Server::Stdio.new(input: STDIN, output: STDERR, adapter: Readapt::Adapter)
31
59
  end
32
60
  end
33
61
 
34
62
  private
35
63
 
36
- def graceful_shutdown
37
- Backport.stop
64
+ # @param machine [Backport::Machine]
65
+ # @return [void]
66
+ def prepare_machine machine
67
+ STDOUT.sync = true
68
+ STDERR.sync = true
69
+ Signal.trap("INT") do
70
+ graceful_shutdown machine
71
+ end
72
+ Signal.trap("TERM") do
73
+ graceful_shutdown machine
74
+ end
75
+ procid = SecureRandom.hex(8)
76
+ Readapt::Error.procid = procid
77
+ stdin, stdout, stderr, thr = Open3.popen3('ruby', $0, 'target', procid)
78
+ stdin.sync = true
79
+ stdout.sync = true
80
+ stderr.sync = true
81
+ stdin.binmode
82
+ Readapt::Server.target_in = stdin
83
+ Readapt::Server.target_pid = thr[:pid]
84
+ output = Backport::Server::Stdio.new(input: stdout, output: stdin, adapter: Readapt::Output)
85
+ error = Backport::Server::Stdio.new(input: stderr, output: stdin, adapter: Readapt::Error)
86
+ machine.prepare output
87
+ machine.prepare error
88
+ at_exit do
89
+ begin
90
+ Process.kill 'KILL', thr[:pid]
91
+ rescue Errno::ESRCH
92
+ # Ignore
93
+ end
94
+ end
95
+ end
96
+
97
+ # @param machine [Backport::Machine]
98
+ # @return [void]
99
+ def graceful_shutdown machine
100
+ machine.stop
38
101
  exit
39
102
  end
40
103
  end
@@ -17,12 +17,7 @@ module Readapt
17
17
  end
18
18
 
19
19
  def object
20
- STDERR.puts "Getting #{id}"
21
20
  ObjectSpace._id2ref(id)
22
21
  end
23
-
24
- # def frames
25
- # @frames ||= []
26
- # end
27
22
  end
28
23
  end
@@ -1,3 +1,3 @@
1
1
  module Readapt
2
- VERSION = "0.8.1"
2
+ VERSION = "1.0.0"
3
3
  end
data/readapt.gemspec CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.required_ruby_version = '>= 2.2'
31
31
 
32
32
  spec.add_dependency 'backport', '~> 1.1'
33
- spec.add_dependency 'thor', '~> 0.20'
33
+ spec.add_dependency 'thor', '~> 1.0'
34
34
 
35
35
  spec.add_development_dependency "rake", "~> 10.0"
36
36
  spec.add_development_dependency "rake-compiler", "~> 1.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: readapt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-14 00:00:00.000000000 Z
11
+ date: 2020-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.20'
33
+ version: '1.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.20'
40
+ version: '1.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -137,9 +137,12 @@ files:
137
137
  - lib/readapt.rb
138
138
  - lib/readapt/adapter.rb
139
139
  - lib/readapt/breakpoint.rb
140
+ - lib/readapt/data_reader.rb
140
141
  - lib/readapt/debugger.rb
142
+ - lib/readapt/error.rb
141
143
  - lib/readapt/finder.rb
142
144
  - lib/readapt/frame.rb
145
+ - lib/readapt/input.rb
143
146
  - lib/readapt/message.rb
144
147
  - lib/readapt/message/attach.rb
145
148
  - lib/readapt/message/base.rb
@@ -160,6 +163,8 @@ files:
160
163
  - lib/readapt/message/threads.rb
161
164
  - lib/readapt/message/variables.rb
162
165
  - lib/readapt/monitor.rb
166
+ - lib/readapt/output.rb
167
+ - lib/readapt/server.rb
163
168
  - lib/readapt/shell.rb
164
169
  - lib/readapt/snapshot.rb
165
170
  - lib/readapt/thread.rb