rtinspect 0.0.1 → 0.0.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,71 @@
1
+ # Contains the RuntimeInspection::Tracer.
2
+ #
3
+ # Copyright (c) 2006-2007 Brad Robel-Forrest.
4
+ #
5
+ # This software can be distributed under the terms of the Ruby license
6
+ # as detailed in the accompanying LICENSE[link:files/LICENSE.html]
7
+ # file.
8
+
9
+ #
10
+ module RuntimeInspection
11
+
12
+ # This will help track the tracing of a particular StopPoint in a
13
+ # given thread. Having a tracer assigned to a thread (via the
14
+ # :rti_tracer element stored in the thread from
15
+ # Thread#handle_tracing) ensures that a RTIManager#bp_finish,
16
+ # RTIManager#bp_next, or RTIManager#bp_step will always occur
17
+ # within in the same thread. Note that it is still possible for
18
+ # another breakpoint to "interrupt" this tracer and stop
19
+ # processing somewhere else.
20
+ #
21
+ class Tracer
22
+ def initialize( portal, cmd )
23
+ @portal = portal
24
+ @cmd = cmd
25
+ end
26
+
27
+ # Given a generic StopPoint, adjust it where necessary if we
28
+ # are at the next real StopPoint that should be passed off to
29
+ # the client.
30
+ #
31
+ def stoppoint( sp )
32
+ case @cmd
33
+ when :step
34
+ sp.state = @portal.stoppoint.state
35
+ return sp
36
+ when :next
37
+ case sp.event
38
+ when 'line'
39
+ if( @portal.stoppoint.classname == sp.classname and
40
+ @portal.stoppoint.methodname == sp.methodname )
41
+ sp.state = @portal.stoppoint.state
42
+ return sp
43
+ end
44
+ when 'return'
45
+ if( @portal.stoppoint.classname == sp.classname and
46
+ @portal.stoppoint.methodname == sp.methodname )
47
+ @cmd = :any_line
48
+ end
49
+ end
50
+ return :continue
51
+ when :fin
52
+ if( sp.event == 'return' and
53
+ @portal.stoppoint.classname == sp.classname and
54
+ @portal.stoppoint.methodname == sp.methodname )
55
+ sp.state = @portal.stoppoint.state
56
+ return sp
57
+ end
58
+ when :any_line
59
+ if sp.event == 'line'
60
+ sp.state = @portal.stoppoint.state
61
+ return sp
62
+ else
63
+ return :continue
64
+ end
65
+ else
66
+ raise "Unknown tracer command: #{@cmd}"
67
+ end
68
+ end
69
+ end
70
+
71
+ end
@@ -1,7 +1,6 @@
1
1
  # :main: README
2
- # :title: Runtime Inspection and Debugging Thread
3
2
  #
4
- # Contains the RuntimeInspectionThread.
3
+ # Contains main entry point of the RuntimeInspection module.
5
4
  #
6
5
  # Copyright (c) 2006-2007 Brad Robel-Forrest.
7
6
  #
@@ -11,940 +10,118 @@
11
10
 
12
11
  require 'socket'
13
12
  require 'thread'
14
- require 'timeout'
15
13
  require 'yaml'
16
14
 
17
- # A thread that exposes inspection and debugging functionality over a
18
- # TCP socket.
19
- #
20
- class RuntimeInspectionThread < Thread
21
-
22
- # This is the value used to distinguish data sent to the client
23
- # from within the command evaluation from the value returned from
24
- # the evaluation.
25
- #
26
- OUTPUT_MARKER = '=>'
27
-
28
- if $DEBUG
29
- @@dbg_handler = Proc.new {|msg| puts msg}
30
- else
31
- @@dbg_handler = Proc.new {}
32
- end
33
-
34
- @@exc_handler = Proc.new do |msg, exc|
35
- STDERR.puts( "#{Thread.current}: Exception while #{msg}",
36
- exc, exc.backtrace )
37
- end
38
-
39
- # Optionally enable your own handling of debug output. Default is
40
- # to either suppress it or send it to $stdout if $DEBUG is enabled
41
- # (only checked at class load time).
42
- #
43
- def self.dbg_handler( &block ) # :yields: msg
44
- @@dbg_handler = block
45
- end
46
-
47
- # Optionally enable your own exception handler. Default is to
48
- # write the exception and backtrace out to $stderr.
49
- #
50
- def self.exc_handler( &block ) # :yields: msg, exc
51
- @@exc_handler = block
52
- end
53
-
54
- # Used to manage information found at each stopping point--either
55
- # hit by a BreakPoint or because of a rti_next or rti_step
56
- # request.
57
- #
58
- StopPoint = Struct.new( :classname, :methodname, :file, :line, :event,
59
- :breakpoint )
60
-
61
- # Used as a messaging mechanism once a StopPoint is hit to
62
- # coordinate commands received from the client in a client thread
63
- # (see run_client_thread) with the actual evaluation run from
64
- # within the StopPoint's context (it's binding--see
65
- # handle_tracing).
66
- #
67
- Portal = Struct.new( :stoppoint, :cmds, :out )
68
-
69
- # Used when we want to redirect stdout or stderr for a specific
70
- # thread. This is intelligent enough so that it reuses one if it
71
- # is already in place from another thread.
72
- #
73
- class ThreadRedirect
15
+ module RuntimeInspection
74
16
 
75
- # Synchronization for ensuring we are either using the same
76
- # redirection manager or creating only one new one.
77
- #
78
- MUTEX = Mutex.new
79
-
80
- # Replace the global stdout and stderr with a redirection
81
- # object. If one is already there, then use it.
82
- #
83
- def self.start
84
- MUTEX.synchronize do
85
- if $stdout.kind_of? self
86
- $stdout.usage += 1
87
- else
88
- $stdout = new( $stdout )
89
- end
90
- if $stderr.kind_of? self
91
- $stderr.usage += 1
92
- else
93
- $stderr = new( $stderr )
94
- end
95
- end
96
- return true
97
- end
98
-
99
- # Stop any redirection and put the original IO back in its
100
- # place unless there are still other threads using
101
- # redirection. Then, wait for them to stop
102
- #
103
- def self.stop
104
- MUTEX.synchronize do
105
- unless $stdout.kind_of? self
106
- if $stdout.usage > 0
107
- $stdout.usage -= 1
108
- else
109
- $stdout = $stdout.default_io
110
- end
111
- end
112
- unless $stderr.kind_of? self
113
- if $stderr.usage > 0
114
- $stderr.usage -= 1
115
- else
116
- $stderr = $stderr.default_io
117
- end
118
- end
119
- end
120
- end
121
-
122
- # Create a new redirection manager for the IO stream provided.
123
- #
124
- def initialize( default_io )
125
- @default_io = default_io
126
- @usage = 0
127
- end
128
-
129
- # A reference counter if other threads are also using this
130
- # redirection manager.
131
- #
132
- attr_accessor :usage
133
-
134
- # The IO to use for non-redirected output.
135
- #
136
- attr_reader :default_io
137
-
138
- # Provide the output method so this object can be used as
139
- # either stdout or stderr.
140
- #
141
- def write( *args )
142
- if rio = Thread.current[:rti_redirect_io]
143
- rio.write( *args )
144
- else
145
- @default_io.write( *args )
146
- end
147
- end
148
-
149
- def method_missing( sym, *args, &block )
150
- if rio = Thread.current[:rti_redirect_io]
151
- rio.send( sym, *args, &block )
152
- else
153
- @default_io.send( sym, *args, &block )
154
- end
155
- end
156
-
157
- end
158
-
159
- # Data managed about each TCP connection. Note that these values
160
- # can be manged from the remote session (e.g. by issuing commands
161
- # like <em>state.use_yaml = true</em>).
162
- #
163
- class State < Hash
164
-
165
- # Create a new object for tracking state information.
166
- #
167
- # +socket+:: The owner connection for this data.
168
- #
169
- # +cmd_count+:: The current evaluation number.
170
- #
171
- # +block_count+:: The current block number. When bigger than zero,
172
- # multiple lines are collected until a blank line
173
- # is encountered and then the entire block is
174
- # evaluated. Each successive block will increase
175
- # the number (similar to the normal _cmd_count_
176
- # value).
177
- #
178
- # +block_cmd+:: The collection of the block if the +block_count+
179
- # is larger than zero.
180
- #
181
- # +use_yaml+:: The output should be YAML.
182
- #
183
- # +eval_timeout+:: The number of seconds to allow an evaluation to
184
- # continue. All other connections are blocked during
185
- # this period.
186
- #
187
- # +safe_level+:: The level $SAFE is declared at for each evaluation.
188
- #
189
- def initialize( socket=Thread.current[:socket],
190
- cmd_count=1, block_count=0, block_cmd='',
191
- use_yaml=false, eval_timeout=60, safe_level=3 )
192
- super()
193
- self[:socket] = socket
194
- self[:cmd_count] = cmd_count
195
- self[:block_count] = block_count
196
- self[:block_cmd] = block_cmd
197
- self[:use_yaml] = use_yaml
198
- self[:eval_timeout] = eval_timeout
199
- self[:safe_level] = safe_level
200
- end
201
-
202
- def method_missing( sym, val=nil )
203
- if key?( sym )
204
- self[sym]
205
- else
206
- str = sym.to_s
207
- if str[-1] == ?=
208
- self[str.chop.to_sym] = val
209
- else
210
- self[sym]
211
- end
212
- end
17
+ class ::Thread
18
+ alias inspect_without_name inspect
19
+ def inspect
20
+ show_with_name( inspect_without_name )
213
21
  end
214
-
215
- end
216
-
217
- class Tracer
218
- def initialize( portal, cmd )
219
- @portal = portal
220
- @cmd = cmd
22
+ alias to_s_without_name to_s
23
+ def to_s
24
+ show_with_name( to_s_without_name )
221
25
  end
222
-
223
- def stoppoint( sp )
224
- case @cmd
225
- when :step
226
- sp.breakpoint = @portal.stoppoint.breakpoint
227
- return sp
228
- when :next
229
- case sp.event
230
- when 'line'
231
- if( @portal.stoppoint.classname == sp.classname and
232
- @portal.stoppoint.methodname == sp.methodname )
233
- sp.breakpoint = @portal.stoppoint.breakpoint
234
- return sp
235
- end
236
- when 'return'
237
- if( @portal.stoppoint.classname == sp.classname and
238
- @portal.stoppoint.methodname == sp.methodname )
239
- @cmd = :any_line
240
- end
241
- end
242
- return :continue
243
- when :fin
244
- if( sp.event == 'return' and
245
- @portal.stoppoint.classname == sp.classname and
246
- @portal.stoppoint.methodname == sp.methodname )
247
- sp.breakpoint = @portal.stoppoint.breakpoint
248
- return sp
249
- end
250
- when :any_line
251
- if sp.event == 'line'
252
- sp.breakpoint = @portal.stoppoint.breakpoint
253
- return sp
254
- else
255
- return :continue
256
- end
26
+ def show_with_name( str )
27
+ if( name = self[:thread_name] or
28
+ (::Thread.main == self and name = :main) )
29
+ str.sub( /Thread:0x[[:xdigit:]]+/,
30
+ "Thread:#{object_id} #{name.inspect}" )
257
31
  else
258
- raise "Unknown tracer command: #{@cmd}"
32
+ str
259
33
  end
260
34
  end
35
+ private :show_with_name
261
36
  end
262
37
 
263
- # This will be the list of all public instance methods that start
264
- # with 'rti_'. These methods are callable from a client's
265
- # connection to invoke various helpers remotely (e.g. manipulating
266
- # breakpoints as well as other helpful functions). The first
267
- # argument to these will always be the state. Remaining arguments
268
- # will be whatever strings were obtained from the client.
269
- #
270
- class RTIManager
271
-
272
- # If this character is used as the first non-whitespace value
273
- # in a command, the remaining data will be used to invoke an
274
- # RTIManager method directly (outside the eval scope).
275
- #
276
- CMD_MARKER = ?.
277
-
278
- # Track the list of available commands.
279
- #
280
- RTI_METHODS = []
281
-
282
- # Used to collect data regarding each breakpoint. The state is
283
- # associated with the appropriate client that is waiting for
284
- # this breakpoint to be hit.
285
- #
286
- BreakPoint = Struct.new( :id, :state )
287
-
288
- # Collect all methods that we are willing to expose to a
289
- # caller.
290
- #
291
- def self.method_added( id )
292
- name = id.id2name
293
- RTI_METHODS << id.id2name
294
- end
295
-
296
- # Get a new RTIManager
297
- #
298
- def initialize( breakpoints, tracing_proc, state )
299
- @breakpoints = breakpoints
300
- @tracing_proc = tracing_proc
301
- @state = state
302
- end
303
-
304
- # Get the list of RTI commands available.
305
- #
306
- def list
307
- RTI_METHODS.sort
308
- end
309
-
310
- # Get the state information for this client.
311
- #
312
- def state
313
- @state
314
- end
315
-
316
- # Given a name, return the real class object. Returns the first
317
- # one found unless an explicit "path" is provided via ::
318
- # designators.
319
- #
320
- def name2class( name )
321
- if name.include?( '::' )
322
- ObjectSpace.each_object( Class ) do |c|
323
- return c if name == c.name
324
- end
325
- else
326
- ObjectSpace.each_object( Class ) do |c|
327
- return c if c.name.match( /#{name}$/ )
328
- end
329
- end
330
- return nil
331
- end
332
-
333
- # Very simple helper to assist caller in retrieving a specific
334
- # object. This will return the first one found. Anything requiring
335
- # more complicated decision logic about what object to return
336
- # should use ObjectSpace directly.
337
- #
338
- # +name+:: The object's class or name (uses #name2class for the
339
- # latter).
340
- #
341
- def get_object( name )
342
- if name.kind_of? String
343
- return nil unless c = name2class(name)
344
- else
345
- c = name
346
- end
347
- ObjectSpace.each_object( c ) {|obj| return obj}
348
- return nil
349
- end
350
-
351
- # Stop all other threads except the RuntimeInsepctionThread. Be
352
- # careful. This will stop any new connections as well.
353
- #
354
- def stop_world
355
- Thread.critical = true
356
- end
357
-
358
- # Start all other threads up again.
359
- #
360
- def start_world
361
- Thread.critical = false
362
- end
363
-
364
- # Add a new BreakPoint. Execution will not stop at any BreakPoints
365
- # until rti_bp_start is called. The argument should use one of the
366
- # following forms:
367
- #
368
- # * Class#method
369
- # * Module#method
370
- # * method
371
- # * file:line
372
- #
373
- # In the first three forms, the BreakPoint will stop on the 'call'
374
- # event into that method.
375
- #
376
- def bp_add( key )
377
- if bp = @breakpoints[key]
378
- return "Breakpoint already held as #{bp.id} by #{bp.state.socket}"
379
- end
38
+ # Load in the supporting files.
39
+ require "rti/state.rb"
40
+ require "rti/tracer.rb"
41
+ require "rti/manager.rb"
42
+ require "rti/redirect.rb"
43
+ require "rti/thread.rb"
380
44
 
381
- @state.bp_waiting ||= Queue.new
382
-
383
- @state.breakpoint_id ||= 0
384
- @state.breakpoint_id += 1
385
-
386
- @breakpoints[key] = BreakPoint.new( @state.breakpoint_id, @state )
387
-
388
- ret = "Added breakpoint #{@state.breakpoint_id}"
389
-
390
- return ret
391
- end
392
-
393
- # Delete a breakpoint.
394
- #
395
- def bp_del( id )
396
- id = id.to_i
397
- ret = "Unable to find breakpoint #{id}"
398
- @breakpoints.delete_if do |key, bp|
399
- if bp.id == id and bp.state == @state
400
- ret = "Deleted breakpoint #{id}"
401
- true
402
- end
403
- end
404
- return ret
405
- end
406
-
407
- # List all the breakpoints.
408
- #
409
- def bp_list( show_all=false )
410
- ordered = @breakpoints.sort do |a, b|
411
- a[1].id <=> b[1].id
412
- end
413
- ordered.collect do |key, bp|
414
- next unless show_all or bp.state == @state
415
- if show_all and bp.state != @state
416
- post = " (#{bp.state.socket})"
417
- end
418
- "#{bp.id}: #{key}#{post}"
419
- end.compact
420
- end
421
-
422
- # Start tracing looking for breakpoints to stop at.
423
- #
424
- def bp_start
425
- if @breakpoints.empty?
426
- return "No breakpoints set"
427
- end
428
- set_trace_func( @tracing_proc )
429
- @state.bp_portal = @state.bp_waiting.pop
430
- return nil
431
- end
432
-
433
- # Continue tracing from current position waiting for next
434
- # breakpoint to hit.
435
- #
436
- def bp_continue
437
- unless @state.bp_portal
438
- return "Not stopped at a breakpoint"
439
- end
440
- @state.bp_portal.cmds << :continue
441
- @state.bp_portal = @state.bp_waiting.pop
442
- return nil
443
- end
444
-
445
- def bp_fin
446
- unless @state.bp_portal
447
- return "Not stopped at a breakpoint"
448
- end
449
- @state.bp_portal.cmds << :fin
450
- @state.bp_portal = @state.bp_waiting.pop
451
- return nil
452
- end
453
-
454
- # Break execution at the next line (without going into another
455
- # class).
456
- #
457
- def bp_next
458
- unless @state.bp_portal
459
- return "Not stopped at a breakpoint"
460
- end
461
- @state.bp_portal.cmds << :next
462
- @state.bp_portal = @state.bp_waiting.pop
463
- return nil
464
- end
465
-
466
- # Follow the next instruction--potentially into another class
467
- # and/or method.
468
- #
469
- def bp_step
470
- unless @state.bp_portal
471
- return "Not stopped at a breakpoint"
472
- end
473
- @state.bp_portal.cmds << :step
474
- @state.bp_portal = @state.bp_waiting.pop
475
- return nil
476
- end
477
-
478
- # Stop tracing for breakpoints.
479
- #
480
- def bp_stop
481
- unless @state.bp_portal
482
- return "Not stopped at a breakpoint"
483
- end
484
- @state.bp_portal.cmds << :stop
485
- @state.delete( :bp_portal )
486
- return nil
487
- end
488
-
489
- end
490
-
491
- # Create a new inspection thread.
492
- #
493
- # +host+:: What address to listen on.
494
- #
495
- # +port+:: What port to listen on.
496
- #
497
- # +binding+:: In what context should the evaluations be run?
498
- #
499
- # +prompt_name+:: What name to use in the prompt.
45
+ # This is the set of options managed by RTI. The output_marker is
46
+ # the value used to distinguish data sent to the client from
47
+ # within the command evaluation from the value returned from the
48
+ # evaluation.
500
49
  #
501
- def initialize( host='localhost', port=56789, binding=TOPLEVEL_BINDING,
502
- prompt_name=File.basename($0,'.rb') )
503
-
504
- @server = TCPServer.new( host, port )
505
- @binding = binding
506
-
507
- @@dbg_handler.call "Runtime inspection available at #{host}:#{port}"
508
-
509
- @breakpoints = {}
510
- @tracing_proc = method( :handle_tracing ).to_proc
511
-
512
- @clients = []
513
- @clients_mutex = Mutex.new
514
- @dead_clients = Queue.new
515
-
516
- run_gc_thread
517
-
518
- super do
519
- begin
520
- while sockets = select( [@server] )
521
- sockets.first.each do |socket|
522
- socket = @server.accept
523
- def socket.set_peerstr
524
- unless closed?
525
- peer = peeraddr
526
- @peerstr = "#{peer[3]}:#{peer[1]}"
527
- end
528
- end
529
- def socket.to_s
530
- @peerstr or super
531
- end
532
- def socket.inspect
533
- super.chop + " #{to_s}>"
534
- end
535
-
536
- socket.set_peerstr
537
- @@dbg_handler.call "Connection established with " +
538
- "#{socket}"
539
-
540
- run_client_thread( prompt_name, socket )
541
- end
542
- end
543
- rescue Object => e
544
- @@exc_handler.call( "running inspection thread", e )
545
- end
546
- end
547
- end
548
-
549
- ######################################################################
550
- # Remaining functions just overrides for Thread class and then
551
- # private stuff.
552
-
553
- def inspect # :nodoc:
554
- super.chop + " #{@clients.inspect}>"
555
- end
556
-
557
- def to_s # :nodoc:
558
- inspect
559
- end
560
-
561
- def terminate # :nodoc:
562
- super
563
- cleanup
564
- end
50
+ OPTS = { :output_marker => '=>' }
565
51
 
566
- def kill # :nodoc:
567
- super
568
- cleanup
52
+ if $DEBUG
53
+ OPTS[:dbg_handler] = Proc.new {|msg| puts msg}
54
+ else
55
+ OPTS[:dbg_handler] = Proc.new {}
569
56
  end
570
57
 
571
- def exit # :nodoc:
572
- super
573
- cleanup
58
+ OPTS[:exc_handler] = Proc.new do |msg, exc|
59
+ STDERR.puts( "#{::Thread.current}: Exception while #{msg}",
60
+ exc, exc.backtrace )
574
61
  end
575
62
 
576
- private
577
-
578
- # Establish a thread that cleans up dead or disconnected client
579
- # threads (see #run_client_thread).
63
+ # Optionally enable your own handling of debug output. Default is
64
+ # to either suppress it or send it to $stdout if $DEBUG is enabled
65
+ # (only checked at class load time).
580
66
  #
581
- def run_gc_thread
582
- @gc_thread = Thread.new do
583
- loop do
584
- th = @dead_clients.pop
585
- if th == :quit
586
- break
587
- end
588
-
589
- if th[:cleaned]
590
- next
591
- else
592
- th[:cleaned] = true
593
- end
594
-
595
- @clients_mutex.synchronize do
596
- @clients.delete( th )
597
- end
598
-
599
- @@dbg_handler.call "Cleaning up #{th[:socket]}"
600
-
601
- state = th[:state]
602
- bp.rti.bp_stop if state.bp_portal
603
- @breakpoints.delete_if{|key, bp| bp.state == state}
604
-
605
- begin
606
- th.kill if th.alive?
607
- th.join
608
- rescue Object => e
609
- @@exc_handler.call( "cleaning up thread for #{th[:socket]}",
610
- e )
611
- end
612
-
613
- begin
614
- socket = th[:socket]
615
- unless socket.closed?
616
- socket.close
617
- @@dbg_handler.call "Closed connection from #{socket}"
618
- end
619
- rescue Object => e
620
- @@exc_handler.call( "cleaning up socket for #{th[:socket]}",
621
- e )
622
- end
623
- end
624
- end
67
+ def self.dbg_handler( &block ) # :yields: msg
68
+ OPTS[:dbg_handler] = block
625
69
  end
626
70
 
627
- # A new client socket has been established. Handle the connection
628
- # in a separate thread so we don't block other connections.
71
+ # Optionally enable display of any trace methods that are not seen
72
+ # as break or stop points. This will use the dbg_handler to
73
+ # output, so if that isn't set, nothing will be shown.
629
74
  #
630
- def run_client_thread( prompt_name, socket )
631
- client_thread = Thread.new do
632
- t = Thread.current
633
- t[:socket] = socket
634
- t[:state] = state = State.new
635
- state.rti = RTIManager.new( @breakpoints, @tracing_proc, state )
636
-
637
- loop do
638
- begin
639
- prompt( prompt_name, socket, state )
640
- select( [socket] )
641
- if handle_client( socket, state ) == :dead
642
- break
643
- end
644
- rescue Object => e
645
- @@exc_handler.call( "handling connection from #{socket}:", e )
646
- if socket.closed?
647
- break
648
- else
649
- socket.puts( "Connection handler exception:" )
650
- socket.puts( e )
651
- socket.puts( e.backtrace )
652
- end
653
- end
654
- end
655
-
656
- @dead_clients << t
657
- end
658
-
659
- def client_thread.inspect
660
- super.chop + " #{self[:socket]}>"
661
- end
662
-
663
- @clients_mutex.synchronize do
664
- @clients << client_thread
665
- end
75
+ def self.show_trace=( val )
76
+ OPTS[:show_trace] = val
666
77
  end
667
78
 
668
- # Show the client a nice prompt with data about the state of the session.
79
+ # Optionally enable your own exception handler. Default is to
80
+ # write the exception and backtrace out to $stderr.
669
81
  #
670
- def prompt( prompt_name, socket, state )
671
- if Thread.critical
672
- crit = '-!!'
673
- end
674
-
675
- if state.block_cmd.empty?
676
- socket.puts
677
-
678
- if portal = state.bp_portal
679
- sp = portal.stoppoint
680
- if sp.breakpoint
681
- pre = "Breakpoint #{sp.breakpoint.id}"
682
- else
683
- pre = "Stopped"
684
- end
685
- socket.puts( "#{pre} in #{sp.classname}\#" +
686
- "#{sp.methodname} from #{sp.file}:#{sp.line} " +
687
- "(#{sp.event})" )
688
- end
689
- end
690
-
691
- socket.print( "#{prompt_name}:#{'%03d'%state.cmd_count}:" +
692
- "#{state.block_count}#{crit}> " )
82
+ def self.exc_handler( &block ) # :yields: msg, exc
83
+ OPTS[:exc_handler] = block
693
84
  end
694
85
 
695
- # Used to coordinate both the redirected eval output as well as
696
- # the eval returned value.
86
+ # The placeholder for an RTI thread started via on_signal.
697
87
  #
698
- EvalReturn = Struct.new( :out, :value )
88
+ @@signal_rti = nil
699
89
 
700
- # Get the next command from the client and process it by either
701
- # evaluating it in the default binding context or in the context
702
- # of a breakpoint.
90
+ # Used to have RTI register itself on or off when the given signal
91
+ # is trapped. This way, the RTI thread is only running when needed
92
+ # during trouble-shooting or debugging. The remaining arguments
93
+ # are passed off to the #new method when the RTI thread is
94
+ # instantiated.
703
95
  #
704
- def handle_client( socket, state )
705
- unless cmd = socket.gets
706
- return :dead
707
- end
708
-
709
- if state.block_count > 0
710
- if cmd.match( /^\s*$/ )
711
- cmd = state.block_cmd
712
- state.block_count += 1
713
- state.block_cmd = ''
714
- else
715
- state.block_cmd += cmd
716
- return state.cmd_count += 1
717
- end
718
- end
719
-
720
- if( cmd[0] == RTIManager::CMD_MARKER )
721
- # Handle RTI commands (e.g. bp_add, bp_start...).
722
- rti_cmd, *rti_args = cmd[1..-1].split( /\s+/ )
723
- ret = EvalReturn.new( nil, state.rti.send( rti_cmd, *rti_args ))
724
- end
725
-
726
- unless ret
727
- # Determine to either run the cmd locally or in a thread
728
- # waiting at a breakpoint.
729
- if state.bp_portal
730
- state.bp_portal.cmds << cmd
731
- ret = state.bp_portal.out.pop
732
- else
733
- ret = run_eval( state, cmd )
734
- end
735
- end
736
-
737
- if out = ret.out
738
- if out.kind_of? StringIO
739
- socket.puts out.string unless out.length < 1
740
- else
741
- socket.puts out
742
- end
743
- end
744
-
745
- v = ret.value
746
- if state.use_yaml
747
- v = v.to_yaml
748
- elsif v.kind_of? Exception
749
- v = "#{v}\n" + v.backtrace.join("\n")
750
- else
751
- v = v.inspect
752
- end
753
-
754
- socket.puts "#{OUTPUT_MARKER} #{v}"
755
-
756
- state.cmd_count += 1
757
- end
758
-
759
- # Turned on selectively via #rti_bp_start where we are actively
760
- # trying to find a point to stop and process commands in this
761
- # context.
762
- #
763
- def handle_tracing( event, file, line, methodname, binding, classname )
764
- sp = StopPoint.new( classname, methodname, file, line, event)
765
- tracer = Thread.current[:rti_tracer]
766
-
767
- if tracer
768
- $goober.puts [:tracer, sp].inspect
769
- $goober.flush
770
-
771
- found = tracer.stoppoint( sp )
772
- if found == :continue
773
- found = nil
774
- end
775
- end
776
-
777
- unless found
778
- return unless file and line
779
- if bp = @breakpoints["#{file}:#{line}"]
780
- sp.breakpoint = bp
781
- found = sp
782
- else
783
- # These breakpoints only stop at the initial call into
784
- # this method.
785
- return unless event == 'call'
786
- if bp = @breakpoints["#{classname}##{methodname}"]
787
- sp.breakpoint = bp
788
- found = sp
789
- elsif bp = @breakpoints["#{methodname}"]
790
- sp.breakpoint = bp
791
- found = sp
96
+ def self.on_signal( signal='USR1', *args )
97
+ unless OPTS[:signal_is_set]
98
+ OPTS[:signal_is_set] = true
99
+ Signal.trap( signal ) do
100
+ if @@signal_rti
101
+ @@signal_rti.kill
102
+ @@signal_rti = nil
103
+ OPTS[:dbg_handler].call "Stopped signal-based RTI thread"
792
104
  else
793
- return
105
+ @@signal_rti = Thread.new( *args )
106
+ OPTS[:dbg_handler].call "Started signal-based RTI thread"
794
107
  end
795
108
  end
796
109
  end
797
-
798
- if bp = found.breakpoint
799
- # If we've found a stoppoint, we no longer need the tracer
800
- # as it is just looking for the next stopping point.
801
- Thread.current[:rti_tracer] = nil
802
- portal = Portal.new( found, Queue.new, Queue.new )
803
- bp.state.bp_waiting << portal
804
- loop do
805
- cmd = portal.cmds.pop
806
- case cmd
807
- when :step
808
- Thread.current[:rti_tracer] = Tracer.new( portal, cmd )
809
- break
810
- when :next
811
- Thread.current[:rti_tracer] = Tracer.new( portal, cmd )
812
- break
813
- when :fin
814
- Thread.current[:rti_tracer] = Tracer.new( portal, cmd )
815
- break
816
- when :continue
817
- break
818
- when :stop
819
- set_trace_func( nil )
820
- break
821
- else
822
- out = run_eval( bp.state, cmd, binding )
823
- portal.out << out
824
- end
825
- end
826
- end
827
- rescue Object => e
828
- @@exc_handler.call( "tracing", e )
829
110
  end
830
111
 
831
- # Run a safe evaluation of a client's command. This redirect's any
832
- # output from the command's evaluation. The return will be both
833
- # the output from the evaluation as well as the value of the
834
- # evaluation.
112
+ # Used to manage information found at each stopping point--either
113
+ # hit by a BreakPoint or because of one of the bp_* methods in the
114
+ # RTIManager.
835
115
  #
836
- def run_eval( state, cmd, binding=@binding, safe_level=nil )
837
- safe_level = state.safe_level unless safe_level
838
- ret = EvalReturn.new
839
-
840
- eval_args = ["$SAFE=#{safe_level};rti=Thread.current[:rti_manager];" +
841
- cmd]
842
- if binding
843
- eval_args << binding
844
- end
845
-
846
- if $DEBUG
847
- if binding != @binding
848
- bindstr = " (explicit binding)"
849
- elsif !binding
850
- bindstr = " (without binding)"
851
- end
852
- @@dbg_handler.call "Executing#{bindstr} [#{safe_level}]: #{cmd}"
853
- end
854
-
855
- # Temporarily capture stdout/stderr into string for just the
856
- # evaluation thread.
857
- ret.out = StringIO.new
858
- ThreadRedirect.start
859
-
860
- eval_thread = Thread.new do
861
- t = Thread.current
862
- t[:rti_redirect_io] = ret.out
863
- t[:rti_manager] = state.rti
864
- begin
865
- eval( *eval_args )
866
- rescue Object => e
867
- e
868
- end
869
- end
870
-
871
- begin
872
- timeout( state.eval_timeout ) do
873
- eval_thread.join
874
- end
875
- rescue Object => e
876
- ret.out.puts e, e.backtrace
877
- # The thread could hang around forever if it isn't
878
- # taken care of...
879
- eval_thread.kill if eval_thread
880
- ensure
881
- ThreadRedirect.stop
882
- end
883
-
884
- ret.value = eval_thread.value
885
-
886
- return ret
887
- end
116
+ StopPoint = Struct.new( :classname, :methodname, :file, :line, :event,
117
+ :thread, :state, :breakpoint )
888
118
 
889
- # Close down all our threads and clean stuff up in anticipation of
890
- # the main thread going away.
119
+ # Used as a messaging mechanism once a StopPoint is hit to
120
+ # coordinate commands received from the client in a client thread
121
+ # (see run_client_thread) with the actual evaluation run from
122
+ # within the StopPoint's context (it's binding--see
123
+ # handle_tracing).
891
124
  #
892
- def cleanup
893
- @server.close unless @server.closed?
894
-
895
- until @clients.empty?
896
- client = nil
897
- @clients_mutex.synchronize do
898
- client = @clients.pop
899
- end
900
- @dead_clients << client
901
- end
902
-
903
- # Tell the GC thread to but still give it a chance to finish
904
- # up with all the clients we just gave it.
905
- @dead_clients << :quit
906
-
907
- until @dead_clients.empty?
908
- @gc_thread.run
909
- end
910
-
911
- @gc_thread.kill
912
- end
913
-
914
- end
915
-
916
- if $0 == __FILE__
917
- class Foo
918
- def bar
919
- @a ||= 0
920
- b = @a + 1
921
- sleep 5
922
- puts @a
923
- @a += 1
924
- end
925
- end
926
-
927
- def start_foo
928
- Thread.new do
929
- foo = Foo.new
930
- z = 0
931
- y = 100
932
- loop do
933
- z += 1
934
- foo.bar
935
- y += 2
936
- end
937
- end
938
- end
939
-
940
- $goober = File.open( 'goober', 'w' )
941
- $goober.sync = false
942
- at_exit do
943
- $goober.flush
944
- end
125
+ Portal = Struct.new( :stoppoint, :cmds, :out )
945
126
 
946
- unless ARGV.length > 0
947
- RuntimeInspectionThread.new.join
948
- exit
949
- end
950
127
  end