55require "time"
66
77module DebugSocket
8+ module Commands
9+ # When running `eval`, we don't want the input to overwrite local variables etc. `eval` runs in the current scope,
10+ # so we have an empty scope here that runs in a module that only has other shortcut commands the client might want
11+ # to run.
12+ def self . isolated_eval ( input )
13+ eval ( input ) # rubocop:disable Security/Eval
14+ # We rescue Exception here because the input could have SyntaxErrors etc.
15+ rescue Exception => e # rubocop:disable Lint/RescueException
16+ DebugSocket . logger &.error { "debug-socket-error=#{ e . inspect } input=#{ input . inspect } path=#{ @path } backtrace=#{ e . backtrace . inspect } " }
17+ "#{ e . class . name } : #{ e . message } \n #{ e . backtrace . join ( "\n " ) } "
18+ end
19+
20+ # Print the backtrace for every Thread
21+ def self . backtrace
22+ pid = Process . pid
23+ "#{ Time . now . utc . iso8601 } #{ $PROGRAM_NAME} \n " + Thread . list . map do |thread |
24+ output = "#{ Time . now . utc . iso8601 } pid=#{ pid } thread.object_id=#{ thread . object_id } thread.status=#{ thread . status } "
25+ backtrace = thread . backtrace || [ ]
26+ output << "\n #{ backtrace . join ( "\n " ) } " if backtrace
27+ output
28+ end . join ( "\n \n " )
29+ end
30+ end
31+
832 @thread = nil
933 @pid = Process . pid
1034
@@ -16,7 +40,7 @@ def self.logger
1640 return @logger if defined? ( @logger )
1741
1842 require "logger"
19- @logger = Logger . new ( STDERR )
43+ @logger = Logger . new ( $stderr )
2044 end
2145
2246 def self . start ( path , &block )
@@ -32,22 +56,27 @@ def self.start(path, &block)
3256
3357 server = UNIXServer . new ( @path )
3458 @thread = Thread . new do
59+ errors = 0
3560 loop do
36- begin
37- socket = server . accept
38- input = socket . read
39- logger &.warn ( "debug-socket-command=#{ input . inspect } " )
40-
41- self . perform_audit ( input , &block ) if block
42-
43- socket . puts ( eval ( input ) ) # rubocop:disable Security/Eval
44-
45- rescue Exception => e # rubocop:disable Lint/RescueException
46- logger &.error { "debug-socket-error=#{ e . inspect } backtrace=#{ e . backtrace . inspect } " }
47- ensure
48- socket &.close
49- end
61+ socket = server . accept
62+ input = socket . read
63+ logger &.warn ( "debug-socket-command=#{ input . inspect } " )
64+
65+ perform_audit ( input , &block ) if block
66+ socket . puts ( Commands . isolated_eval ( input ) )
67+
68+ errors = 0
69+ rescue StandardError => e
70+ errors += 1
71+ logger &.error { "debug-socket-error=#{ e . inspect } errors=#{ errors } path=#{ @path } backtrace=#{ e . backtrace . inspect } " }
72+ raise e if errors > 20
73+
74+ sleep ( 1 )
75+ ensure
76+ socket &.close
5077 end
78+ rescue Exception => e # rubocop:disable Lint/RescueException
79+ logger &.error { "debug-socket-error=#{ e . inspect } DebugSocket is broken now path=#{ @path } backtrace=#{ e . backtrace . inspect } " }
5180 end
5281
5382 logger &.unknown { "Debug socket listening on #{ @path } " }
@@ -65,21 +94,10 @@ def self.stop
6594 @path = nil
6695 end
6796
68- def self . backtrace
69- pid = Process . pid
70- "#{ Time . now . utc . iso8601 } #{ $PROGRAM_NAME} \n " + Thread . list . map do |thread |
71- output =
72- +"#{ Time . now . utc . iso8601 } pid=#{ pid } thread.object_id=#{ thread . object_id } thread.status=#{ thread . status } "
73- backtrace = thread . backtrace || [ ]
74- output << "\n #{ backtrace . join ( "\n " ) } " if backtrace
75- output
76- end . join ( "\n \n " )
77- end
78-
7997 # Allow debug socket input commands to be audited by an external callback
8098 private_class_method def self . perform_audit ( input )
8199 yield input
82- rescue Exception => e
83- logger &.error "debug-socket-error=callback unsuccessful: #{ e . inspect } for #{ input . inspect } socket_path =#{ @path } "
100+ rescue Exception => e # rubocop:disable Lint/RescueException
101+ logger &.error "debug-socket-error=callback unsuccessful: #{ e . inspect } for #{ input . inspect } path =#{ @path } backtrace= #{ e . backtrace . inspect } "
84102 end
85103end
0 commit comments