rubinius/rubinius

View on GitHub
core/thread.rb

Summary

Maintainability
D
1 day
Test Coverage
#--
# Be very careful about calling raise in here! Thread has its own
# raise which, if you're calling raise, you probably don't want. Use
# Kernel.raise to call the proper raise.
#++
class Thread
  attr_reader :recursive_objects
  attr_reader :pid
  attr_reader :exception
  attr_reader :stack_size
  attr_reader :source

  def self.new(*args, **kw, &block)
    stack_size = Rubinius::Type.try_convert kw[:stack_size], Fixnum, :to_int

    thr = Rubinius.invoke_primitive :thread_s_new, args, stack_size, block, self

    Rubinius::VariableScope.of_sender.locked!

    unless thr.send :initialized?
      raise ThreadError, "Thread#initialize not called"
    end

    return thr
  end

  def self.start(*args, **kw, &block)
    raise ArgumentError.new("no block passed to Thread.start") unless block

    stack_size = Rubinius::Type.try_convert kw[:stack_size], Fixnum, :to_int

    Rubinius.invoke_primitive :thread_s_start, args, stack_size, block, self
  end

  class << self
    alias_method :fork, :start
  end

  def initialize(*args, &block)
    unless block
      Kernel.raise ThreadError, "no block passed to Thread#initialize"
    end

    if @initialized
      Kernel.raise ThreadError, "already initialized thread"
    end

    @args = args
    @block = block
    @initialized = true

    Thread.current.group.add self
  end

  private :initialize

  alias_method :__thread_initialize__, :initialize

  def initialized?
    @initialized
  end

  private :initialized?

  def self.current
    Rubinius.primitive :thread_current
    Kernel.raise PrimitiveFailure, "Thread.current primitive failed"
  end

  def self.allocate
    raise TypeError, "allocator undefined for Thread"
  end

  def self.pass
    Rubinius.primitive :thread_pass
    Kernel.raise PrimitiveFailure, "Thread.pass primitive failed"
  end

  def self.list
    Rubinius.primitive :thread_list
    Kernel.raise PrimitiveFailure, "Thread.list primitive failed"
  end

  def self.count
    Rubinius.primitive :thread_count
    Kernel.raise PrimitiveFailure, "Thread.count primitive failed"
  end

  def self.stop
    sleep
    nil
  end

  def name
    Rubinius.primitive :thread_name
    Kernel.raise PrimitiveFailure, "Thread#name primitive failed"
  end

  def name=(name)
    return unless name

    thread_name = StringValue(name)
    unless thread_name.ascii_only?
      Kernel.raise ArgumentError, "name must be ASCII only"
    end

    Rubinius.invoke_primitive :thread_set_name, self, thread_name

    name
  end

  def fiber_list
    Rubinius.primitive :thread_fiber_list
    Kernel.raise PrimitiveFailure, "Thread.fiber_list primitive failed"
  end

  def wakeup
    Rubinius.primitive :thread_wakeup
    Kernel.raise PrimitiveFailure, "Thread#wakeup primitive failed"
  end

  def priority
    Rubinius.primitive :thread_get_priority
    Kernel.raise ThreadError, "Thread#priority primitive failed"
  end

  def priority=(val)
    Rubinius.primitive :thread_set_priority
    Kernel.raise TypeError, "priority must be a Fixnum" unless val.kind_of? Fixnum
    Kernel.raise ThreadError, "Thread#priority= primitive failed"
  end

  def __context__
    Rubinius.primitive :thread_context
    Kernel.raise PrimitiveFailure, "Thread#__context__ primitive failed"
  end

  def mri_backtrace
    Rubinius.primitive :thread_mri_backtrace
    Kernel.raise PrimitiveFailure, "Thread#mri_backtrace primitive failed"
  end

  @abort_on_exception = false

  def self.abort_on_exception
    @abort_on_exception
  end

  def self.abort_on_exception=(val)
    @abort_on_exception = val
  end

  # It's also an instance method...
  def abort_on_exception=(val)
    @abort_on_exception = val
  end

  def abort_on_exception
    @abort_on_exception || false
  end

  def inspect
    str = "#<#{self.class}:0x#{object_id.to_s(16)} id=#{@thread_id} pid=#{@pid}"
    str << " source=#{@source}" if @source
    str << " status=#{status || "dead"}"
    str << ">"
  end

  alias_method :to_s, :inspect

  def alive?
    Rubinius::Mirror.reflect(self).alive?
  end

  def stop?
    Rubinius::Mirror.reflect(self).stop?
  end

  def status
    Rubinius::Mirror.reflect(self).status
  end

  def join(timeout=undefined)
    if undefined.equal? timeout or nil.equal? timeout
      timeout = nil
    else
      timeout = Rubinius::Type.coerce_to_float timeout
    end

    value = Rubinius.invoke_primitive :thread_join, self, timeout

    if @exception
      Kernel.raise @exception
    else
      value
    end
  end

  def group
    @group
  end

  def raise(exc=undefined, msg=nil, trace=nil)
    return self unless alive?

    if exc.respond_to? :exception
      exc = exc.exception msg
      Kernel.raise TypeError, 'exception class/object expected' unless Exception === exc
      exc.set_backtrace trace if trace
    elsif undefined.equal? exc
      exc = RuntimeError.exception nil
    elsif exc.kind_of? String
      exc = RuntimeError.exception exc
    else
      Kernel.raise TypeError, 'exception class/object expected'
    end

    if $DEBUG
      STDERR.puts "Exception: #{exc.message} (#{exc.class})"
    end

    if self == Thread.current
      Kernel.raise exc
    else
      Rubinius.invoke_primitive :thread_raise, self, exc
    end
  end

  def thread_variable_get(key)
    key = Rubinius::Type.coerce_to_symbol key
    Rubinius.invoke_primitive :thread_variable_get, self, key
  end

  def thread_variable_set(key, value)
    key = Rubinius::Type.coerce_to_symbol key
    Rubinius.invoke_primitive :thread_variable_set, self, key, value
  end

  def thread_variable?(key)
    key = Rubinius::Type.coerce_to_symbol key
    Rubinius.invoke_primitive :thread_variable_key_p, self, key
  end

  def thread_variables
    Rubinius.primitive :thread_variables
    raise PrimitiveFailure, "Thread#thread_variables primitive failed"
  end

  def [](key)
    key = Rubinius::Type.coerce_to_symbol key
    Rubinius.invoke_primitive :thread_fiber_variable_get, self, key
  end

  def []=(key, value)
    key = Rubinius::Type.coerce_to_symbol key
    Rubinius.invoke_primitive :thread_fiber_variable_set, self, key, value
  end

  def key?(key)
    key = Rubinius::Type.coerce_to_symbol key
    Rubinius.invoke_primitive :thread_fiber_variable_key_p, self, key
  end

  def keys
    Rubinius.primitive :thread_fiber_variables
    raise PrimitiveFailure, "Thread#keys primitive failed"
  end

  def self.main
    @main_thread
  end

  def self.initialize_main_thread(thread)
    @main_thread = thread
  end

  def self.exit
    Thread.current.kill
  end

  def self.kill(thread)
    thread.kill
  end

  alias_method :run, :wakeup

  def kill
    Rubinius.invoke_primitive :thread_kill, self
  end

  alias_method :exit, :kill
  alias_method :terminate, :kill

  def value
    join
    @value
  end

  def self.exclusive
    MUTEX_FOR_THREAD_EXCLUSIVE.synchronize { yield }
  end

  # Implementation note: ideally, the recursive_objects
  # lookup table would be different per method call.
  # Currently it doesn't cause problems, but if ever
  # a method :foo calls a method :bar which could
  # recurse back to :foo, it could require making
  # the tables independant.

  def self.recursion_guard(obj)
    id = obj.object_id
    objects = current.recursive_objects
    objects[id] = true

    begin
      yield
    ensure
      objects.delete id
    end
  end

  def self.guarding?(obj)
    current.recursive_objects[obj.object_id]
  end

  # detect_recursion will return if there's a recursion
  # on obj (or the pair obj+paired_obj).
  # If there is one, it returns true.
  # Otherwise, it will yield once and return false.

  def self.detect_recursion(obj, paired_obj=undefined)
    id = obj.object_id
    pair_id = paired_obj.object_id
    objects = current.recursive_objects

    case objects[id]

      # Default case, we haven't seen +obj+ yet, so we add it and run the block.
    when nil
      objects[id] = pair_id
      begin
        yield
      ensure
        objects.delete id
      end

      # We've seen +obj+ before and it's got multiple paired objects associated
      # with it, so check the pair and yield if there is no recursion.
    when Rubinius::LookupTable
      return true if objects[id][pair_id]
      objects[id][pair_id] = true

      begin
        yield
      ensure
        objects[id].delete pair_id
      end

      # We've seen +obj+ with one paired object, so check the stored one for
      # recursion.
      #
      # This promotes the value to a LookupTable since there is another new paired
      # object.
    else
      previous = objects[id]
      return true if previous == pair_id

      objects[id] = Rubinius::LookupTable.new(previous => true, pair_id => true)

      begin
        yield
      ensure
        objects[id] = previous
      end
    end

    false
  end

  # Similar to detect_recursion, but will short circuit all inner recursion
  # levels (using a throw)

  class InnerRecursionDetected < Exception; end

  def self.detect_outermost_recursion(obj, paired_obj=undefined, &block)
    rec = current.recursive_objects

    if rec[:__detect_outermost_recursion__]
      if detect_recursion(obj, paired_obj, &block)
        raise InnerRecursionDetected
      end
      false
    else
      begin
        rec[:__detect_outermost_recursion__] = true

        begin
          detect_recursion(obj, paired_obj, &block)
        rescue InnerRecursionDetected
          return true
        end

        return nil
      ensure
        rec.delete :__detect_outermost_recursion__
      end
    end
  end

  def randomizer
    @randomizer ||= Rubinius::Randomizer.new
  end

  def backtrace
    mri_backtrace.map do |tup|
      code = tup[0]
      line = tup[1]
      is_block = tup[2]
      name = tup[3]

      "#{code.active_path}:#{line}:in `#{name}'"
    end
  end

  # This is a class in MRI, although you can't initialize it.
  class Backtrace
    # Stores location of a single call frame, available since Ruby 2.0.
    class Location
      attr_reader :label
      attr_reader :path
      attr_reader :absolute_path
      attr_reader :lineno

      def initialize(label, absolute_path, path, lineno)
        @label         = label
        @absolute_path = absolute_path
        @path          = path
        @lineno        = lineno
      end

      private :initialize

      alias_method :base_label, :label

      def to_s
        "#{absolute_path}:#{lineno}:in `#{label}'"
      end

      def inspect
        to_s
      end
    end
  end
end