Skip to content

Commit

Permalink
[GR-18163] Use nanosecond precision for Kernel#sleep and Mutex#sleep (#…
Browse files Browse the repository at this point in the history
…2716)

PullRequest: truffleruby/3479
  • Loading branch information
eregon committed Sep 3, 2022
2 parents faa584a + 5cf7c51 commit 75f7183
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 125 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Bug fixes:
* 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 `String#split` missing a value in its return array when called with a pattern of `" "` and a _limit_ value > 0 on a string with trailing whitespace where the limit hasn't been met (@nirvdrum).
* Fix `Kernel#sleep` and `Mutex#sleep` for durations smaller than 1 millisecond (#2716, @eregon).

Compatibility:

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2016, 2021 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 2.0, or
* GNU General Public License version 2, or
* GNU Lesser General Public License version 2.1.
*/
package org.truffleruby.core.cast;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
import org.truffleruby.language.RubyBaseNode;
import org.truffleruby.language.NotProvided;
import org.truffleruby.language.control.RaiseException;

import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.truffleruby.language.dispatch.DispatchNode;

import java.util.concurrent.TimeUnit;

public abstract class DurationToNanoSecondsNode extends RubyBaseNode {

private final ConditionProfile durationLessThanZeroProfile = ConditionProfile.create();

public abstract long execute(Object duration);

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

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

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

@Fallback
protected long duration(Object duration,
@Cached DispatchNode durationToNanoSeconds,
@Cached ToLongNode toLongNode) {
final Object nanoseconds = durationToNanoSeconds.call(
coreLibrary().truffleKernelOperationsModule,
"convert_duration_to_nanoseconds",
duration);
return validate(toLongNode.execute(nanoseconds));
}

private long validate(long durationInNanos) {
if (durationLessThanZeroProfile.profile(durationInNanos < 0)) {
throw new RaiseException(getContext(), coreExceptions().argumentErrorTimeIntervalPositive(this));
}
return durationInNanos;
}
}
33 changes: 11 additions & 22 deletions src/main/java/org/truffleruby/core/kernel/KernelNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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.DurationToNanoSecondsNode;
import org.truffleruby.core.cast.NameToJavaStringNode;
import org.truffleruby.core.cast.ToIntNode;
import org.truffleruby.core.cast.ToStrNode;
Expand Down Expand Up @@ -1604,52 +1604,41 @@ protected boolean hasSingletonMethods(Object self,

}

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

@CreateCast("duration")
protected RubyBaseNodeWithExecute coerceDuration(RubyBaseNodeWithExecute duration) {
return DurationToMillisecondsNodeGen.create(false, duration);
}
public abstract static class SleepNode extends CoreMethodArrayArgumentsNode {

@Specialization
protected long sleep(long durationInMillis,
@Cached BranchProfile errorProfile) {
if (durationInMillis < 0) {
errorProfile.enter();
throw new RaiseException(
getContext(),
coreExceptions().argumentError("time interval must be positive", this));
}
protected long sleep(Object maybeDuration,
@Cached DurationToNanoSecondsNode durationToNanoSecondsNode) {
long durationInNanos = durationToNanoSecondsNode.execute(maybeDuration);
assert durationInNanos >= 0;

final RubyThread thread = getLanguage().getCurrentThread();

// Clear the wakeUp flag, following Ruby semantics:
// 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
29 changes: 13 additions & 16 deletions src/main/java/org/truffleruby/core/mutex/MutexNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,20 @@
package org.truffleruby.core.mutex;

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.Specialization;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.truffleruby.builtins.CoreMethod;
import org.truffleruby.builtins.CoreMethodArrayArgumentsNode;
import org.truffleruby.builtins.CoreMethodNode;
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.DurationToNanoSecondsNode;
import org.truffleruby.core.kernel.KernelNodes;
import org.truffleruby.core.klass.RubyClass;
import org.truffleruby.core.proc.RubyProc;
import org.truffleruby.core.thread.RubyThread;
import org.truffleruby.language.RubyBaseNodeWithExecute;
import org.truffleruby.language.RubyNode;
import org.truffleruby.language.NotProvided;
import org.truffleruby.language.Visibility;
import org.truffleruby.language.control.RaiseException;
import org.truffleruby.language.objects.AllocationTracing;
Expand Down Expand Up @@ -148,19 +144,20 @@ protected Object synchronize(RubyMutex mutex, RubyProc block,

}

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

@CreateCast("duration")
protected RubyBaseNodeWithExecute coerceDuration(RubyBaseNodeWithExecute duration) {
return DurationToMillisecondsNodeGen.create(true, duration);
}
public abstract static class SleepNode extends CoreMethodArrayArgumentsNode {

@Specialization
protected long sleep(RubyMutex mutex, long durationInMillis,
protected long sleep(RubyMutex mutex, Object maybeDuration,
@Cached DurationToNanoSecondsNode durationToNanoSecondsNode,
@Cached ConditionProfile nilProfile,
@Cached BranchProfile errorProfile) {
if (nilProfile.profile(maybeDuration == nil)) {
maybeDuration = NotProvided.INSTANCE;
}

long durationInNanos = durationToNanoSecondsNode.execute(maybeDuration);

final ReentrantLock lock = mutex.lock;
final RubyThread thread = getLanguage().getCurrentThread();

Expand All @@ -175,7 +172,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
8 changes: 6 additions & 2 deletions tool/jt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ def help
--reveal enable assertions
--asm show assembly
--igv dump select Graal graphs to graal_dumps/ (-Dgraal.Dump=Truffle:1)
--igv-full dump all Graal graphs to graal_dumps/ (-Dgraal.Dump=Truffle:2)
--igv-full dump all Graal graphs to graal_dumps/ (-Dgraal.Dump=Truffle:2,TruffleHostInlining:0)
--igv-network dump to IGV directly through the network (-Dgraal.PrintGraph=Network)
--infopoints show source location for each node in IGV
--fg disable background compilation
Expand Down Expand Up @@ -1018,7 +1018,11 @@ def rebuild(*options)
vm_args << '--engine.TraceCompilation'
when '--igv', '--igv-full'
truffleruby_compiler!
vm_args << (arg == '--igv-full' ? '--vm.Dgraal.Dump=Truffle:2' : '--vm.Dgraal.Dump=Truffle:1')
if arg == '--igv-full'
vm_args << '--vm.Dgraal.Dump=Truffle:2,TruffleHostInlining:0'
else
vm_args << '--vm.Dgraal.Dump=Truffle:1'
end
vm_args << '--vm.Dgraal.PrintBackendCFG=false'
when '--igv-network'
truffleruby_compiler!
Expand Down

0 comments on commit 75f7183

Please sign in to comment.