class ThreadWeaver::ControllableThread

Attributes

last_trace_point_summary[R]

Public Class Methods

new(context, name:, &blk) click to toggle source
Calls superclass method
# File lib/thread_weaver/controllable_thread.rb, line 15
def initialize(context, name:, &blk)
  @waiting = T.let(false, T::Boolean)
  @execution_counter = T.let(-1, Integer)
  @last_trace_point_summary = T.let("<no traces detected>", String)
  @line_counts_by_class = T.let({}, T::Hash[Module, Integer])
  @current_instruction = T.let(PauseAtThreadStart.new, ThreadInstruction)

  self.name = name
  self.report_on_exception = false

  super do
    tracer = TracePoint.new(:line, :call, :return, :b_call, :b_return, :thread_begin, :thread_end, :c_call, :c_return) { |tp|
      current_thread = Thread.current
      if current_thread == self
        current_thread.handle_trace_point(tp)
      end
    }
    handle_thread_start
    tracer.enable
    blk.call(context)
    handle_thread_end
  ensure
    tracer&.disable
  end

  wait_until_next_instruction_complete
end

Public Instance Methods

handle_trace_point(tp) click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 85
def handle_trace_point(tp)
  event = T.let(tp.event, Symbol)
  klass = T.let(tp.defined_class, T.nilable(Module))
  path = T.let(tp.path, T.nilable(String))
  line = T.let(tp.lineno, T.nilable(Integer))
  method_name = T.let(tp.method_id, T.nilable(Symbol))

  @last_trace_point_summary = "#{event} #{klass}##{method_name} #{path}#L#{line}"

  if klass
    current_count = @line_counts_by_class.fetch(klass, 0)
    @line_counts_by_class[klass] = (current_count + 1)
  end

  case @current_instruction
  when PauseAtThreadStart
    if event == :thread_begin
      wait_until_released
    end
  when ContinueToThreadEnd
    # do nothing
  when PauseWhenLineCount
    current_count = @current_instruction.target_classes.map { |klass| @line_counts_by_class.fetch(klass, 0) }.sum
    required_count = @current_instruction.count
    if required_count == current_count
      wait_until_released
    end
  when PauseAtMethodCall
    if @current_instruction.klass == klass && @current_instruction.method_name == method_name
      wait_until_released
    end
  when PauseAtMethodReturn
    if @current_instruction.klass == klass && @current_instruction.method_name == method_name
      wait_until_released
    end
  when PauseAtSourceLine
    if path&.end_with?(@current_instruction.path_suffix) && @current_instruction.line == line
      wait_until_released
    end
  else
    T.absurd(@current_instruction)
  end
end
join() click to toggle source
Calls superclass method
# File lib/thread_weaver/controllable_thread.rb, line 130
def join
  while alive?
    release
    do_nothing
  end
  super()
end
next() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 58
def next
  assert_self_is_not_current_thread

  case @current_instruction
  when PauseWhenLineCount, PauseAtSourceLine
    set_next_instruction(
      @current_instruction.next
    )
  else
    raise "Next is only supported when paused on a #{PauseWhenLineCount.name} or a #{PauseAtSourceLine} instruction "
  end
end
release() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 51
def release
  assert_self_is_not_current_thread

  @waiting = false
end
set_and_wait_for_next_instruction(instruction) click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 79
def set_and_wait_for_next_instruction(instruction)
  set_next_instruction(instruction)
  wait_until_next_instruction_complete
end
set_next_instruction(instruction) click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 72
def set_next_instruction(instruction)
  assert_self_is_not_current_thread
  @current_instruction = instruction
  release
end
wait_until_next_instruction_complete() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 44
def wait_until_next_instruction_complete
  assert_self_is_not_current_thread

  do_nothing while alive? && !@waiting
end

Private Instance Methods

assert_self_is_current_thread() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 168
def assert_self_is_current_thread
  raise "illegal call from thread other than self" unless Thread.current == self
end
assert_self_is_not_current_thread() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 173
def assert_self_is_not_current_thread
  raise "illegal call from self" unless Thread.current != self
end
do_nothing() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 163
def do_nothing
  Thread.pass
end
handle_thread_end() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 149
def handle_thread_end
  assert_self_is_current_thread
  unless @current_instruction.is_a?(ContinueToThreadEnd)
    raise ThreadCompletedEarlyError.new("Thread #{name} completed while attempting to match instruction #{@current_instruction}")
  end
end
handle_thread_start() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 141
def handle_thread_start
  assert_self_is_current_thread
  if @current_instruction.is_a?(PauseAtThreadStart)
    wait_until_released
  end
end
wait_until_released() click to toggle source
# File lib/thread_weaver/controllable_thread.rb, line 157
def wait_until_released
  @waiting = true
  do_nothing while @waiting
end