diff options
Diffstat (limited to 'ext/monitor')
| -rw-r--r-- | ext/monitor/depend | 162 | ||||
| -rw-r--r-- | ext/monitor/extconf.rb | 2 | ||||
| -rw-r--r-- | ext/monitor/lib/monitor.rb | 289 | ||||
| -rw-r--r-- | ext/monitor/monitor.c | 301 |
4 files changed, 754 insertions, 0 deletions
diff --git a/ext/monitor/depend b/ext/monitor/depend new file mode 100644 index 0000000000..0c7d54afc8 --- /dev/null +++ b/ext/monitor/depend @@ -0,0 +1,162 @@ +# 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 new file mode 100644 index 0000000000..78c53fa0c5 --- /dev/null +++ b/ext/monitor/extconf.rb @@ -0,0 +1,2 @@ +require 'mkmf' +create_makefile('monitor') diff --git a/ext/monitor/lib/monitor.rb b/ext/monitor/lib/monitor.rb new file mode 100644 index 0000000000..82d0a75c56 --- /dev/null +++ b/ext/monitor/lib/monitor.rb @@ -0,0 +1,289 @@ +# frozen_string_literal: false +# = monitor.rb +# +# Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org> +# +# 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 +# monitor is that its methods are executed with mutual exclusion. That is, at +# each point in time, at most one thread may be executing any of its methods. +# This mutual exclusion greatly simplifies reasoning about the implementation +# of monitors compared to reasoning about parallel code that updates a data +# structure. +# +# You can read more about the general principles on the Wikipedia page for +# Monitors[https://en.wikipedia.org/wiki/Monitor_%28synchronization%29]. +# +# == Examples +# +# === Simple object.extend +# +# require 'monitor.rb' +# +# buf = [] +# buf.extend(MonitorMixin) +# empty_cond = buf.new_cond +# +# # consumer +# Thread.start do +# loop do +# buf.synchronize do +# empty_cond.wait_while { buf.empty? } +# print buf.shift +# end +# end +# end +# +# # producer +# while line = ARGF.gets +# buf.synchronize do +# buf.push(line) +# empty_cond.signal +# end +# end +# +# The consumer thread waits for the producer thread to push a line to buf +# while <tt>buf.empty?</tt>. The producer thread (main thread) reads a +# line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt> +# to notify the consumer thread of new data. +# +# === Simple Class include +# +# require 'monitor' +# +# class SynchronizedArray < Array +# +# include MonitorMixin +# +# def initialize(*args) +# super(*args) +# end +# +# alias :old_shift :shift +# alias :old_unshift :unshift +# +# def shift(n=1) +# self.synchronize do +# self.old_shift(n) +# end +# end +# +# def unshift(item) +# self.synchronize do +# self.old_unshift(item) +# end +# end +# +# # other methods ... +# end +# +# +SynchronizedArray+ implements an Array with synchronized access to items. +# This Class is implemented as subclass of Array which includes the +# MonitorMixin module. +# +module MonitorMixin + # + # 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) + obj.__send__(:mon_initialize) + end + + # + # Attempts to enter exclusive section. Returns +false+ if lock fails. + # + def mon_try_enter + @mon_data.try_enter + end + # For backward compatibility + alias try_mon_enter mon_try_enter + + # + # Enters exclusive section. + # + def mon_enter + @mon_data.enter + end + + # + # Leaves exclusive section. + # + def mon_exit + mon_check_owner + @mon_data.exit + end + + # + # Returns true if this monitor is locked by any thread + # + def mon_locked? + @mon_data.mon_locked? + end + + # + # Returns true if this monitor is locked by current thread. + # + def mon_owned? + @mon_data.mon_owned? + end + + # + # Enters exclusive section and executes the block. Leaves the exclusive + # section automatically when the block exits. See example under + # +MonitorMixin+. + # + def mon_synchronize(&b) + @mon_data.synchronize(&b) + end + alias synchronize mon_synchronize + + # + # Creates a new MonitorMixin::ConditionVariable associated with the + # Monitor object. + # + def new_cond + unless defined?(@mon_data) + mon_initialize + @mon_initialized_by_new_cond = true + end + return ConditionVariable.new(@mon_data) + end + + private + + # Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead + # of this constructor. Have look at the examples above to understand how to + # use this module. + def initialize(...) + super + mon_initialize + end + + # Initializes the MonitorMixin after being included in a class or when an + # object has been extended with the MonitorMixin + def mon_initialize + if defined?(@mon_data) + if defined?(@mon_initialized_by_new_cond) + return # already initialized. + elsif @mon_data_owner_object_id == self.object_id + raise ThreadError, "already initialized" + end + end + @mon_data = ::Monitor.new + @mon_data_owner_object_id = self.object_id + end + + # Ensures that the MonitorMixin is owned by the current thread, + # otherwise raises an exception. + def mon_check_owner + @mon_data.mon_check_owner + 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 + alias try_mon_enter try_enter + alias mon_try_enter try_enter + alias mon_enter enter + alias mon_exit exit + alias mon_synchronize synchronize +end + +# Documentation comments: +# - All documentation comes from Nutshell. +# - MonitorMixin.new_cond appears in the example, but is not documented in +# Nutshell. +# - All the internals (internal modules Accessible and Initializable, class +# ConditionVariable) appear in RDoc. It might be good to hide them, by +# making them private, or marking them :nodoc:, etc. +# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but +# not synchronize. +# - mon_owner is in Nutshell, but appears as an accessor in a separate module +# here, so is hard/impossible to RDoc. Some other useful accessors +# (mon_count and some queue stuff) are also in this module, and don't appear +# directly in the RDoc output. +# - in short, it may be worth changing the code layout in this file to make the +# documentation easier diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c new file mode 100644 index 0000000000..c43751c4e2 --- /dev/null +++ b/ext/monitor/monitor.c @@ -0,0 +1,301 @@ +#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); +} |
