summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/monitor/depend162
-rw-r--r--ext/monitor/extconf.rb2
-rw-r--r--ext/monitor/monitor.c301
-rw-r--r--lib/monitor.rb (renamed from ext/monitor/lib/monitor.rb)79
-rw-r--r--spec/ruby/core/kernel/require_spec.rb9
-rw-r--r--thread_sync.c262
-rw-r--r--thread_sync.rb186
7 files changed, 455 insertions, 546 deletions
diff --git a/ext/monitor/depend b/ext/monitor/depend
deleted file mode 100644
index 0c7d54afc8..0000000000
--- a/ext/monitor/depend
+++ /dev/null
@@ -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
diff --git a/ext/monitor/extconf.rb b/ext/monitor/extconf.rb
deleted file mode 100644
index 78c53fa0c5..0000000000
--- a/ext/monitor/extconf.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require 'mkmf'
-create_makefile('monitor')
diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c
deleted file mode 100644
index c43751c4e2..0000000000
--- a/ext/monitor/monitor.c
+++ /dev/null
@@ -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);
-}
diff --git a/ext/monitor/lib/monitor.rb b/lib/monitor.rb
index 82d0a75c56..21329a5de7 100644
--- a/ext/monitor/lib/monitor.rb
+++ b/lib/monitor.rb
@@ -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
diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb
index 60d17242fe..da2f48fb61 100644
--- a/spec/ruby/core/kernel/require_spec.rb
+++ b/spec/ruby/core/kernel/require_spec.rb
@@ -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
diff --git a/thread_sync.c b/thread_sync.c
index fa6a60ab62..cf4e3843ff 100644
--- a/thread_sync.c
+++ b/thread_sync.c
@@ -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");
}
diff --git a/thread_sync.rb b/thread_sync.rb
index 398c0d02b7..c9d37772d7 100644
--- a/thread_sync.rb
+++ b/thread_sync.rb
@@ -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: