Skip to content

Commit 15c9930

Browse files
committed
Use nanosecond precision for Kernel#sleep and Mutex#sleep
* Fixes #2716
1 parent 95f5f97 commit 15c9930

File tree

5 files changed

+29
-32
lines changed

5 files changed

+29
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Bug fixes:
1414
* Fix `File::Stat`'s `#executable?` and `#executable_real?` predicates that unconditionally returned `true` for a superuser (#2690, @andrykonchin).
1515
* The `strip` option `--keep-section=.llvmbc` is not supported on macOS (#2697, @eregon).
1616
* Disallow the marshaling of polyglot exceptions since we can't properly reconstruct them (@nirvdrum).
17+
* Fix `Kernel#sleep` and `Mutex#sleep` for durations smaller than 1 millisecond (#2716, @eregon).
1718

1819
Compatibility:
1920

src/main/java/org/truffleruby/core/cast/DurationToMillisecondsNode.java renamed to src/main/java/org/truffleruby/core/cast/DurationToNanoSecondsNode.java

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
import com.oracle.truffle.api.profiles.ConditionProfile;
2222
import org.truffleruby.language.dispatch.DispatchNode;
2323

24+
import java.util.concurrent.TimeUnit;
25+
2426
@NodeChild(value = "duration", type = RubyBaseNodeWithExecute.class)
25-
public abstract class DurationToMillisecondsNode extends RubyBaseNodeWithExecute {
27+
public abstract class DurationToNanoSecondsNode extends RubyBaseNodeWithExecute {
2628

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

30-
public DurationToMillisecondsNode(boolean acceptsNil) {
32+
public DurationToNanoSecondsNode(boolean acceptsNil) {
3133
this.acceptsNil = acceptsNil;
3234
}
3335

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

39-
@Specialization
40-
protected long duration(int duration) {
41-
return validate(duration * 1000L);
42-
}
43-
4441
@Specialization
4542
protected long duration(long duration) {
46-
return validate(duration * 1000);
43+
return validate(TimeUnit.SECONDS.toNanos(duration));
4744
}
4845

4946
@Specialization
5047
protected long duration(double duration) {
51-
return validate((long) (duration * 1000));
48+
return validate((long) (duration * 1e9));
5249
}
5350

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

6562
@Specialization
6663
protected Object duration(RubyDynamicObject duration,
67-
@Cached DispatchNode durationToMilliseconds,
64+
@Cached DispatchNode durationToNanoSeconds,
6865
@Cached ToLongNode toLongNode) {
69-
final Object milliseconds = durationToMilliseconds.call(
66+
final Object nanoseconds = durationToNanoSeconds.call(
7067
coreLibrary().truffleKernelOperationsModule,
71-
"convert_duration_to_milliseconds",
68+
"convert_duration_to_nanoseconds",
7269
duration);
73-
return validate(toLongNode.execute(milliseconds));
70+
return validate(toLongNode.execute(nanoseconds));
7471
}
7572

76-
private long validate(long durationInMillis) {
77-
if (durationLessThanZeroProfile.profile(durationInMillis < 0)) {
73+
private long validate(long durationInNanos) {
74+
if (durationLessThanZeroProfile.profile(durationInNanos < 0)) {
7875
throw new RaiseException(getContext(), coreExceptions().argumentErrorTimeIntervalPositive(this));
7976
}
80-
return durationInMillis;
77+
return durationInNanos;
8178
}
8279

8380
}

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
import org.truffleruby.core.cast.BooleanCastNode;
4444
import org.truffleruby.core.cast.BooleanCastNodeGen;
4545
import org.truffleruby.core.cast.BooleanCastWithDefaultNode;
46-
import org.truffleruby.core.cast.DurationToMillisecondsNodeGen;
46+
import org.truffleruby.core.cast.DurationToNanoSecondsNodeGen;
4747
import org.truffleruby.core.cast.NameToJavaStringNode;
4848
import org.truffleruby.core.cast.ToIntNode;
4949
import org.truffleruby.core.cast.ToStrNode;
@@ -1608,13 +1608,13 @@ public abstract static class SleepNode extends CoreMethodNode {
16081608

16091609
@CreateCast("duration")
16101610
protected RubyBaseNodeWithExecute coerceDuration(RubyBaseNodeWithExecute duration) {
1611-
return DurationToMillisecondsNodeGen.create(false, duration);
1611+
return DurationToNanoSecondsNodeGen.create(false, duration);
16121612
}
16131613

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

1630-
return sleepFor(getContext(), thread, durationInMillis, this);
1630+
return sleepFor(getContext(), thread, durationInNanos, this);
16311631
}
16321632

16331633
@TruffleBoundary
1634-
public static long sleepFor(RubyContext context, RubyThread thread, long durationInMillis,
1634+
public static long sleepFor(RubyContext context, RubyThread thread, long durationInNanos,
16351635
Node currentNode) {
1636-
assert durationInMillis >= 0;
1636+
assert durationInNanos >= 0;
16371637

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

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

1646-
if (sleptInMillis >= durationInMillis || thread.wakeUp.getAndSet(false)) {
1645+
if (sleptInNanos >= durationInNanos || thread.wakeUp.getAndSet(false)) {
16471646
return BlockingAction.SUCCESS;
16481647
}
16491648

1650-
Thread.sleep(durationInMillis - sleptInMillis);
1649+
TimeUnit.NANOSECONDS.sleep(durationInNanos - sleptInNanos);
16511650
return BlockingAction.SUCCESS;
16521651
});
16531652

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import org.truffleruby.builtins.CoreModule;
2222
import org.truffleruby.builtins.UnaryCoreMethodNode;
2323
import org.truffleruby.builtins.YieldingCoreMethodNode;
24-
import org.truffleruby.core.cast.DurationToMillisecondsNodeGen;
24+
import org.truffleruby.core.cast.DurationToNanoSecondsNodeGen;
2525
import org.truffleruby.core.kernel.KernelNodes;
2626
import org.truffleruby.core.klass.RubyClass;
2727
import org.truffleruby.core.proc.RubyProc;
@@ -155,11 +155,11 @@ public abstract static class SleepNode extends CoreMethodNode {
155155

156156
@CreateCast("duration")
157157
protected RubyBaseNodeWithExecute coerceDuration(RubyBaseNodeWithExecute duration) {
158-
return DurationToMillisecondsNodeGen.create(true, duration);
158+
return DurationToNanoSecondsNodeGen.create(true, duration);
159159
}
160160

161161
@Specialization
162-
protected long sleep(RubyMutex mutex, long durationInMillis,
162+
protected long sleep(RubyMutex mutex, long durationInNanos,
163163
@Cached BranchProfile errorProfile) {
164164
final ReentrantLock lock = mutex.lock;
165165
final RubyThread thread = getLanguage().getCurrentThread();
@@ -175,7 +175,7 @@ protected long sleep(RubyMutex mutex, long durationInMillis,
175175

176176
MutexOperations.unlock(lock, thread);
177177
try {
178-
return KernelNodes.SleepNode.sleepFor(getContext(), thread, durationInMillis, this);
178+
return KernelNodes.SleepNode.sleepFor(getContext(), thread, durationInNanos, this);
179179
} finally {
180180
MutexOperations.lockEvenWithExceptions(getContext(), lock, thread, this);
181181
}

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' })

0 commit comments

Comments
 (0)