From 6b96a4182115127826dcc274ebf426d64b64f09e Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 31 Oct 2023 14:38:51 -0500 Subject: [PATCH] Guard Fiber::Scheduler support behind a property This commit adds the property "jruby.experimental.fiber.scheduler" to control whether the Fiber::Scheduler subsystem will be used at runtime. It is currently off by default, as this is an experimental feature in all versions of Ruby that support it. Set this property at the JVM level, or pass -Xexperimental.fiber.scheduler to enable it. None of the classes or constants associated with the fiber scheduler will be defined if the property is not enabled. In the future, this property may disappear when this feature is finalized, or may remain under a different name to allow disabling all scheduler checks. --- core/src/main/java/org/jruby/Ruby.java | 19 ++++-- core/src/main/java/org/jruby/RubyIO.java | 11 ++-- .../src/main/java/org/jruby/RubyIOBuffer.java | 1 + .../java/org/jruby/ext/fiber/ThreadFiber.java | 58 +++++++++--------- .../jruby/ext/fiber/ThreadFiberLibrary.java | 6 ++ .../java/org/jruby/util/cli/Category.java | 3 +- .../main/java/org/jruby/util/cli/Options.java | 2 + .../main/java/org/jruby/util/io/OpenFile.java | 61 +++++++++++-------- rakelib/test.rake | 6 +- 9 files changed, 100 insertions(+), 67 deletions(-) diff --git a/core/src/main/java/org/jruby/Ruby.java b/core/src/main/java/org/jruby/Ruby.java index f24ecba7807e..4736f50fb184 100644 --- a/core/src/main/java/org/jruby/Ruby.java +++ b/core/src/main/java/org/jruby/Ruby.java @@ -443,7 +443,11 @@ private Ruby(RubyInstanceConfig config) { randomClass = null; } ioClass = RubyIO.createIOClass(this); - ioBufferClass = RubyIOBuffer.createIOBufferClass(this); + if (Options.FIBER_SCHEDULER.load()) { + ioBufferClass = RubyIOBuffer.createIOBufferClass(this); + } else { + ioBufferClass = null; + } structClass = profile.allowClass("Struct") ? RubyStruct.createStructClass(this) : null; bindingClass = profile.allowClass("Binding") ? RubyBinding.createBindingClass(this) : null; @@ -1718,11 +1722,14 @@ private void initExceptions() { RubyClass runtimeError = this.runtimeError; ObjectAllocator runtimeErrorAllocator = runtimeError.getAllocator(); - bufferLockedError = ioBufferClass.defineClassUnder("LockedError", runtimeError, runtimeErrorAllocator); - bufferAllocationError = ioBufferClass.defineClassUnder("AllocationError", runtimeError, runtimeErrorAllocator); - bufferAccessError = ioBufferClass.defineClassUnder("AccessError", runtimeError, runtimeErrorAllocator); - bufferInvalidatedError = ioBufferClass.defineClassUnder("InvalidatedError", runtimeError, runtimeErrorAllocator); - bufferMaskError = ioBufferClass.defineClassUnder("MaskError", runtimeError, runtimeErrorAllocator); + + if (Options.FIBER_SCHEDULER.load()) { + bufferLockedError = ioBufferClass.defineClassUnder("LockedError", runtimeError, runtimeErrorAllocator); + bufferAllocationError = ioBufferClass.defineClassUnder("AllocationError", runtimeError, runtimeErrorAllocator); + bufferAccessError = ioBufferClass.defineClassUnder("AccessError", runtimeError, runtimeErrorAllocator); + bufferInvalidatedError = ioBufferClass.defineClassUnder("InvalidatedError", runtimeError, runtimeErrorAllocator); + bufferMaskError = ioBufferClass.defineClassUnder("MaskError", runtimeError, runtimeErrorAllocator); + } initErrno(); diff --git a/core/src/main/java/org/jruby/RubyIO.java b/core/src/main/java/org/jruby/RubyIO.java index 1fd0e2c81551..e224f8c9b915 100644 --- a/core/src/main/java/org/jruby/RubyIO.java +++ b/core/src/main/java/org/jruby/RubyIO.java @@ -86,6 +86,7 @@ import org.jruby.runtime.encoding.EncodingService; import org.jruby.util.ShellLauncher.POpenProcess; import org.jruby.util.*; +import org.jruby.util.cli.Options; import org.jruby.util.io.ChannelFD; import org.jruby.util.io.EncodingUtils; import org.jruby.util.io.FilenoUtil; @@ -3795,10 +3796,12 @@ public static RubyIO convertToIO(ThreadContext context, IRubyObject obj) { @JRubyMethod(name = "select", required = 1, optional = 3, checkArity = false, meta = true) public static IRubyObject select(ThreadContext context, IRubyObject recv, IRubyObject[] argv) { - IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); - if (!scheduler.isNil()) { - IRubyObject result = FiberScheduler.ioSelectv(context, scheduler, argv); - if (result != UNDEF) return result; + if (Options.FIBER_SCHEDULER.load()) { + IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); + if (!scheduler.isNil()) { + IRubyObject result = FiberScheduler.ioSelectv(context, scheduler, argv); + if (result != UNDEF) return result; + } } int argc = Arity.checkArgumentCount(context, argv, 1, 4); diff --git a/core/src/main/java/org/jruby/RubyIOBuffer.java b/core/src/main/java/org/jruby/RubyIOBuffer.java index 5d5e13e66679..dfe2916cf588 100644 --- a/core/src/main/java/org/jruby/RubyIOBuffer.java +++ b/core/src/main/java/org/jruby/RubyIOBuffer.java @@ -14,6 +14,7 @@ import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; +import org.jruby.util.cli.Options; import org.jruby.util.io.ChannelFD; import org.jruby.util.io.OpenFile; diff --git a/core/src/main/java/org/jruby/ext/fiber/ThreadFiber.java b/core/src/main/java/org/jruby/ext/fiber/ThreadFiber.java index c676c7dd64b4..7844b49fa5d7 100644 --- a/core/src/main/java/org/jruby/ext/fiber/ThreadFiber.java +++ b/core/src/main/java/org/jruby/ext/fiber/ThreadFiber.java @@ -691,38 +691,40 @@ public IRubyObject backtrace_locations(ThreadContext context, IRubyObject level, return threadFiber.thread.backtrace_locations(context, level, length); } - // MRI: rb_fiber_s_schedule_kw and rb_fiber_s_schedule, kw passes on context - @JRubyMethod(name = "schedule", meta = true, rest = true, keywords = true) - public static IRubyObject schedule(ThreadContext context, IRubyObject self, IRubyObject[] args, Block block) { - RubyThread thread = context.getThread(); - IRubyObject scheduler = thread.getScheduler(); - IRubyObject fiber = context.nil; - - if (!scheduler.isNil()) { - fiber = scheduler.callMethod(context, "fiber", args, block); - } else { - throw context.runtime.newRuntimeError("No scheduler is available!"); - } + public static class FiberSchedulerSupport { + // MRI: rb_fiber_s_schedule_kw and rb_fiber_s_schedule, kw passes on context + @JRubyMethod(name = "schedule", meta = true, rest = true, keywords = true) + public static IRubyObject schedule(ThreadContext context, IRubyObject self, IRubyObject[] args, Block block) { + RubyThread thread = context.getThread(); + IRubyObject scheduler = thread.getScheduler(); + IRubyObject fiber = context.nil; + + if (!scheduler.isNil()) { + fiber = scheduler.callMethod(context, "fiber", args, block); + } else { + throw context.runtime.newRuntimeError("No scheduler is available!"); + } - return fiber; - } + return fiber; + } - // MRI: rb_fiber_s_scheduler - @JRubyMethod(name = "scheduler", meta = true) - public static IRubyObject get_scheduler(ThreadContext context, IRubyObject self) { - return context.getFiberCurrentThread().getScheduler(); - } + // MRI: rb_fiber_s_scheduler + @JRubyMethod(name = "scheduler", meta = true) + public static IRubyObject get_scheduler(ThreadContext context, IRubyObject self) { + return context.getFiberCurrentThread().getScheduler(); + } - // MRI: rb_fiber_current_scheduler - @JRubyMethod(name = "current_scheduler", meta = true) - public static IRubyObject current_scheduler(ThreadContext context, IRubyObject self) { - return context.getFiberCurrentThread().getSchedulerCurrent(); - } + // MRI: rb_fiber_current_scheduler + @JRubyMethod(name = "current_scheduler", meta = true) + public static IRubyObject current_scheduler(ThreadContext context, IRubyObject self) { + return context.getFiberCurrentThread().getSchedulerCurrent(); + } - // MRI: rb_fiber_set_scheduler - @JRubyMethod(name = "set_scheduler", meta = true) - public static IRubyObject set_scheduler(ThreadContext context, IRubyObject self, IRubyObject scheduler) { - return context.getFiberCurrentThread().setFiberScheduler(scheduler); + // MRI: rb_fiber_set_scheduler + @JRubyMethod(name = "set_scheduler", meta = true) + public static IRubyObject set_scheduler(ThreadContext context, IRubyObject self, IRubyObject scheduler) { + return context.getFiberCurrentThread().setFiberScheduler(scheduler); + } } public FiberData getData() { diff --git a/core/src/main/java/org/jruby/ext/fiber/ThreadFiberLibrary.java b/core/src/main/java/org/jruby/ext/fiber/ThreadFiberLibrary.java index e6582249d6af..92d2c7222693 100644 --- a/core/src/main/java/org/jruby/ext/fiber/ThreadFiberLibrary.java +++ b/core/src/main/java/org/jruby/ext/fiber/ThreadFiberLibrary.java @@ -33,6 +33,7 @@ import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.load.Library; import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.util.cli.Options; /** * A thread-based implementation of Ruby 1.9 Fiber library. @@ -43,6 +44,11 @@ public RubyClass createFiberClass(final Ruby runtime) { cFiber.defineAnnotatedMethods(ThreadFiber.class); + if (Options.FIBER_SCHEDULER.load()) { + // define additional methods for Fiber::Scheduler support + cFiber.defineAnnotatedMethods(ThreadFiber.FiberSchedulerSupport.class); + } + return cFiber; } } diff --git a/core/src/main/java/org/jruby/util/cli/Category.java b/core/src/main/java/org/jruby/util/cli/Category.java index 934ef1e54321..9df89cb3dd30 100644 --- a/core/src/main/java/org/jruby/util/cli/Category.java +++ b/core/src/main/java/org/jruby/util/cli/Category.java @@ -46,7 +46,8 @@ public enum Category { JAVA_INTEGRATION("java integration"), PROFILING("profiling"), CLI("command line options"), - COMPLIANCE("compliance options"); + COMPLIANCE("compliance options"), + EXPERIMENTAL("experimental features"); Category(String desc) { this.desc = desc; diff --git a/core/src/main/java/org/jruby/util/cli/Options.java b/core/src/main/java/org/jruby/util/cli/Options.java index 77492f731d88..040ffb8d3308 100644 --- a/core/src/main/java/org/jruby/util/cli/Options.java +++ b/core/src/main/java/org/jruby/util/cli/Options.java @@ -193,6 +193,8 @@ public class Options { public static final Option PROFILE_MAX_METHODS = integer(PROFILING, "profile.max.methods", 100000, "Maximum number of methods to consider for profiling."); + public static final Option FIBER_SCHEDULER = bool(EXPERIMENTAL, "fiber.scheduler", false, "Enable experimental Fiber::Scheduler support."); + public static final Option CLI_AUTOSPLIT = bool(CLI, "cli.autosplit", false, "Split $_ into $F for -p or -n. Same as -a."); public static final Option CLI_DEBUG = bool(CLI, "cli.debug", false, "Enable debug mode logging. Same as -d."); public static final Option CLI_PROCESS_LINE_ENDS = bool(CLI, "cli.process.line.ends", false, "Enable line ending processing. Same as -l."); diff --git a/core/src/main/java/org/jruby/util/io/OpenFile.java b/core/src/main/java/org/jruby/util/io/OpenFile.java index 6a3ec3a7cd71..87ea1e962c8a 100644 --- a/core/src/main/java/org/jruby/util/io/OpenFile.java +++ b/core/src/main/java/org/jruby/util/io/OpenFile.java @@ -28,7 +28,6 @@ import org.jruby.Finalizable; import org.jruby.Ruby; import org.jruby.RubyArgsFile; -import org.jruby.RubyBasicObject; import org.jruby.RubyBignum; import org.jruby.RubyEncoding; import org.jruby.RubyException; @@ -48,6 +47,7 @@ import org.jruby.util.ByteList; import org.jruby.util.ShellLauncher; import org.jruby.util.StringSupport; +import org.jruby.util.cli.Options; import static org.jruby.util.StringSupport.*; @@ -61,6 +61,7 @@ public OpenFile(RubyIO io, IRubyObject nil) { writeconvPreEcopts = nil; encs.ecopts = nil; posix = new PosixShim(runtime); + fiberScheduler = Options.FIBER_SCHEDULER.load(); } // IO Mode flags @@ -162,6 +163,8 @@ public static class Buffer { private final Ptr spPtr = new Ptr(); private final Ptr dpPtr = new Ptr(); + private final boolean fiberScheduler; + public void clearStdio() { stdio_file = null; } @@ -462,7 +465,7 @@ public int io_fflush(ThreadContext context) { // rb_io_wait_writable public boolean waitWritable(ThreadContext context, long timeout) { - IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); + IRubyObject scheduler = fiberScheduler ? context.getFiberCurrentThread().getSchedulerCurrent() : null; boolean locked = lock(); try { @@ -477,7 +480,7 @@ public boolean waitWritable(ThreadContext context, long timeout) { return true; case EAGAIN: case EWOULDBLOCK: - if (!scheduler.isNil()) { + if (fiberScheduler && !scheduler.isNil()) { return FiberScheduler.ioWaitWritable(context, scheduler, RubyIO.newIO(context.runtime, channel())).isTrue(); } @@ -498,7 +501,7 @@ public boolean waitWritable(ThreadContext context) { // rb_io_wait_readable public boolean waitReadable(ThreadContext context, long timeout) { - IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); + IRubyObject scheduler = fiberScheduler ? context.getFiberCurrentThread().getSchedulerCurrent() : null; boolean locked = lock(); try { @@ -513,7 +516,7 @@ public boolean waitReadable(ThreadContext context, long timeout) { return true; case EAGAIN: case EWOULDBLOCK: - if (!scheduler.isNil()) { + if (fiberScheduler && !scheduler.isNil()) { return FiberScheduler.ioWaitReadable(context, scheduler, RubyIO.newIO(context.runtime, channel())).isTrue(); } @@ -1478,12 +1481,14 @@ public static int readInternal(ThreadContext context, OpenFile fptr, ChannelFD f // rb_io_buffer_read_internal public static int readInternal(ThreadContext context, OpenFile fptr, ChannelFD fd, ByteBuffer buffer, int buf, int count) { // try scheduler first - IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); - if (!scheduler.isNil()) { - IRubyObject result = FiberScheduler.ioReadMemory(context, scheduler, fptr.io, buffer, buf, count); + if (fptr.fiberScheduler) { + IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); + if (!scheduler.isNil()) { + IRubyObject result = FiberScheduler.ioReadMemory(context, scheduler, fptr.io, buffer, buf, count); - if (result != null) { - return FiberScheduler.resultApply(context, result); + if (result != null) { + return FiberScheduler.resultApply(context, result); + } } } @@ -1513,12 +1518,14 @@ simple read(2) because EINTR does not damage the descriptor. public static int preadInternal(ThreadContext context, OpenFile fptr, ChannelFD fd, ByteBuffer buffer, int from, int length) { // try scheduler first - IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); - if (!scheduler.isNil()) { - IRubyObject result = FiberScheduler.ioPReadMemory(context, scheduler, fptr.io, buffer, from, length, 0); + if (fptr.fiberScheduler) { + IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); + if (!scheduler.isNil()) { + IRubyObject result = FiberScheduler.ioPReadMemory(context, scheduler, fptr.io, buffer, from, length, 0); - if (result != null) { - return FiberScheduler.resultApply(context, result); + if (result != null) { + return FiberScheduler.resultApply(context, result); + } } } @@ -1532,12 +1539,14 @@ public static int preadInternal(ThreadContext context, OpenFile fptr, ChannelFD public static int pwriteInternal(ThreadContext context, OpenFile fptr, ChannelFD fd, ByteBuffer buffer, int from, int length) { // try scheduler first - IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); - if (!scheduler.isNil()) { - IRubyObject result = FiberScheduler.ioPWriteMemory(context, scheduler, fptr.io, buffer, from, length, 0); + if (fptr.fiberScheduler) { + IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); + if (!scheduler.isNil()) { + IRubyObject result = FiberScheduler.ioPWriteMemory(context, scheduler, fptr.io, buffer, from, length, 0); - if (result != null) { - return FiberScheduler.resultApply(context, result); + if (result != null) { + return FiberScheduler.resultApply(context, result); + } } } @@ -2479,12 +2488,14 @@ public static int writeInternal(ThreadContext context, OpenFile fptr, byte[] buf // rb_io_buffer_write_internal public static int writeInternal(ThreadContext context, OpenFile fptr, ByteBuffer bufBytes, int buf, int count) { - IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); - if (!scheduler.isNil()) { - IRubyObject result = FiberScheduler.ioWriteMemory(context, scheduler, fptr.io, bufBytes, buf, count); + if (fptr.fiberScheduler) { + IRubyObject scheduler = context.getFiberCurrentThread().getSchedulerCurrent(); + if (!scheduler.isNil()) { + IRubyObject result = FiberScheduler.ioWriteMemory(context, scheduler, fptr.io, bufBytes, buf, count); - if (result != null) { - return FiberScheduler.resultApply(context, result); + if (result != null) { + return FiberScheduler.resultApply(context, result); + } } } diff --git a/rakelib/test.rake b/rakelib/test.rake index 1f531dee797b..51b20dc86d6e 100644 --- a/rakelib/test.rake +++ b/rakelib/test.rake @@ -100,9 +100,9 @@ namespace :test do mri_suites = [:core, :extra, :stdlib] mri_suites = { - core: "-Xbacktrace.style=mri -Xdebug.fullTrace", - extra: "--disable-gems -Xbacktrace.style=mri -Xdebug.fullTrace", - stdlib: "-Xbacktrace.style=mri -Xdebug.fullTrace", + core: "-Xbacktrace.style=mri -Xdebug.fullTrace -Xexperimental.fiber.scheduler", + extra: "--disable-gems -Xbacktrace.style=mri -Xdebug.fullTrace -Xexperimental.fiber.scheduler", + stdlib: "-Xbacktrace.style=mri -Xdebug.fullTrace -Xexperimental.fiber.scheduler", } mri_suites.each do |suite, extra_jruby_opts|