Skip to content

Commit

Permalink
Use nanosecond precision for Kernel#sleep and Mutex#sleep
Browse files Browse the repository at this point in the history
* Fixes #2716
  • Loading branch information
eregon committed Sep 2, 2022
1 parent 95f5f97 commit 15c9930
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Bug fixes:
* Fix `File::Stat`'s `#executable?` and `#executable_real?` predicates that unconditionally returned `true` for a superuser (#2690, @andrykonchin).
* The `strip` option `--keep-section=.llvmbc` is not supported on macOS (#2697, @eregon).
* Disallow the marshaling of polyglot exceptions since we can't properly reconstruct them (@nirvdrum).
* Fix `Kernel#sleep` and `Mutex#sleep` for durations smaller than 1 millisecond (#2716, @eregon).

Compatibility:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.truffleruby.language.dispatch.DispatchNode;

import java.util.concurrent.TimeUnit;

@NodeChild(value = "duration", type = RubyBaseNodeWithExecute.class)
public abstract class DurationToMillisecondsNode extends RubyBaseNodeWithExecute {
public abstract class DurationToNanoSecondsNode extends RubyBaseNodeWithExecute {

private final ConditionProfile durationLessThanZeroProfile = ConditionProfile.create();
private final boolean acceptsNil;

public DurationToMillisecondsNode(boolean acceptsNil) {
public DurationToNanoSecondsNode(boolean acceptsNil) {
this.acceptsNil = acceptsNil;
}

Expand All @@ -36,19 +38,14 @@ protected long noDuration(NotProvided duration) {
return Long.MAX_VALUE;
}

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

@Specialization
protected long duration(long duration) {
return validate(duration * 1000);
return validate(TimeUnit.SECONDS.toNanos(duration));
}

@Specialization
protected long duration(double duration) {
return validate((long) (duration * 1000));
return validate((long) (duration * 1e9));
}

@Specialization
Expand All @@ -64,20 +61,20 @@ protected long durationNil(Nil duration) {

@Specialization
protected Object duration(RubyDynamicObject duration,
@Cached DispatchNode durationToMilliseconds,
@Cached DispatchNode durationToNanoSeconds,
@Cached ToLongNode toLongNode) {
final Object milliseconds = durationToMilliseconds.call(
final Object nanoseconds = durationToNanoSeconds.call(
coreLibrary().truffleKernelOperationsModule,
"convert_duration_to_milliseconds",
"convert_duration_to_nanoseconds",
duration);
return validate(toLongNode.execute(milliseconds));
return validate(toLongNode.execute(nanoseconds));
}

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

}
19 changes: 9 additions & 10 deletions src/main/java/org/truffleruby/core/kernel/KernelNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
import org.truffleruby.core.cast.BooleanCastNode;
import org.truffleruby.core.cast.BooleanCastNodeGen;
import org.truffleruby.core.cast.BooleanCastWithDefaultNode;
import org.truffleruby.core.cast.DurationToMillisecondsNodeGen;
import org.truffleruby.core.cast.DurationToNanoSecondsNodeGen;
import org.truffleruby.core.cast.NameToJavaStringNode;
import org.truffleruby.core.cast.ToIntNode;
import org.truffleruby.core.cast.ToStrNode;
Expand Down Expand Up @@ -1608,13 +1608,13 @@ public abstract static class SleepNode extends CoreMethodNode {

@CreateCast("duration")
protected RubyBaseNodeWithExecute coerceDuration(RubyBaseNodeWithExecute duration) {
return DurationToMillisecondsNodeGen.create(false, duration);
return DurationToNanoSecondsNodeGen.create(false, duration);
}

@Specialization
protected long sleep(long durationInMillis,
protected long sleep(long durationInNanos,
@Cached BranchProfile errorProfile) {
if (durationInMillis < 0) {
if (durationInNanos < 0) {
errorProfile.enter();
throw new RaiseException(
getContext(),
Expand All @@ -1627,27 +1627,26 @@ protected long sleep(long durationInMillis,
// it should only be considered if we are inside the sleep when Thread#{run,wakeup} is called.
thread.wakeUp.set(false);

return sleepFor(getContext(), thread, durationInMillis, this);
return sleepFor(getContext(), thread, durationInNanos, this);
}

@TruffleBoundary
public static long sleepFor(RubyContext context, RubyThread thread, long durationInMillis,
public static long sleepFor(RubyContext context, RubyThread thread, long durationInNanos,
Node currentNode) {
assert durationInMillis >= 0;
assert durationInNanos >= 0;

// We want a monotonic clock to measure sleep duration
final long startInNanos = System.nanoTime();

context.getThreadManager().runUntilResult(currentNode, () -> {
final long nowInNanos = System.nanoTime();
final long sleptInNanos = nowInNanos - startInNanos;
final long sleptInMillis = TimeUnit.NANOSECONDS.toMillis(sleptInNanos);

if (sleptInMillis >= durationInMillis || thread.wakeUp.getAndSet(false)) {
if (sleptInNanos >= durationInNanos || thread.wakeUp.getAndSet(false)) {
return BlockingAction.SUCCESS;
}

Thread.sleep(durationInMillis - sleptInMillis);
TimeUnit.NANOSECONDS.sleep(durationInNanos - sleptInNanos);
return BlockingAction.SUCCESS;
});

Expand Down
8 changes: 4 additions & 4 deletions src/main/java/org/truffleruby/core/mutex/MutexNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.truffleruby.builtins.CoreModule;
import org.truffleruby.builtins.UnaryCoreMethodNode;
import org.truffleruby.builtins.YieldingCoreMethodNode;
import org.truffleruby.core.cast.DurationToMillisecondsNodeGen;
import org.truffleruby.core.cast.DurationToNanoSecondsNodeGen;
import org.truffleruby.core.kernel.KernelNodes;
import org.truffleruby.core.klass.RubyClass;
import org.truffleruby.core.proc.RubyProc;
Expand Down Expand Up @@ -155,11 +155,11 @@ public abstract static class SleepNode extends CoreMethodNode {

@CreateCast("duration")
protected RubyBaseNodeWithExecute coerceDuration(RubyBaseNodeWithExecute duration) {
return DurationToMillisecondsNodeGen.create(true, duration);
return DurationToNanoSecondsNodeGen.create(true, duration);
}

@Specialization
protected long sleep(RubyMutex mutex, long durationInMillis,
protected long sleep(RubyMutex mutex, long durationInNanos,
@Cached BranchProfile errorProfile) {
final ReentrantLock lock = mutex.lock;
final RubyThread thread = getLanguage().getCurrentThread();
Expand All @@ -175,7 +175,7 @@ protected long sleep(RubyMutex mutex, long durationInMillis,

MutexOperations.unlock(lock, thread);
try {
return KernelNodes.SleepNode.sleepFor(getContext(), thread, durationInMillis, this);
return KernelNodes.SleepNode.sleepFor(getContext(), thread, durationInNanos, this);
} finally {
MutexOperations.lockEvenWithExceptions(getContext(), lock, thread, this);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/ruby/truffleruby/core/truffle/kernel_operations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ def self.to_enum_with_size(enum, method, size_method)
enum.to_enum(method) { enum.send(size_method) }
end

def self.convert_duration_to_milliseconds(duration)
def self.convert_duration_to_nanoseconds(duration)
unless duration.respond_to?(:divmod)
raise TypeError, "can't convert #{duration.class} into time interval"
end
a, b = duration.divmod(1)
((a + b) * 1000)
((a + b) * 1_000_000_000)
end

def self.define_hooked_variable(name, getter, setter, defined = proc { 'global-variable' })
Expand Down

0 comments on commit 15c9930

Please sign in to comment.