Make Monitor a core class

[Feature #21788]

It allows monitor to access internal routines and remove some overhead.

Before:

```
ruby 4.0.0dev (2025-12-13T04:52:13Z master 71dd272506) +YJIT +PRISM [arm64-darwin25]
Warming up --------------------------------------
               Mutex     2.111M i/100ms
             Monitor     1.736M i/100ms
Calculating -------------------------------------
               Mutex     25.050M (± 0.4%) i/s   (39.92 ns/i) -    126.631M in   5.055208s
             Monitor     19.809M (± 0.1%) i/s   (50.48 ns/i) -    100.672M in   5.082015s
```

After:

```
ruby 4.0.0dev (2025-12-13T06:49:18Z core-monitor 6fabf389fd) +YJIT +PRISM [arm64-darwin25]
Warming up --------------------------------------
               Mutex     2.144M i/100ms
             Monitor     1.859M i/100ms
Calculating -------------------------------------
               Mutex     24.771M (± 0.4%) i/s   (40.37 ns/i) -    124.342M in   5.019716s
             Monitor     23.722M (± 0.4%) i/s   (42.15 ns/i) -    118.998M in   5.016361s
```

Bench:

```ruby
require 'bundler/inline'

gemfile do
  gem "benchmark-ips"
end

mutex = Mutex.new
require "monitor"
monitor = Monitor.new

Benchmark.ips do |x|
  x.report("Mutex") { mutex.synchronize { } }
  x.report("Monitor") { monitor.synchronize { } }
end
```
This commit is contained in:
Jean Boussier 2025-12-13 06:35:58 +01:00
parent f33073a6cb
commit d8b8a95af9
Notes: git 2026-02-12 19:35:46 +00:00
7 changed files with 455 additions and 546 deletions

View File

@ -1,162 +0,0 @@
# AUTOGENERATED DEPENDENCIES START
monitor.o: $(RUBY_EXTCONF_H)
monitor.o: $(arch_hdrdir)/ruby/config.h
monitor.o: $(hdrdir)/ruby/assert.h
monitor.o: $(hdrdir)/ruby/backward.h
monitor.o: $(hdrdir)/ruby/backward/2/assume.h
monitor.o: $(hdrdir)/ruby/backward/2/attributes.h
monitor.o: $(hdrdir)/ruby/backward/2/bool.h
monitor.o: $(hdrdir)/ruby/backward/2/inttypes.h
monitor.o: $(hdrdir)/ruby/backward/2/limits.h
monitor.o: $(hdrdir)/ruby/backward/2/long_long.h
monitor.o: $(hdrdir)/ruby/backward/2/stdalign.h
monitor.o: $(hdrdir)/ruby/backward/2/stdarg.h
monitor.o: $(hdrdir)/ruby/defines.h
monitor.o: $(hdrdir)/ruby/intern.h
monitor.o: $(hdrdir)/ruby/internal/abi.h
monitor.o: $(hdrdir)/ruby/internal/anyargs.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/char.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/double.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/int.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/long.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/short.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h
monitor.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h
monitor.o: $(hdrdir)/ruby/internal/assume.h
monitor.o: $(hdrdir)/ruby/internal/attr/alloc_size.h
monitor.o: $(hdrdir)/ruby/internal/attr/artificial.h
monitor.o: $(hdrdir)/ruby/internal/attr/cold.h
monitor.o: $(hdrdir)/ruby/internal/attr/const.h
monitor.o: $(hdrdir)/ruby/internal/attr/constexpr.h
monitor.o: $(hdrdir)/ruby/internal/attr/deprecated.h
monitor.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h
monitor.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h
monitor.o: $(hdrdir)/ruby/internal/attr/error.h
monitor.o: $(hdrdir)/ruby/internal/attr/flag_enum.h
monitor.o: $(hdrdir)/ruby/internal/attr/forceinline.h
monitor.o: $(hdrdir)/ruby/internal/attr/format.h
monitor.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h
monitor.o: $(hdrdir)/ruby/internal/attr/noalias.h
monitor.o: $(hdrdir)/ruby/internal/attr/nodiscard.h
monitor.o: $(hdrdir)/ruby/internal/attr/noexcept.h
monitor.o: $(hdrdir)/ruby/internal/attr/noinline.h
monitor.o: $(hdrdir)/ruby/internal/attr/nonnull.h
monitor.o: $(hdrdir)/ruby/internal/attr/noreturn.h
monitor.o: $(hdrdir)/ruby/internal/attr/packed_struct.h
monitor.o: $(hdrdir)/ruby/internal/attr/pure.h
monitor.o: $(hdrdir)/ruby/internal/attr/restrict.h
monitor.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h
monitor.o: $(hdrdir)/ruby/internal/attr/warning.h
monitor.o: $(hdrdir)/ruby/internal/attr/weakref.h
monitor.o: $(hdrdir)/ruby/internal/cast.h
monitor.o: $(hdrdir)/ruby/internal/compiler_is.h
monitor.o: $(hdrdir)/ruby/internal/compiler_is/apple.h
monitor.o: $(hdrdir)/ruby/internal/compiler_is/clang.h
monitor.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h
monitor.o: $(hdrdir)/ruby/internal/compiler_is/intel.h
monitor.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h
monitor.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h
monitor.o: $(hdrdir)/ruby/internal/compiler_since.h
monitor.o: $(hdrdir)/ruby/internal/config.h
monitor.o: $(hdrdir)/ruby/internal/constant_p.h
monitor.o: $(hdrdir)/ruby/internal/core.h
monitor.o: $(hdrdir)/ruby/internal/core/rarray.h
monitor.o: $(hdrdir)/ruby/internal/core/rbasic.h
monitor.o: $(hdrdir)/ruby/internal/core/rbignum.h
monitor.o: $(hdrdir)/ruby/internal/core/rclass.h
monitor.o: $(hdrdir)/ruby/internal/core/rdata.h
monitor.o: $(hdrdir)/ruby/internal/core/rfile.h
monitor.o: $(hdrdir)/ruby/internal/core/rhash.h
monitor.o: $(hdrdir)/ruby/internal/core/robject.h
monitor.o: $(hdrdir)/ruby/internal/core/rregexp.h
monitor.o: $(hdrdir)/ruby/internal/core/rstring.h
monitor.o: $(hdrdir)/ruby/internal/core/rstruct.h
monitor.o: $(hdrdir)/ruby/internal/core/rtypeddata.h
monitor.o: $(hdrdir)/ruby/internal/ctype.h
monitor.o: $(hdrdir)/ruby/internal/dllexport.h
monitor.o: $(hdrdir)/ruby/internal/dosish.h
monitor.o: $(hdrdir)/ruby/internal/error.h
monitor.o: $(hdrdir)/ruby/internal/eval.h
monitor.o: $(hdrdir)/ruby/internal/event.h
monitor.o: $(hdrdir)/ruby/internal/fl_type.h
monitor.o: $(hdrdir)/ruby/internal/gc.h
monitor.o: $(hdrdir)/ruby/internal/glob.h
monitor.o: $(hdrdir)/ruby/internal/globals.h
monitor.o: $(hdrdir)/ruby/internal/has/attribute.h
monitor.o: $(hdrdir)/ruby/internal/has/builtin.h
monitor.o: $(hdrdir)/ruby/internal/has/c_attribute.h
monitor.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h
monitor.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h
monitor.o: $(hdrdir)/ruby/internal/has/extension.h
monitor.o: $(hdrdir)/ruby/internal/has/feature.h
monitor.o: $(hdrdir)/ruby/internal/has/warning.h
monitor.o: $(hdrdir)/ruby/internal/intern/array.h
monitor.o: $(hdrdir)/ruby/internal/intern/bignum.h
monitor.o: $(hdrdir)/ruby/internal/intern/class.h
monitor.o: $(hdrdir)/ruby/internal/intern/compar.h
monitor.o: $(hdrdir)/ruby/internal/intern/complex.h
monitor.o: $(hdrdir)/ruby/internal/intern/cont.h
monitor.o: $(hdrdir)/ruby/internal/intern/dir.h
monitor.o: $(hdrdir)/ruby/internal/intern/enum.h
monitor.o: $(hdrdir)/ruby/internal/intern/enumerator.h
monitor.o: $(hdrdir)/ruby/internal/intern/error.h
monitor.o: $(hdrdir)/ruby/internal/intern/eval.h
monitor.o: $(hdrdir)/ruby/internal/intern/file.h
monitor.o: $(hdrdir)/ruby/internal/intern/hash.h
monitor.o: $(hdrdir)/ruby/internal/intern/io.h
monitor.o: $(hdrdir)/ruby/internal/intern/load.h
monitor.o: $(hdrdir)/ruby/internal/intern/marshal.h
monitor.o: $(hdrdir)/ruby/internal/intern/numeric.h
monitor.o: $(hdrdir)/ruby/internal/intern/object.h
monitor.o: $(hdrdir)/ruby/internal/intern/parse.h
monitor.o: $(hdrdir)/ruby/internal/intern/proc.h
monitor.o: $(hdrdir)/ruby/internal/intern/process.h
monitor.o: $(hdrdir)/ruby/internal/intern/random.h
monitor.o: $(hdrdir)/ruby/internal/intern/range.h
monitor.o: $(hdrdir)/ruby/internal/intern/rational.h
monitor.o: $(hdrdir)/ruby/internal/intern/re.h
monitor.o: $(hdrdir)/ruby/internal/intern/ruby.h
monitor.o: $(hdrdir)/ruby/internal/intern/select.h
monitor.o: $(hdrdir)/ruby/internal/intern/select/largesize.h
monitor.o: $(hdrdir)/ruby/internal/intern/set.h
monitor.o: $(hdrdir)/ruby/internal/intern/signal.h
monitor.o: $(hdrdir)/ruby/internal/intern/sprintf.h
monitor.o: $(hdrdir)/ruby/internal/intern/string.h
monitor.o: $(hdrdir)/ruby/internal/intern/struct.h
monitor.o: $(hdrdir)/ruby/internal/intern/thread.h
monitor.o: $(hdrdir)/ruby/internal/intern/time.h
monitor.o: $(hdrdir)/ruby/internal/intern/variable.h
monitor.o: $(hdrdir)/ruby/internal/intern/vm.h
monitor.o: $(hdrdir)/ruby/internal/interpreter.h
monitor.o: $(hdrdir)/ruby/internal/iterator.h
monitor.o: $(hdrdir)/ruby/internal/memory.h
monitor.o: $(hdrdir)/ruby/internal/method.h
monitor.o: $(hdrdir)/ruby/internal/module.h
monitor.o: $(hdrdir)/ruby/internal/newobj.h
monitor.o: $(hdrdir)/ruby/internal/scan_args.h
monitor.o: $(hdrdir)/ruby/internal/special_consts.h
monitor.o: $(hdrdir)/ruby/internal/static_assert.h
monitor.o: $(hdrdir)/ruby/internal/stdalign.h
monitor.o: $(hdrdir)/ruby/internal/stdbool.h
monitor.o: $(hdrdir)/ruby/internal/stdckdint.h
monitor.o: $(hdrdir)/ruby/internal/symbol.h
monitor.o: $(hdrdir)/ruby/internal/value.h
monitor.o: $(hdrdir)/ruby/internal/value_type.h
monitor.o: $(hdrdir)/ruby/internal/variable.h
monitor.o: $(hdrdir)/ruby/internal/warning_push.h
monitor.o: $(hdrdir)/ruby/internal/xmalloc.h
monitor.o: $(hdrdir)/ruby/missing.h
monitor.o: $(hdrdir)/ruby/ruby.h
monitor.o: $(hdrdir)/ruby/st.h
monitor.o: $(hdrdir)/ruby/subst.h
monitor.o: monitor.c
# AUTOGENERATED DEPENDENCIES END

View File

@ -1,2 +0,0 @@
require 'mkmf'
create_makefile('monitor')

View File

@ -1,301 +0,0 @@
#include "ruby/ruby.h"
/* Thread::Monitor */
struct rb_monitor {
long count;
VALUE owner;
VALUE mutex;
};
static void
monitor_mark(void *ptr)
{
struct rb_monitor *mc = ptr;
rb_gc_mark_movable(mc->owner);
rb_gc_mark_movable(mc->mutex);
}
static void
monitor_compact(void *ptr)
{
struct rb_monitor *mc = ptr;
mc->owner = rb_gc_location(mc->owner);
mc->mutex = rb_gc_location(mc->mutex);
}
static const rb_data_type_t monitor_data_type = {
.wrap_struct_name = "monitor",
.function = {
.dmark = monitor_mark,
.dfree = RUBY_TYPED_DEFAULT_FREE,
.dsize = NULL, // Fully embeded
.dcompact = monitor_compact,
},
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
};
static VALUE
monitor_alloc(VALUE klass)
{
struct rb_monitor *mc;
VALUE obj;
obj = TypedData_Make_Struct(klass, struct rb_monitor, &monitor_data_type, mc);
RB_OBJ_WRITE(obj, &mc->mutex, rb_mutex_new());
RB_OBJ_WRITE(obj, &mc->owner, Qnil);
mc->count = 0;
return obj;
}
static struct rb_monitor *
monitor_ptr(VALUE monitor)
{
struct rb_monitor *mc;
TypedData_Get_Struct(monitor, struct rb_monitor, &monitor_data_type, mc);
return mc;
}
static bool
mc_owner_p(struct rb_monitor *mc, VALUE current_fiber)
{
return mc->owner == current_fiber;
}
/*
* call-seq:
* try_enter -> true or false
*
* Attempts to enter exclusive section. Returns +false+ if lock fails.
*/
static VALUE
monitor_try_enter(VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
VALUE current_fiber = rb_fiber_current();
if (!mc_owner_p(mc, current_fiber)) {
if (!rb_mutex_trylock(mc->mutex)) {
return Qfalse;
}
RB_OBJ_WRITE(monitor, &mc->owner, current_fiber);
mc->count = 0;
}
mc->count += 1;
return Qtrue;
}
struct monitor_args {
VALUE monitor;
struct rb_monitor *mc;
VALUE current_fiber;
};
static inline void
monitor_args_init(struct monitor_args *args, VALUE monitor)
{
args->monitor = monitor;
args->mc = monitor_ptr(monitor);
args->current_fiber = rb_fiber_current();
}
static void
monitor_enter0(struct monitor_args *args)
{
if (!mc_owner_p(args->mc, args->current_fiber)) {
rb_mutex_lock(args->mc->mutex);
RB_OBJ_WRITE(args->monitor, &args->mc->owner, args->current_fiber);
args->mc->count = 0;
}
args->mc->count++;
}
/*
* call-seq:
* enter -> nil
*
* Enters exclusive section.
*/
static VALUE
monitor_enter(VALUE monitor)
{
struct monitor_args args;
monitor_args_init(&args, monitor);
monitor_enter0(&args);
return Qnil;
}
static inline void
monitor_check_owner0(struct monitor_args *args)
{
if (!mc_owner_p(args->mc, args->current_fiber)) {
rb_raise(rb_eThreadError, "current fiber not owner");
}
}
/* :nodoc: */
static VALUE
monitor_check_owner(VALUE monitor)
{
struct monitor_args args;
monitor_args_init(&args, monitor);
monitor_check_owner0(&args);
return Qnil;
}
static void
monitor_exit0(struct monitor_args *args)
{
monitor_check_owner0(args);
if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count);
args->mc->count--;
if (args->mc->count == 0) {
RB_OBJ_WRITE(args->monitor, &args->mc->owner, Qnil);
rb_mutex_unlock(args->mc->mutex);
}
}
/*
* call-seq:
* exit -> nil
*
* Leaves exclusive section.
*/
static VALUE
monitor_exit(VALUE monitor)
{
struct monitor_args args;
monitor_args_init(&args, monitor);
monitor_exit0(&args);
return Qnil;
}
/* :nodoc: */
static VALUE
monitor_locked_p(VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
return rb_mutex_locked_p(mc->mutex);
}
/* :nodoc: */
static VALUE
monitor_owned_p(VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
return rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc, rb_fiber_current()) ? Qtrue : Qfalse;
}
static VALUE
monitor_exit_for_cond(VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
long cnt = mc->count;
RB_OBJ_WRITE(monitor, &mc->owner, Qnil);
mc->count = 0;
return LONG2NUM(cnt);
}
struct wait_for_cond_data {
VALUE monitor;
VALUE cond;
VALUE timeout;
VALUE count;
};
static VALUE
monitor_wait_for_cond_body(VALUE v)
{
struct wait_for_cond_data *data = (struct wait_for_cond_data *)v;
struct rb_monitor *mc = monitor_ptr(data->monitor);
// cond.wait(monitor.mutex, timeout)
VALUE signaled = rb_funcall(data->cond, rb_intern("wait"), 2, mc->mutex, data->timeout);
return RTEST(signaled) ? Qtrue : Qfalse;
}
static VALUE
monitor_enter_for_cond(VALUE v)
{
// assert(rb_mutex_owned_p(mc->mutex) == Qtrue)
// but rb_mutex_owned_p is not exported...
struct wait_for_cond_data *data = (struct wait_for_cond_data *)v;
struct rb_monitor *mc = monitor_ptr(data->monitor);
RB_OBJ_WRITE(data->monitor, &mc->owner, rb_fiber_current());
mc->count = NUM2LONG(data->count);
return Qnil;
}
/* :nodoc: */
static VALUE
monitor_wait_for_cond(VALUE monitor, VALUE cond, VALUE timeout)
{
VALUE count = monitor_exit_for_cond(monitor);
struct wait_for_cond_data data = {
monitor,
cond,
timeout,
count,
};
return rb_ensure(monitor_wait_for_cond_body, (VALUE)&data,
monitor_enter_for_cond, (VALUE)&data);
}
static VALUE
monitor_sync_body(VALUE monitor)
{
return rb_yield_values(0);
}
static VALUE
monitor_sync_ensure(VALUE v_args)
{
monitor_exit0((struct monitor_args *)v_args);
return Qnil;
}
/*
* call-seq:
* synchronize { } -> result of the block
*
* Enters exclusive section and executes the block. Leaves the exclusive
* section automatically when the block exits. See example under
* +MonitorMixin+.
*/
static VALUE
monitor_synchronize(VALUE monitor)
{
struct monitor_args args;
monitor_args_init(&args, monitor);
monitor_enter0(&args);
return rb_ensure(monitor_sync_body, (VALUE)&args, monitor_sync_ensure, (VALUE)&args);
}
void
Init_monitor(void)
{
#ifdef HAVE_RB_EXT_RACTOR_SAFE
rb_ext_ractor_safe(true);
#endif
VALUE rb_cMonitor = rb_define_class("Monitor", rb_cObject);
rb_define_alloc_func(rb_cMonitor, monitor_alloc);
rb_define_method(rb_cMonitor, "try_enter", monitor_try_enter, 0);
rb_define_method(rb_cMonitor, "enter", monitor_enter, 0);
rb_define_method(rb_cMonitor, "exit", monitor_exit, 0);
rb_define_method(rb_cMonitor, "synchronize", monitor_synchronize, 0);
/* internal methods for MonitorMixin */
rb_define_method(rb_cMonitor, "mon_locked?", monitor_locked_p, 0);
rb_define_method(rb_cMonitor, "mon_check_owner", monitor_check_owner, 0);
rb_define_method(rb_cMonitor, "mon_owned?", monitor_owned_p, 0);
/* internal methods for MonitorMixin::ConditionVariable */
rb_define_method(rb_cMonitor, "wait_for_cond", monitor_wait_for_cond, 2);
}

View File

@ -6,9 +6,6 @@
# This library is distributed under the terms of the Ruby license.
# You can freely distribute/modify this library.
#
require 'monitor.so'
#
# In concurrent programming, a monitor is an object or module intended to be
# used safely by more than one thread. The defining characteristic of a
@ -89,65 +86,14 @@ require 'monitor.so'
# MonitorMixin module.
#
module MonitorMixin
ConditionVariable = Monitor::ConditionVariable # :nodoc:
#
# FIXME: This isn't documented in Nutshell.
#
# Since MonitorMixin.new_cond returns a ConditionVariable, and the example
# above calls while_wait and signal, this class should be documented.
#
class ConditionVariable
#
# Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
#
# If +timeout+ is given, this method returns after +timeout+ seconds passed,
# even if no other thread doesn't signal.
#
def wait(timeout = nil)
@monitor.mon_check_owner
@monitor.wait_for_cond(@cond, timeout)
end
#
# Calls wait repeatedly while the given block yields a truthy value.
#
def wait_while
while yield
wait
end
end
#
# Calls wait repeatedly until the given block yields a truthy value.
#
def wait_until
until yield
wait
end
end
#
# Wakes up the first thread in line waiting for this lock.
#
def signal
@monitor.mon_check_owner
@cond.signal
end
#
# Wakes up all threads waiting for this lock.
#
def broadcast
@monitor.mon_check_owner
@cond.broadcast
end
private
def initialize(monitor) # :nodoc:
@monitor = monitor
@cond = Thread::ConditionVariable.new
end
end
def self.extend_object(obj) # :nodoc:
super(obj)
@ -245,26 +191,7 @@ module MonitorMixin
end
end
# Use the Monitor class when you want to have a lock object for blocks with
# mutual exclusion.
#
# require 'monitor'
#
# lock = Monitor.new
# lock.synchronize do
# # exclusive access
# end
#
class Monitor
#
# Creates a new MonitorMixin::ConditionVariable associated with the
# Monitor object.
#
def new_cond
::MonitorMixin::ConditionVariable.new(self)
end
# for compatibility
class Monitor # :nodoc:
alias try_mon_enter try_enter
alias mon_try_enter try_enter
alias mon_enter enter

View File

@ -21,13 +21,16 @@ describe "Kernel#require" do
provided << "set"
provided << "pathname"
end
ruby_version_is "4.1" do
provided << "monitor"
end
it "#{provided.join(', ')} are already required" do
out = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems --disable-did-you-mean')
features = out.lines.map { |line| File.basename(line.chomp, '.*') }
# Ignore CRuby internals
features -= %w[encdb transdb windows_1252 windows_31j]
features -= %w[encdb transdb windows_1252 windows_31]
features.reject! { |feature| feature.end_with?('-fake') }
features.sort.should == provided.sort
@ -37,6 +40,10 @@ describe "Kernel#require" do
requires = requires.map { |f| f == "pathname" ? "pathname.so" : f }
end
ruby_version_is "4.1" do
requires = requires.map { |f| f == "monitor" ? "monitor.so" : f }
end
code = requires.map { |f| "puts require #{f.inspect}\n" }.join
required = ruby_exe(code, options: '--disable-gems')
required.should == "false\n" * requires.size

View File

@ -1261,6 +1261,263 @@ rb_condvar_broadcast(rb_execution_context_t *ec, VALUE self)
return self;
}
/* Thread::Monitor */
struct rb_monitor {
long count;
rb_serial_t ec_serial;
VALUE mutex;
};
static void
monitor_mark(void *ptr)
{
struct rb_monitor *mc = ptr;
rb_gc_mark_movable(mc->mutex);
}
static void
monitor_compact(void *ptr)
{
struct rb_monitor *mc = ptr;
mc->mutex = rb_gc_location(mc->mutex);
}
static const rb_data_type_t monitor_data_type = {
.wrap_struct_name = "monitor",
.function = {
.dmark = monitor_mark,
.dfree = RUBY_TYPED_DEFAULT_FREE,
.dsize = NULL, // Fully embeded
.dcompact = monitor_compact,
},
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
};
static VALUE
monitor_alloc(VALUE klass)
{
struct rb_monitor *mc;
VALUE obj;
obj = TypedData_Make_Struct(klass, struct rb_monitor, &monitor_data_type, mc);
RB_OBJ_WRITE(obj, &mc->mutex, rb_mutex_new());
mc->ec_serial = 0;
mc->count = 0;
return obj;
}
static struct rb_monitor *
monitor_ptr(VALUE monitor)
{
struct rb_monitor *mc;
TypedData_Get_Struct(monitor, struct rb_monitor, &monitor_data_type, mc);
return mc;
}
static bool
mc_owner_p(struct rb_monitor *mc, rb_serial_t current_fiber_serial)
{
return mc->ec_serial == current_fiber_serial;
}
static VALUE
rb_monitor_try_enter(rb_execution_context_t *ec, VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
rb_serial_t current_fiber_serial = rb_ec_serial(ec);
if (!mc_owner_p(mc, current_fiber_serial)) {
if (!rb_mut_trylock(ec, mc->mutex)) {
return Qfalse;
}
mc->ec_serial = current_fiber_serial;
mc->count = 0;
}
mc->count += 1;
return Qtrue;
}
struct monitor_args {
VALUE monitor;
struct rb_monitor *mc;
rb_serial_t current_fiber_serial;
rb_execution_context_t *ec;
};
static void
monitor_enter0(struct monitor_args *args)
{
if (!mc_owner_p(args->mc, args->current_fiber_serial)) {
struct mutex_args mut_args = {
.self = args->mc->mutex,
.mutex = mutex_ptr(args->mc->mutex),
.ec= args->ec,
};
do_mutex_lock(&mut_args, 1);
args->mc->ec_serial = args->current_fiber_serial;
args->mc->count = 0;
}
args->mc->count++;
}
static VALUE
rb_monitor_enter(rb_execution_context_t *ec, VALUE monitor)
{
struct monitor_args args = {
.monitor = monitor,
.mc = monitor_ptr(monitor),
.ec = ec,
.current_fiber_serial = rb_ec_serial(ec),
};
monitor_enter0(&args);
return Qnil;
}
static inline void
monitor_check_owner0(struct monitor_args *args)
{
if (!mc_owner_p(args->mc, args->current_fiber_serial)) {
rb_raise(rb_eThreadError, "current fiber not owner");
}
}
static VALUE
rb_monitor_check_owner(rb_execution_context_t *ec, VALUE monitor)
{
struct monitor_args args = {
.monitor = monitor,
.mc = monitor_ptr(monitor),
.ec = ec,
.current_fiber_serial = rb_ec_serial(ec),
};
monitor_check_owner0(&args);
return Qnil;
}
static void
monitor_exit0(struct monitor_args *args)
{
monitor_check_owner0(args);
if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count);
args->mc->count--;
if (args->mc->count == 0) {
args->mc->ec_serial = 0;
struct mutex_args mut_args = {
.self = args->mc->mutex,
.mutex = mutex_ptr(args->mc->mutex),
.ec= args->ec,
};
do_mutex_unlock(&mut_args);
}
}
static VALUE
rb_monitor_exit(rb_execution_context_t *ec, VALUE monitor)
{
struct monitor_args args = {
.monitor = monitor,
.mc = monitor_ptr(monitor),
.ec = ec,
.current_fiber_serial = rb_ec_serial(ec),
};
monitor_exit0(&args);
return Qnil;
}
static VALUE
rb_monitor_locked_p(rb_execution_context_t *ec, VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
return rb_mutex_locked_p(mc->mutex);
}
static VALUE
rb_monitor_owned_p(rb_execution_context_t *ec, VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
return RBOOL(rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc, rb_ec_serial(ec)));
}
static VALUE
monitor_exit_for_cond(VALUE monitor)
{
struct rb_monitor *mc = monitor_ptr(monitor);
long cnt = mc->count;
mc->ec_serial = 0;
mc->count = 0;
return LONG2NUM(cnt);
}
struct wait_for_cond_data {
VALUE monitor;
VALUE cond;
VALUE timeout;
VALUE count;
};
static VALUE
monitor_wait_for_cond_body(VALUE v)
{
struct wait_for_cond_data *data = (struct wait_for_cond_data *)v;
struct rb_monitor *mc = monitor_ptr(data->monitor);
// cond.wait(monitor.mutex, timeout)
VALUE signaled = rb_funcall(data->cond, rb_intern("wait"), 2, mc->mutex, data->timeout);
return RTEST(signaled) ? Qtrue : Qfalse;
}
static VALUE
monitor_enter_for_cond(VALUE v)
{
// assert(rb_mutex_owned_p(mc->mutex) == Qtrue)
// but rb_mutex_owned_p is not exported...
struct wait_for_cond_data *data = (struct wait_for_cond_data *)v;
struct rb_monitor *mc = monitor_ptr(data->monitor);
mc->ec_serial = rb_ec_serial(GET_EC());
mc->count = NUM2LONG(data->count);
return Qnil;
}
static VALUE
rb_monitor_wait_for_cond(rb_execution_context_t *ec, VALUE monitor, VALUE cond, VALUE timeout)
{
VALUE count = monitor_exit_for_cond(monitor);
struct wait_for_cond_data data = {
monitor,
cond,
timeout,
count,
};
return rb_ensure(monitor_wait_for_cond_body, (VALUE)&data,
monitor_enter_for_cond, (VALUE)&data);
}
static VALUE
monitor_sync_ensure(VALUE v_args)
{
monitor_exit0((struct monitor_args *)v_args);
return Qnil;
}
static VALUE
rb_monitor_synchronize(rb_execution_context_t *ec, VALUE monitor)
{
struct monitor_args args = {
.monitor = monitor,
.mc = monitor_ptr(monitor),
.ec = ec,
.current_fiber_serial = rb_ec_serial(ec),
};
monitor_enter0(&args);
return rb_ec_ensure(ec, do_ec_yield, (VALUE)ec, monitor_sync_ensure, (VALUE)&args);
}
static void
Init_thread_sync(void)
{
@ -1283,6 +1540,11 @@ Init_thread_sync(void)
id_sleep = rb_intern("sleep");
/* Monitor */
VALUE rb_cMonitor = rb_define_class_id_under_no_pin(rb_cThread, rb_intern("Monitor"), rb_cObject);
rb_define_alloc_func(rb_cMonitor, monitor_alloc);
rb_provide("monitor.so");
rb_provide("thread.rb");
}

View File

@ -529,9 +529,187 @@ class Thread
Primitive.rb_condvar_wait(mutex, timeout)
end
end
# Use the Monitor class when you want to have a lock object for blocks with
# mutual exclusion.
#
# lock = Monitor.new
# lock.synchronize do
# # exclusive access
# end
#
# Contrary to Mutex, Monitor is reentrant:
#
# lock = Monitor.new
# lock.synchronize do
# lock.synchronize do
# # exclusive access
# end
# end
class Monitor
# call-seq:
# synchronize { } -> result of the block
#
# Enters exclusive section and executes the block. Leaves the exclusive
# section automatically when the block exits. See example under
# +MonitorMixin+.
def synchronize(&)
Primitive.rb_monitor_synchronize
end
# call-seq:
# try_enter -> true or false
#
# Attempts to enter exclusive section. Returns +false+ if lock fails.
def try_enter
Primitive.rb_monitor_try_enter
end
# call-seq:
# enter -> nil
#
# Enters exclusive section.
def enter
Primitive.rb_monitor_enter
end
# call-seq:
# exit -> nil
#
# Leaves exclusive section.
def exit
Primitive.rb_monitor_exit
end
# internal methods for MonitorMixin
def mon_check_owner # :nodoc:
Primitive.rb_monitor_check_owner
end
def mon_locked? # :nodoc:
Primitive.rb_monitor_locked_p
end
def mon_owned? # :nodoc:
Primitive.rb_monitor_owned_p
end
# internal methods for MonitorMixin::ConditionVariable
def wait_for_cond(cond, timeout) # :nodoc:
Primitive.rb_monitor_wait_for_cond(cond, timeout)
end
# Creates a new Monitor::ConditionVariable associated with the
# Monitor object.
#
def new_cond
ConditionVariable.new(self)
end
# Condition variables, allow to suspend the current thread while in
# the middle of a critical section until a condition is met, such as
# a resource being available.
#
# Example:
#
# monitor = Thread::Monitor.new
#
# resource_available = false
# condvar = monitor.new_cond
#
# a1 = Thread.new {
# # Thread 'a1' waits for the resource to become available and consumes
# # the resource.
# monitor.synchronize {
# condvar.wait_until { resource_available }
# # After the loop, 'resource_available' is guaranteed to be true.
#
# resource_available = false
# puts "a1 consumed the resource"
# }
# }
#
# a2 = Thread.new {
# # Thread 'a2' behaves like 'a1'.
# monitor.synchronize {
# condvar.wait_until { resource_available }
# resource_available = false
# puts "a2 consumed the resource"
# }
# }
#
# b = Thread.new {
# # Thread 'b' periodically makes the resource available.
# loop {
# monitor.synchronize {
# resource_available = true
#
# # Notify one waiting thread if any. It is possible that neither
# # 'a1' nor 'a2 is waiting on 'condvar' at this moment. That's OK.
# condvar.signal
# }
# sleep 1
# }
# }
#
# # Eventually both 'a1' and 'a2' will have their resources, albeit in an
# # unspecified order.
# [a1, a2].each {|th| th.join}
class ConditionVariable
def initialize(monitor) # :nodoc:
@monitor = monitor
@cond = Thread::ConditionVariable.new
end
# Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
#
# If +timeout+ is given, this method returns after +timeout+ seconds passed,
# even if no other thread doesn't signal.
#
def wait(timeout = nil)
@monitor.mon_check_owner
@monitor.wait_for_cond(@cond, timeout)
end
#
# Calls wait repeatedly while the given block yields a truthy value.
#
def wait_while
while yield
wait
end
end
#
# Calls wait repeatedly until the given block yields a truthy value.
#
def wait_until
until yield
wait
end
end
#
# Wakes up the first thread in line waiting for this lock.
#
def signal
@monitor.mon_check_owner
@cond.signal
end
#
# Wakes up all threads waiting for this lock.
#
def broadcast
@monitor.mon_check_owner
@cond.broadcast
end
end
end
end
Mutex = Thread::Mutex
ConditionVariable = Thread::ConditionVariable
Queue = Thread::Queue
SizedQueue = Thread::SizedQueue
Mutex = Thread::Mutex # :nodoc:
Monitor = Thread::Monitor # :nodoc:
ConditionVariable = Thread::ConditionVariable # :nodoc:
Queue = Thread::Queue # :nodoc:
SizedQueue = Thread::SizedQueue # :nodoc: