Skip to content

Commit

Permalink
Showing 11 changed files with 289 additions and 164 deletions.
104 changes: 1 addition & 103 deletions lib/ruby/truffle/truffle/thread.rb
Original file line number Diff line number Diff line change
@@ -22,106 +22,4 @@ class ThreadError < StandardError
Thread.abort_on_exception = true
end

#
# ConditionVariable objects augment class Mutex. Using condition variables,
# it is possible to suspend while in the middle of a critical section until a
# resource becomes available.
#
# Example:
#
# require 'thread'
#
# mutex = Mutex.new
# resource = ConditionVariable.new
#
# a = Thread.new {
# mutex.synchronize {
# # Thread 'a' now needs the resource
# resource.wait(mutex)
# # 'a' can now have the resource
# }
# }
#
# b = Thread.new {
# mutex.synchronize {
# # Thread 'b' has finished using the resource
# resource.signal
# }
# }
#
class ConditionVariable
#
# Creates a new ConditionVariable
#
def initialize
@waiters = {}
@waiters_mutex = Mutex.new
end

#
# Releases the lock held in +mutex+ and waits; reacquires the lock on wakeup.
#
# If +timeout+ is given, this method returns after +timeout+ seconds passed,
# even if no other thread has signaled.
#
def wait(mutex, timeout=nil)
Thread.handle_interrupt(StandardError => :never) do
begin
Thread.handle_interrupt(StandardError => :on_blocking) do
@waiters_mutex.synchronize do
@waiters[Thread.current] = true
end
mutex.sleep timeout
end
ensure
@waiters_mutex.synchronize do
@waiters.delete(Thread.current)
end
end
end
self
end

#
# Wakes up the first thread in line waiting for this lock.
#
def signal
Thread.handle_interrupt(StandardError => :on_blocking) do
begin
t, _ = @waiters_mutex.synchronize { @waiters.shift }
t.run if t
rescue ThreadError
retry # t was already dead?
end
end
self
end

#
# Wakes up all threads waiting for this lock.
#
def broadcast
Thread.handle_interrupt(StandardError => :on_blocking) do
threads = nil
@waiters_mutex.synchronize do
threads = @waiters.keys
@waiters.clear
end
for t in threads
begin
t.run
rescue ThreadError
end
end
end
self
end

# Truffle: define marshal_dump as MRI tests expect it
def marshal_dump
raise TypeError, "can't dump #{self.class}"
end

end

# Truffle: Queue and SizedQueue are defined in Java
# Truffle: ConditionVariable, Queue and SizedQueue are defined in Java
3 changes: 1 addition & 2 deletions test/mri/excludes_truffle/TestConditionVariable.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
exclude :test_condvar_wait_deadlock_2, "timeout"
exclude :test_condvar_wait_and_broadcast, "timeout"
exclude :test_condvar_wait_deadlock, "spawn"
exclude :test_dup, "needs investigation"
exclude :test_dump, "Binding#eval"
exclude :test_dump, "copy of class methods of a class"
exclude :"test_initialized", "needs investigation"
3 changes: 3 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/Layouts.java
Original file line number Diff line number Diff line change
@@ -38,6 +38,8 @@
import org.jruby.truffle.core.method.UnboundMethodLayoutImpl;
import org.jruby.truffle.core.module.ModuleLayout;
import org.jruby.truffle.core.module.ModuleLayoutImpl;
import org.jruby.truffle.core.mutex.ConditionVariableLayout;
import org.jruby.truffle.core.mutex.ConditionVariableLayoutImpl;
import org.jruby.truffle.core.mutex.MutexLayout;
import org.jruby.truffle.core.mutex.MutexLayoutImpl;
import org.jruby.truffle.core.numeric.BignumLayout;
@@ -102,6 +104,7 @@ public abstract class Layouts {
public static final BindingLayout BINDING = BindingLayoutImpl.INSTANCE;
public static final ByteArrayLayout BYTE_ARRAY = ByteArrayLayoutImpl.INSTANCE;
public static final ClassLayout CLASS = ClassLayoutImpl.INSTANCE;
public static final ConditionVariableLayout CONDITION_VARIABLE = ConditionVariableLayoutImpl.INSTANCE;
public static final DirLayout DIR = DirLayoutImpl.INSTANCE;
public static final EncodingConverterLayout ENCODING_CONVERTER = EncodingConverterLayoutImpl.INSTANCE;
public static final EncodingLayout ENCODING = EncodingLayoutImpl.INSTANCE;
4 changes: 4 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/CoreLibrary.java
Original file line number Diff line number Diff line change
@@ -53,6 +53,7 @@
import org.jruby.truffle.core.method.UnboundMethodNodesFactory;
import org.jruby.truffle.core.module.ModuleNodes;
import org.jruby.truffle.core.module.ModuleNodesFactory;
import org.jruby.truffle.core.mutex.ConditionVariableNodesFactory;
import org.jruby.truffle.core.mutex.MutexNodesFactory;
import org.jruby.truffle.core.numeric.BignumNodesFactory;
import org.jruby.truffle.core.numeric.FixnumNodesFactory;
@@ -497,6 +498,8 @@ public CoreLibrary(RubyContext context) {
bindingClass = defineClass("Binding");
bindingFactory = Layouts.BINDING.createBindingShape(bindingClass, bindingClass);
Layouts.CLASS.setInstanceFactoryUnsafe(bindingClass, bindingFactory);
final DynamicObject conditionVariableClass = defineClass("ConditionVariable");
Layouts.CLASS.setInstanceFactoryUnsafe(conditionVariableClass, Layouts.CONDITION_VARIABLE.createConditionVariableShape(conditionVariableClass, conditionVariableClass));
dirClass = defineClass("Dir");
Layouts.CLASS.setInstanceFactoryUnsafe(dirClass, Layouts.DIR.createDirShape(dirClass, dirClass));
encodingClass = defineClass("Encoding");
@@ -708,6 +711,7 @@ public void addCoreMethods() {
coreMethodNodeManager.addCoreMethodNodes(BindingNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(BignumNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ClassNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ConditionVariableNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ExceptionNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(FalseClassNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(FiberNodesFactory.getFactories());
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.core.cast;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.control.RaiseException;

@NodeChild("duration")
public abstract class DurationToMillisecondsNode extends RubyNode {

@Child NumericToFloatNode floatCastNode;

private final ConditionProfile durationLessThanZeroProfile = ConditionProfile.createBinaryProfile();

public abstract long executeDurationToMillis(VirtualFrame frame, Object duration);

@Specialization
public long duration(NotProvided duration) {
return Long.MAX_VALUE;
}

@Specialization
public long duration(int duration) {
return validate(duration * 1000L);
}

@Specialization
public long duration(long duration) {
return validate(duration * 1000);
}

@Specialization
public long duration(double duration) {
return validate((long) (duration * 1000));
}

@Specialization(guards = "isRubiniusUndefined(duration)")
public long duration(DynamicObject duration) {
return duration(NotProvided.INSTANCE);
}

@Specialization(guards = "!isRubiniusUndefined(duration)")
public long duration(VirtualFrame frame, DynamicObject duration) {
if (floatCastNode == null) {
CompilerDirectives.transferToInterpreter();
floatCastNode = insert(NumericToFloatNodeGen.create(getContext(), getSourceSection(), "to_f", null));
}
return duration(floatCastNode.executeDouble(frame, duration));
}

private long validate(long durationInMillis) {
if (durationLessThanZeroProfile.profile(durationInMillis < 0)) {
throw new RaiseException(coreExceptions().argumentErrorTimeItervalPositive(this));
}
return durationInMillis;
}

}
Original file line number Diff line number Diff line change
@@ -54,11 +54,10 @@
import org.jruby.truffle.core.basicobject.BasicObjectNodesFactory;
import org.jruby.truffle.core.binding.BindingNodes;
import org.jruby.truffle.core.cast.BooleanCastWithDefaultNodeGen;
import org.jruby.truffle.core.cast.DurationToMillisecondsNodeGen;
import org.jruby.truffle.core.cast.NameToJavaStringNode;
import org.jruby.truffle.core.cast.NameToJavaStringNodeGen;
import org.jruby.truffle.core.cast.NameToSymbolOrStringNodeGen;
import org.jruby.truffle.core.cast.NumericToFloatNode;
import org.jruby.truffle.core.cast.NumericToFloatNodeGen;
import org.jruby.truffle.core.cast.ToPathNodeGen;
import org.jruby.truffle.core.cast.ToStrNodeGen;
import org.jruby.truffle.core.encoding.EncodingNodes;
@@ -1628,43 +1627,18 @@ public DynamicObject singletonMethods(Object self, boolean includeAncestors) {

}

@NodeChild(value = "duration", type = RubyNode.class)
@CoreMethod(names = "sleep", isModuleFunction = true, optional = 1)
public abstract static class SleepNode extends CoreMethodArrayArgumentsNode {
public abstract static class SleepNode extends CoreMethodNode {

@Child NumericToFloatNode floatCastNode;

@Specialization
public long sleep(NotProvided duration) {
return doSleepMillis(Long.MAX_VALUE);
}

@Specialization
public long sleep(int duration) {
return doSleepMillis(duration * 1000L);
@CreateCast("duration")
public RubyNode coerceDuration(RubyNode duration) {
return DurationToMillisecondsNodeGen.create(duration);
}

@Specialization
public long sleep(long duration) {
return doSleepMillis(duration * 1000);
}

@Specialization
public long sleep(double duration) {
return doSleepMillis((long) (duration * 1000));
}

@Specialization(guards = "isRubiniusUndefined(duration)")
public long sleep(DynamicObject duration) {
return sleep(NotProvided.INSTANCE);
}

@Specialization(guards = "!isRubiniusUndefined(duration)")
public long sleep(VirtualFrame frame, DynamicObject duration) {
if (floatCastNode == null) {
CompilerDirectives.transferToInterpreter();
floatCastNode = insert(NumericToFloatNodeGen.create(getContext(), getSourceSection(), "to_f", null));
}
return sleep(floatCastNode.executeDouble(frame, duration));
return doSleepMillis(duration);
}

@TruffleBoundary
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2015, 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.core.mutex;

import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectFactory;
import com.oracle.truffle.api.object.dsl.Layout;
import org.jruby.truffle.core.basicobject.BasicObjectLayout;

@Layout
public interface ConditionVariableLayout extends BasicObjectLayout {

DynamicObjectFactory createConditionVariableShape(
DynamicObject logicalClass,
DynamicObject metaClass);

DynamicObject createConditionVariable(
DynamicObjectFactory factory,
Object condition);

boolean isConditionVariable(DynamicObject object);

Object getCondition(DynamicObject object);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2015, 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.core.mutex;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.CreateCast;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import java.util.concurrent.locks.ReentrantLock;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.builtins.CoreClass;
import org.jruby.truffle.builtins.CoreMethod;
import org.jruby.truffle.builtins.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.builtins.CoreMethodNode;
import org.jruby.truffle.builtins.UnaryCoreMethodNode;
import org.jruby.truffle.core.cast.DurationToMillisecondsNodeGen;
import org.jruby.truffle.core.thread.ThreadManager.BlockingAction;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.objects.AllocateObjectNode;
import org.jruby.truffle.language.objects.AllocateObjectNodeGen;

@CoreClass("ConditionVariable")
public abstract class ConditionVariableNodes {

private static Object getCondition(DynamicObject conditionVariable) {
return Layouts.CONDITION_VARIABLE.getCondition(conditionVariable);
}

@CoreMethod(names = "allocate", constructor = true)
public abstract static class AllocateNode extends CoreMethodArrayArgumentsNode {

@Child private AllocateObjectNode allocateNode;

public AllocateNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
allocateNode = AllocateObjectNodeGen.create(context, sourceSection, null, null);
}

@Specialization
public DynamicObject allocate(DynamicObject rubyClass) {
return allocateNode.allocate(rubyClass, new Object());
}

}

@NodeChildren({
@NodeChild(value = "conditionVariable", type = RubyNode.class),
@NodeChild(value = "mutex", type = RubyNode.class),
@NodeChild(value = "duration", type = RubyNode.class)
})
@CoreMethod(names = "wait", required = 1, optional = 1)
public abstract static class WaitNode extends CoreMethodNode {

@CreateCast("duration")
public RubyNode coerceDuration(RubyNode duration) {
return DurationToMillisecondsNodeGen.create(duration);
}

@Specialization(guards = "isRubyMutex(mutex)")
public DynamicObject wait(DynamicObject conditionVariable, DynamicObject mutex, long timeoutInMillis) {
final ReentrantLock lock = Layouts.MUTEX.getLock(mutex);
final DynamicObject thread = getContext().getThreadManager().getCurrentThread();
final Object condition = getCondition(conditionVariable);

doWait(timeoutInMillis, lock, thread, condition);

return conditionVariable;
}

@TruffleBoundary
private void doWait(final long durationInMillis, ReentrantLock lock, DynamicObject thread, Object condition) {
final long start = System.currentTimeMillis();

// First lock the condition so we only release the Mutex when we are in wait() and ready to be notified
synchronized (condition) {
MutexOperations.unlock(lock, thread, this);
try {
getContext().getThreadManager().runUntilResult(this, new BlockingAction<Boolean>() {
@Override
public Boolean block() throws InterruptedException {
long now = System.currentTimeMillis();
long slept = now - start;

if (slept >= durationInMillis) {
return SUCCESS;
}

condition.wait(durationInMillis - slept);
return SUCCESS;
}
});
} finally {
MutexOperations.lock(lock, thread, this);
}
}
}
}

@CoreMethod(names = "signal")
public abstract static class SignalNode extends UnaryCoreMethodNode {

@Specialization
public DynamicObject doSignal(DynamicObject conditionVariable) {
final Object condition = getCondition(conditionVariable);
synchronized (condition) {
condition.notify();
}
return conditionVariable;
}

}

@CoreMethod(names = "broadcast")
public abstract static class BroadcastNode extends UnaryCoreMethodNode {

@Specialization
public DynamicObject doBroadcast(DynamicObject conditionVariable) {
final Object condition = getCondition(conditionVariable);
synchronized (condition) {
condition.notifyAll();
}
return conditionVariable;
}

}

@CoreMethod(names = "marshal_dump")
public abstract static class MarshalDumpNode extends CoreMethodArrayArgumentsNode {

@Specialization
public Object marshal_dump(DynamicObject self) {
throw new RaiseException(coreExceptions().typeErrorCantDump(self, this));
}

}

}
41 changes: 16 additions & 25 deletions truffle/src/main/java/org/jruby/truffle/core/mutex/MutexNodes.java
Original file line number Diff line number Diff line change
@@ -11,6 +11,9 @@

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.CreateCast;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
@@ -20,13 +23,13 @@
import org.jruby.truffle.builtins.CoreClass;
import org.jruby.truffle.builtins.CoreMethod;
import org.jruby.truffle.builtins.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.builtins.CoreMethodNode;
import org.jruby.truffle.builtins.UnaryCoreMethodNode;
import org.jruby.truffle.core.cast.DurationToMillisecondsNodeGen;
import org.jruby.truffle.core.kernel.KernelNodes;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.objects.AllocateObjectNode;
import org.jruby.truffle.language.objects.AllocateObjectNodeGen;

import java.util.concurrent.locks.ReentrantLock;

@CoreClass("Mutex")
@@ -124,36 +127,24 @@ public DynamicObject unlock(DynamicObject mutex) {

}

@NodeChildren({
@NodeChild(value = "mutex", type = RubyNode.class),
@NodeChild(value = "duration", type = RubyNode.class)
})
@CoreMethod(names = "sleep", optional = 1)
public abstract static class SleepNode extends CoreMethodArrayArgumentsNode {

private final ConditionProfile durationLessThanZeroProfile = ConditionProfile.createBinaryProfile();

@Specialization
public long sleep(DynamicObject mutex, NotProvided duration) {
return doSleepMillis(mutex, Long.MAX_VALUE);
}
public abstract static class SleepNode extends CoreMethodNode {

@Specialization(guards = "isNil(duration)")
public long sleep(DynamicObject mutex, DynamicObject duration) {
return sleep(mutex, NotProvided.INSTANCE);
@CreateCast("duration")
public RubyNode coerceDuration(RubyNode duration) {
return DurationToMillisecondsNodeGen.create(duration);
}

@Specialization
public long sleep(DynamicObject mutex, long duration) {
return doSleepMillis(mutex, duration * 1000);
}

@Specialization
public long sleep(DynamicObject mutex, double duration) {
return doSleepMillis(mutex, (long) (duration * 1000.0));
public long sleep(DynamicObject mutex, long millis) {
return doSleepMillis(mutex, millis);
}

public long doSleepMillis(DynamicObject mutex, long durationInMillis) {
if (durationLessThanZeroProfile.profile(durationInMillis < 0)) {
throw new RaiseException(coreExceptions().argumentErrorTimeItervalPositive(this));
}

final ReentrantLock lock = Layouts.MUTEX.getLock(mutex);
final DynamicObject thread = getContext().getThreadManager().getCurrentThread();

Original file line number Diff line number Diff line change
@@ -214,7 +214,6 @@ public DynamicObject clear(DynamicObject self) {
public abstract static class MarshalDumpNode extends CoreMethodArrayArgumentsNode {

@Specialization
@TruffleBoundary
public Object marshal_dump(DynamicObject self) {
throw new RaiseException(coreExceptions().typeErrorCantDump(self, this));
}
Original file line number Diff line number Diff line change
@@ -133,6 +133,10 @@ public static boolean isRubyModule(DynamicObject value) {
return Layouts.MODULE.isModule(value);
}

public static boolean isRubyMutex(DynamicObject value) {
return Layouts.MUTEX.isMutex(value);
}

public static boolean isRubyRegexp(Object value) {
return Layouts.REGEXP.isRegexp(value);
}

1 comment on commit 1dc8945

@pitr-ch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.