Skip to content

Commit 75f7183

Browse files
committed
[GR-18163] Use nanosecond precision for Kernel#sleep and Mutex#sleep (#2716)
PullRequest: truffleruby/3479
2 parents faa584a + 5cf7c51 commit 75f7183

File tree

7 files changed

+95
-125
lines changed

7 files changed

+95
-125
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Bug fixes:
1616
* The `strip` option `--keep-section=.llvmbc` is not supported on macOS (#2697, @eregon).
1717
* Disallow the marshaling of polyglot exceptions since we can't properly reconstruct them (@nirvdrum).
1818
* 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).
19+
* Fix `Kernel#sleep` and `Mutex#sleep` for durations smaller than 1 millisecond (#2716, @eregon).
1920

2021
Compatibility:
2122

src/main/java/org/truffleruby/core/cast/DurationToMillisecondsNode.java

Lines changed: 0 additions & 83 deletions
This file was deleted.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.core.cast;
11+
12+
import com.oracle.truffle.api.dsl.Cached;
13+
import com.oracle.truffle.api.dsl.Fallback;
14+
import org.truffleruby.language.RubyBaseNode;
15+
import org.truffleruby.language.NotProvided;
16+
import org.truffleruby.language.control.RaiseException;
17+
18+
import com.oracle.truffle.api.dsl.Specialization;
19+
import com.oracle.truffle.api.profiles.ConditionProfile;
20+
import org.truffleruby.language.dispatch.DispatchNode;
21+
22+
import java.util.concurrent.TimeUnit;
23+
24+
public abstract class DurationToNanoSecondsNode extends RubyBaseNode {
25+
26+
private final ConditionProfile durationLessThanZeroProfile = ConditionProfile.create();
27+
28+
public abstract long execute(Object duration);
29+
30+
@Specialization
31+
protected long noDuration(NotProvided duration) {
32+
return Long.MAX_VALUE;
33+
}
34+
35+
@Specialization
36+
protected long duration(long duration) {
37+
return validate(TimeUnit.SECONDS.toNanos(duration));
38+
}
39+
40+
@Specialization
41+
protected long duration(double duration) {
42+
return validate((long) (duration * 1e9));
43+
}
44+
45+
@Fallback
46+
protected long duration(Object duration,
47+
@Cached DispatchNode durationToNanoSeconds,
48+
@Cached ToLongNode toLongNode) {
49+
final Object nanoseconds = durationToNanoSeconds.call(
50+
coreLibrary().truffleKernelOperationsModule,
51+
"convert_duration_to_nanoseconds",
52+
duration);
53+
return validate(toLongNode.execute(nanoseconds));
54+
}
55+
56+
private long validate(long durationInNanos) {
57+
if (durationLessThanZeroProfile.profile(durationInNanos < 0)) {
58+
throw new RaiseException(getContext(), coreExceptions().argumentErrorTimeIntervalPositive(this));
59+
}
60+
return durationInNanos;
61+
}
62+
}

src/main/java/org/truffleruby/core/kernel/KernelNodes.java

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import org.truffleruby.core.cast.BooleanCastNode;
4545
import org.truffleruby.core.cast.BooleanCastNodeGen;
4646
import org.truffleruby.core.cast.BooleanCastWithDefaultNode;
47-
import org.truffleruby.core.cast.DurationToMillisecondsNodeGen;
47+
import org.truffleruby.core.cast.DurationToNanoSecondsNode;
4848
import org.truffleruby.core.cast.NameToJavaStringNode;
4949
import org.truffleruby.core.cast.ToIntNode;
5050
import org.truffleruby.core.cast.ToStrNode;
@@ -1604,52 +1604,41 @@ protected boolean hasSingletonMethods(Object self,
16041604

16051605
}
16061606

1607-
@NodeChild(value = "duration", type = RubyBaseNodeWithExecute.class)
16081607
@CoreMethod(names = "sleep", isModuleFunction = true, optional = 1)
1609-
public abstract static class SleepNode extends CoreMethodNode {
1610-
1611-
@CreateCast("duration")
1612-
protected RubyBaseNodeWithExecute coerceDuration(RubyBaseNodeWithExecute duration) {
1613-
return DurationToMillisecondsNodeGen.create(false, duration);
1614-
}
1608+
public abstract static class SleepNode extends CoreMethodArrayArgumentsNode {
16151609

16161610
@Specialization
1617-
protected long sleep(long durationInMillis,
1618-
@Cached BranchProfile errorProfile) {
1619-
if (durationInMillis < 0) {
1620-
errorProfile.enter();
1621-
throw new RaiseException(
1622-
getContext(),
1623-
coreExceptions().argumentError("time interval must be positive", this));
1624-
}
1611+
protected long sleep(Object maybeDuration,
1612+
@Cached DurationToNanoSecondsNode durationToNanoSecondsNode) {
1613+
long durationInNanos = durationToNanoSecondsNode.execute(maybeDuration);
1614+
assert durationInNanos >= 0;
16251615

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

16281618
// Clear the wakeUp flag, following Ruby semantics:
16291619
// it should only be considered if we are inside the sleep when Thread#{run,wakeup} is called.
16301620
thread.wakeUp.set(false);
16311621

1632-
return sleepFor(getContext(), thread, durationInMillis, this);
1622+
return sleepFor(getContext(), thread, durationInNanos, this);
16331623
}
16341624

16351625
@TruffleBoundary
1636-
public static long sleepFor(RubyContext context, RubyThread thread, long durationInMillis,
1626+
public static long sleepFor(RubyContext context, RubyThread thread, long durationInNanos,
16371627
Node currentNode) {
1638-
assert durationInMillis >= 0;
1628+
assert durationInNanos >= 0;
16391629

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

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

1648-
if (sleptInMillis >= durationInMillis || thread.wakeUp.getAndSet(false)) {
1637+
if (sleptInNanos >= durationInNanos || thread.wakeUp.getAndSet(false)) {
16491638
return BlockingAction.SUCCESS;
16501639
}
16511640

1652-
Thread.sleep(durationInMillis - sleptInMillis);
1641+
TimeUnit.NANOSECONDS.sleep(durationInNanos - sleptInNanos);
16531642
return BlockingAction.SUCCESS;
16541643
});
16551644

src/main/java/org/truffleruby/core/mutex/MutexNodes.java

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,20 @@
1010
package org.truffleruby.core.mutex;
1111

1212
import com.oracle.truffle.api.dsl.Cached;
13-
import com.oracle.truffle.api.dsl.CreateCast;
14-
import com.oracle.truffle.api.dsl.NodeChild;
1513
import com.oracle.truffle.api.dsl.Specialization;
1614
import com.oracle.truffle.api.profiles.BranchProfile;
1715
import com.oracle.truffle.api.profiles.ConditionProfile;
1816
import org.truffleruby.builtins.CoreMethod;
1917
import org.truffleruby.builtins.CoreMethodArrayArgumentsNode;
20-
import org.truffleruby.builtins.CoreMethodNode;
2118
import org.truffleruby.builtins.CoreModule;
2219
import org.truffleruby.builtins.UnaryCoreMethodNode;
2320
import org.truffleruby.builtins.YieldingCoreMethodNode;
24-
import org.truffleruby.core.cast.DurationToMillisecondsNodeGen;
21+
import org.truffleruby.core.cast.DurationToNanoSecondsNode;
2522
import org.truffleruby.core.kernel.KernelNodes;
2623
import org.truffleruby.core.klass.RubyClass;
2724
import org.truffleruby.core.proc.RubyProc;
2825
import org.truffleruby.core.thread.RubyThread;
29-
import org.truffleruby.language.RubyBaseNodeWithExecute;
30-
import org.truffleruby.language.RubyNode;
26+
import org.truffleruby.language.NotProvided;
3127
import org.truffleruby.language.Visibility;
3228
import org.truffleruby.language.control.RaiseException;
3329
import org.truffleruby.language.objects.AllocationTracing;
@@ -148,19 +144,20 @@ protected Object synchronize(RubyMutex mutex, RubyProc block,
148144

149145
}
150146

151-
@NodeChild(value = "mutex", type = RubyNode.class)
152-
@NodeChild(value = "duration", type = RubyBaseNodeWithExecute.class)
153147
@CoreMethod(names = "sleep", optional = 1)
154-
public abstract static class SleepNode extends CoreMethodNode {
155-
156-
@CreateCast("duration")
157-
protected RubyBaseNodeWithExecute coerceDuration(RubyBaseNodeWithExecute duration) {
158-
return DurationToMillisecondsNodeGen.create(true, duration);
159-
}
148+
public abstract static class SleepNode extends CoreMethodArrayArgumentsNode {
160149

161150
@Specialization
162-
protected long sleep(RubyMutex mutex, long durationInMillis,
151+
protected long sleep(RubyMutex mutex, Object maybeDuration,
152+
@Cached DurationToNanoSecondsNode durationToNanoSecondsNode,
153+
@Cached ConditionProfile nilProfile,
163154
@Cached BranchProfile errorProfile) {
155+
if (nilProfile.profile(maybeDuration == nil)) {
156+
maybeDuration = NotProvided.INSTANCE;
157+
}
158+
159+
long durationInNanos = durationToNanoSecondsNode.execute(maybeDuration);
160+
164161
final ReentrantLock lock = mutex.lock;
165162
final RubyThread thread = getLanguage().getCurrentThread();
166163

@@ -175,7 +172,7 @@ protected long sleep(RubyMutex mutex, long durationInMillis,
175172

176173
MutexOperations.unlock(lock, thread);
177174
try {
178-
return KernelNodes.SleepNode.sleepFor(getContext(), thread, durationInMillis, this);
175+
return KernelNodes.SleepNode.sleepFor(getContext(), thread, durationInNanos, this);
179176
} finally {
180177
MutexOperations.lockEvenWithExceptions(getContext(), lock, thread, this);
181178
}

src/main/ruby/truffleruby/core/truffle/kernel_operations.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ def self.to_enum_with_size(enum, method, size_method)
2222
enum.to_enum(method) { enum.send(size_method) }
2323
end
2424

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

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

tool/jt.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ def help
781781
--reveal enable assertions
782782
--asm show assembly
783783
--igv dump select Graal graphs to graal_dumps/ (-Dgraal.Dump=Truffle:1)
784-
--igv-full dump all Graal graphs to graal_dumps/ (-Dgraal.Dump=Truffle:2)
784+
--igv-full dump all Graal graphs to graal_dumps/ (-Dgraal.Dump=Truffle:2,TruffleHostInlining:0)
785785
--igv-network dump to IGV directly through the network (-Dgraal.PrintGraph=Network)
786786
--infopoints show source location for each node in IGV
787787
--fg disable background compilation
@@ -1018,7 +1018,11 @@ def rebuild(*options)
10181018
vm_args << '--engine.TraceCompilation'
10191019
when '--igv', '--igv-full'
10201020
truffleruby_compiler!
1021-
vm_args << (arg == '--igv-full' ? '--vm.Dgraal.Dump=Truffle:2' : '--vm.Dgraal.Dump=Truffle:1')
1021+
if arg == '--igv-full'
1022+
vm_args << '--vm.Dgraal.Dump=Truffle:2,TruffleHostInlining:0'
1023+
else
1024+
vm_args << '--vm.Dgraal.Dump=Truffle:1'
1025+
end
10221026
vm_args << '--vm.Dgraal.PrintBackendCFG=false'
10231027
when '--igv-network'
10241028
truffleruby_compiler!

0 commit comments

Comments
 (0)