readapt 0.8.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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