Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: opal/opal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 75f3abcaf9ad
Choose a base ref
...
head repository: opal/opal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 39db72c6731f
Choose a head ref
  • 2 commits
  • 4 files changed
  • 2 contributors

Commits on Apr 28, 2014

  1. Thread additions, Mutex and Queue

    Thread.list reports itself as the current thread, and Thread#new
    explicitly disallows creation of threads.
    
    Thread now supports the fiber- and thread-local variable storage
    interface
    
    Mutex and Queue are implemented, and behave as expected, given the
    single-threaded environment.
    mieko committed Apr 28, 2014
    Copy the full SHA
    c4c3a1c View commit details

Commits on Sep 5, 2014

  1. Merge pull request #533 from mieko/thread_shim

    Add compatibility Thread, Queue and Mutex implementation.
    elia committed Sep 5, 2014
    Copy the full SHA
    39db72c View commit details
Showing with 279 additions and 7 deletions.
  1. +40 −0 spec/opal/stdlib/thread/mutex_spec.rb
  2. +32 −0 spec/opal/stdlib/thread/thread_queue_spec.rb
  3. +60 −0 spec/opal/stdlib/thread/thread_spec.rb
  4. +147 −7 stdlib/thread.rb
40 changes: 40 additions & 0 deletions spec/opal/stdlib/thread/mutex_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require 'thread'

describe Mutex do
before do
@mutex = Mutex.new
end

it "cannot be locked twice" do
@mutex.lock
lambda do
@mutex.lock
end.should raise_error(ThreadError)
end

it "reports locked? status" do
@mutex.locked?.should be_false
@mutex.lock
@mutex.locked?.should be_true
end

it "reports locked? status with try_lock" do
@mutex.try_lock.should be_true
@mutex.locked?.should be_true
@mutex.try_lock.should be_false
end

it "is locked and unlocked by synchronize" do
@mutex.synchronize do
@mutex.locked?.should be_true
end
@mutex.locked?.should be_false
end

it "will not be locked by synchronize if already locked" do
@mutex.lock
lambda do
@mutex.synchronize {}
end.should raise_error(ThreadError)
end
end
32 changes: 32 additions & 0 deletions spec/opal/stdlib/thread/thread_queue_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'thread'

describe Thread::Queue do
before do
@queue = Thread::Queue.new
end

it "is aliased as ::Queue" do
::Thread::Queue.should == ::Queue
end

it "will not allow deadlock" do
lambda do
@queue.pop
end.should raise_error(ThreadError)
end

it "pops in FIFO order" do
@queue.push(1)
@queue.push(2)

@queue.pop.should == 1
@queue.pop.should == 2
end

it "can be cleared by clear" do
@queue.push(1)
@queue.clear
@queue.size.should == 0
@queue.empty?.should be_true
end
end
60 changes: 60 additions & 0 deletions spec/opal/stdlib/thread/thread_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'thread'

# Our implementation of Thread only supports faux thread-local variables.
# Since we can't actually create a thread, nothing in rubyspec will run.
describe Thread do
it "returns a value for current" do
Thread.current.should_not be_nil
end

it "only has current in list" do
Thread.list.should == [Thread.current]
end

it "does not allow creation of new threads" do
lambda do
Thread.new {}
end.should raise_error(NotImplementedError)
end

describe "local storage" do
before do
@current = Thread.current
@current.send(:core_initialize!)
end

it "stores fiber-local variables" do
@current[:a] = 'hello'
@current[:a].should == 'hello'
end

it "returns fiber-local keys that are assigned" do
@current[:a] = 'hello'
@current.key?(:a).should be_true
@current.keys.should === ['a']
end

it "considers fiber-local keys, as symbols or strings equal" do
@current[:a] = 1
@current['a'] = 2
@current.keys.size.should == 1
@current[:a].should == 2
end

it "implements thread-local variables" do
@current.thread_variable_set('a', 1)
@current.thread_variable_get('a').should == 1
@current.thread_variables.should == ['a']
end

it "distinguishes between fiber-local and thread-local variables" do
@current[:a] = 1
@current.thread_variables.should == []

@current.thread_variable_set(:a, 2)

@current[:a].should == 1
@current.thread_variable_get(:a).should == 2
end
end
end
154 changes: 147 additions & 7 deletions stdlib/thread.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,160 @@
# This shim implementation of Thread is meant to only appease code that tries
# to be safe in the presence of threads, but does not actually utilize them,
# e.g., uses thread- or fiber-local variables.

class ThreadError < StandardError
end

class Thread
def self.current
@current ||= self.new
unless @current
@current = allocate
@current.core_initialize!
end

@current
end

def initialize
@vars = {}
def self.list
[current]
end

# Do not allow creation of new instances.
def initialize(*args)
fail NotImplementedError, "Thread creation not available"
end

# fiber-local attribute access.
def [](key)
@vars[key]
@fiber_locals[coerce_key_name(key)]
end

def []=(key, value)
@fiber_locals[coerce_key_name(key)] = value
end

def key?(key)
@fiber_locals.key?(coerce_key_name(key))
end

def keys
@fiber_locals.keys
end

# thread-local attribute access.
def thread_variable_get(key)
@thread_locals[coerce_key_name(key)]
end

def thread_variable_set(key, value)
@thread_locals[coerce_key_name(key)] = value
end

def thread_variable?(key)
@thread_locals.key?(coerce_key_name(key))
end

def thread_variables
@thread_locals.keys
end

private
def core_initialize!
@thread_locals = {}
@fiber_locals = {}
end

def coerce_key_name(key)
Opal.coerce_to!(key, String, :to_s)
end

def []=(key, val)
@vars[key] = val
public
class Queue
def initialize
clear
end

def clear
@storage = []
end

def empty?
@storage.empty?
end

def size
@storage.size
end

alias length size

def pop(non_block = false)
if empty?
fail ThreadError, "Queue empty" if non_block
fail ThreadError, "Deadlock"
end

@storage.shift
end

alias shift pop
alias deq pop

def push(value)
@storage.push(value)
end

alias << push
alias enq push
end

end

class Queue
Queue = Thread::Queue

class Mutex
def initialize
# We still keep the @locked state so any logic based on try_lock while
# held yields reasonable results.
@locked = false
end

def lock
fail ThreadError, "Deadlock" if @locked
@locked = true
self
end

def locked?
@locked
end

def owned?
# Being the only "thread", we implicitly own any locked mutex.
@locked
end

def try_lock
if locked?
false
else
lock
true
end
end

def unlock
fail ThreadError, "Mutex not locked" unless @locked
@locked = false
self
end

def synchronize
lock
begin
yield
ensure
unlock
end
end
end