mirror of
https://github.com/mistergibson/gxg-web-client.git
synced 2026-08-15 11:34:29 -07:00
454 lines
10 KiB
Ruby
454 lines
10 KiB
Ruby
#--
|
|
# 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
|