From 829c0e01d58cb35b52e5fa31d2b0c240cef77fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20S=C3=A1nchez=20Yeste?= Date: Thu, 26 Mar 2026 21:13:44 +0100 Subject: [PATCH 1/4] Add opt-in Ractor-safe InstanceRegistry via RICE_RACTOR_SAFE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InstanceRegistry is the only Rice registry that performs writes at runtime (lookup, add, remove, clear). The other registries (TypeRegistry, NativeRegistry, HandlerRegistry, ModuleRegistry) are written once during Init and are read-only at runtime. When RICE_RACTOR_SAFE is defined, a std::recursive_mutex protects all mutable InstanceRegistry operations. This allows C extensions built with Rice to be used safely from multiple Ruby Ractors. Without the define, the generated code is identical to the current Rice — zero impact on existing projects. Usage in extconf.rb: $CXXFLAGS << " -DRICE_RACTOR_SAFE" --- rice/detail/InstanceRegistry.hpp | 7 +++++++ rice/detail/InstanceRegistry.ipp | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/rice/detail/InstanceRegistry.hpp b/rice/detail/InstanceRegistry.hpp index dac0f29c..92bea9e0 100644 --- a/rice/detail/InstanceRegistry.hpp +++ b/rice/detail/InstanceRegistry.hpp @@ -3,6 +3,10 @@ #include +#ifdef RICE_RACTOR_SAFE +#include +#endif + namespace Rice::detail { class InstanceRegistry @@ -30,6 +34,9 @@ namespace Rice::detail private: bool shouldTrack(bool isOwner) const; +#ifdef RICE_RACTOR_SAFE + std::recursive_mutex mutex_; +#endif std::map objectMap_; }; } // namespace Rice::detail diff --git a/rice/detail/InstanceRegistry.ipp b/rice/detail/InstanceRegistry.ipp index a159df66..946cfbf7 100644 --- a/rice/detail/InstanceRegistry.ipp +++ b/rice/detail/InstanceRegistry.ipp @@ -5,6 +5,10 @@ namespace Rice::detail template inline VALUE InstanceRegistry::lookup(T* cppInstance, bool isOwner) { +#ifdef RICE_RACTOR_SAFE + std::lock_guard lock(mutex_); +#endif + if (!this->shouldTrack(isOwner)) { return Qnil; @@ -17,6 +21,10 @@ namespace Rice::detail template inline void InstanceRegistry::add(T* cppInstance, VALUE rubyInstance, bool isOwner) { +#ifdef RICE_RACTOR_SAFE + std::lock_guard lock(mutex_); +#endif + if (!this->shouldTrack(isOwner)) { return; @@ -27,11 +35,17 @@ namespace Rice::detail inline void InstanceRegistry::remove(void* cppInstance) { +#ifdef RICE_RACTOR_SAFE + std::lock_guard lock(mutex_); +#endif this->objectMap_.erase(cppInstance); } inline void InstanceRegistry::clear() { +#ifdef RICE_RACTOR_SAFE + std::lock_guard lock(mutex_); +#endif this->objectMap_.clear(); } From cac8836b94de36dc33fdc109c4f3f7394852e178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20S=C3=A1nchez=20Yeste?= Date: Thu, 26 Mar 2026 21:31:33 +0100 Subject: [PATCH 2/4] Add Ractor safety tests (Minitest) A minimal Rice extension (Counter class) compiled with -DRICE_RACTOR_SAFE, plus 5 Minitest tests verifying: - Single Ractor creating and using Rice-wrapped objects - Sequential Ractors with rapid handoff - Concurrent Ractors (would segfault without the mutex) - Many objects in a Ractor (stresses InstanceRegistry add/lookup) - Main thread and Ractor using Rice simultaneously The tests auto-build the extension on first run. Located in test/ruby/ matching the existing Rake::TestTask glob. --- test/ruby/ext/extconf.rb | 9 ++ test/ruby/ext/ractor_test_ext.cpp | 54 +++++++++++ test/ruby/test_ractor.rb | 155 ++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 test/ruby/ext/extconf.rb create mode 100644 test/ruby/ext/ractor_test_ext.cpp create mode 100644 test/ruby/test_ractor.rb diff --git a/test/ruby/ext/extconf.rb b/test/ruby/ext/extconf.rb new file mode 100644 index 00000000..f4940d7c --- /dev/null +++ b/test/ruby/ext/extconf.rb @@ -0,0 +1,9 @@ +require "mkmf-rice" + +# Use the local Rice source (with RICE_RACTOR_SAFE support) +rice_dir = File.expand_path("../../../../rice", __dir__) +$INCFLAGS.prepend("-I#{rice_dir} ") + +$CXXFLAGS << " -DRICE_RACTOR_SAFE" + +create_makefile("ractor_test_ext") diff --git a/test/ruby/ext/ractor_test_ext.cpp b/test/ruby/ext/ractor_test_ext.cpp new file mode 100644 index 00000000..52f42e07 --- /dev/null +++ b/test/ruby/ext/ractor_test_ext.cpp @@ -0,0 +1,54 @@ +// Minimal Rice extension for testing Ractor safety. +// Compiled with -DRICE_RACTOR_SAFE to enable mutex-protected InstanceRegistry. + +#include +#include + +#include + +namespace { + class Counter { + public: + Counter(int start) : value_(start) {} + int value() const { return value_; } + void increment() { value_++; } + private: + int value_; + }; + + // Object with an expensive constructor — spends significant time inside + // Rice's InstanceRegistry add/lookup cycle, widening the window for + // concurrent access from multiple Ractors. + class HeavyObject { + public: + HeavyObject(int size) { + data_.resize(size); + for (int i = 0; i < size; i++) { + data_[i] = i * i; + } + } + + int sum() const { + int s = 0; + for (auto v : data_) { s += v; } + return s; + } + + private: + std::vector data_; + }; +} + +extern "C" +void Init_ractor_test_ext() { + rb_ext_ractor_safe(true); + + Rice::define_class("Counter") + .define_constructor(Rice::Constructor()) + .define_method("value", &Counter::value) + .define_method("increment", &Counter::increment); + + Rice::define_class("HeavyObject") + .define_constructor(Rice::Constructor()) + .define_method("sum", &HeavyObject::sum); +} diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb new file mode 100644 index 00000000..55ef6519 --- /dev/null +++ b/test/ruby/test_ractor.rb @@ -0,0 +1,155 @@ +require "minitest/autorun" + +# Build the test extension if needed +ext_dir = File.expand_path("ext", __dir__) +bundle = File.join(ext_dir, "ractor_test_ext.bundle") +so = File.join(ext_dir, "ractor_test_ext.so") + +unless File.exist?(bundle) || File.exist?(so) + Dir.chdir(ext_dir) do + system(RbConfig.ruby, "extconf.rb", exception: true) + system("make", "clean", exception: true) + system("make", exception: true) + end +end + +$LOAD_PATH.unshift(ext_dir) +require "ractor_test_ext" + +class RactorSafetyTest < Minitest::Test + def test_single_ractor + r = Ractor.new do + c = Counter.new(10) + c.increment + c.increment + c.value + end + + assert_equal 12, r.take + end + + def test_sequential_ractors + results = 5.times.map do |i| + r = Ractor.new(i) do |start| + c = Counter.new(start) + c.increment + c.value + end + r.take + end + + assert_equal [1, 2, 3, 4, 5], results + end + + def test_concurrent_ractors + r1 = Ractor.new do + c = Counter.new(100) + c.increment + c.value + end + + r2 = Ractor.new do + c = Counter.new(200) + c.increment + c.value + end + + assert_equal 101, r1.take + assert_equal 201, r2.take + end + + def test_many_objects_in_ractor + r = Ractor.new do + counters = 50.times.map { |i| Counter.new(i) } + counters.each(&:increment) + counters.map(&:value) + end + + result = r.take + assert_equal 50, result.size + assert_equal 50.times.map { |i| i + 1 }, result + end + + def test_main_thread_and_ractor + main_counter = Counter.new(0) + main_counter.increment + + r = Ractor.new do + c = Counter.new(1000) + c.increment + c.value + end + + assert_equal 1, main_counter.value + assert_equal 1001, r.take + end + + # --- Heavy concurrency test --- + # + # Calibrates HeavyObject constructor to take ~1 second, then launches + # two Ractors creating objects simultaneously. Each new() spends ~1s + # inside Rice's InstanceRegistry, making concurrent collisions certain. + # Without RICE_RACTOR_SAFE this would segfault. + + TARGET_SECONDS = 1.0 + OBJECTS_PER_RACTOR = 3 + + def test_concurrent_heavy_objects + size = calibrate_heavy_object_size + + r1 = Ractor.new(size) do |sz| + OBJECTS_PER_RACTOR.times.map { HeavyObject.new(sz).sum } + end + + r2 = Ractor.new(size) do |sz| + OBJECTS_PER_RACTOR.times.map { HeavyObject.new(sz).sum } + end + + results1 = r1.take + results2 = r2.take + + assert_equal OBJECTS_PER_RACTOR, results1.size + assert_equal OBJECTS_PER_RACTOR, results2.size + + # All sums must be identical (same size → same computation) + expected_sum = results1.first + (results1 + results2).each do |s| + assert_equal expected_sum, s + end + end + + private + + # Find the size parameter that makes HeavyObject.new take ~TARGET_SECONDS. + # Uses exponential search then binary refinement. + def calibrate_heavy_object_size + # Exponential search for upper bound + size = 1_000 + loop do + elapsed = measure_seconds { HeavyObject.new(size) } + break if elapsed >= TARGET_SECONDS + size *= 2 + end + + # Binary search between size/2 and size + lo = size / 2 + hi = size + while hi - lo > lo / 10 # ~10% precision is enough + mid = (lo + hi) / 2 + elapsed = measure_seconds { HeavyObject.new(mid) } + if elapsed < TARGET_SECONDS + lo = mid + else + hi = mid + end + end + + (lo + hi) / 2 + end + + def measure_seconds + t = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield + Process.clock_gettime(Process::CLOCK_MONOTONIC) - t + end +end From 0dd214810c0078c046f19eb603a2114236641df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20S=C3=A1nchez=20Yeste?= Date: Thu, 26 Mar 2026 21:55:37 +0100 Subject: [PATCH 3/4] Add negative test: concurrent Ractors crash without RICE_RACTOR_SAFE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A second test extension compiled WITHOUT -DRICE_RACTOR_SAFE proves that concurrent Ractor access to Rice-wrapped objects corrupts the unprotected InstanceRegistry (std::map). The corruption manifests as a hang (infinite loop in the corrupted red-black tree), not always as a segfault. The test runs in a subprocess with a 30s timeout — hang or crash both confirm the bug. This is the counterpart to test_ractor.rb which proves the same workload succeeds WITH RICE_RACTOR_SAFE. --- test/ruby/ext_unsafe/extconf.rb | 10 +++ .../ext_unsafe/ractor_test_unsafe_ext.cpp | 51 +++++++++++ test/ruby/test_ractor.rb | 69 ++++++++++----- test/ruby/test_ractor_unsafe.rb | 85 +++++++++++++++++++ 4 files changed, 193 insertions(+), 22 deletions(-) create mode 100644 test/ruby/ext_unsafe/extconf.rb create mode 100644 test/ruby/ext_unsafe/ractor_test_unsafe_ext.cpp create mode 100644 test/ruby/test_ractor_unsafe.rb diff --git a/test/ruby/ext_unsafe/extconf.rb b/test/ruby/ext_unsafe/extconf.rb new file mode 100644 index 00000000..aa1d94ae --- /dev/null +++ b/test/ruby/ext_unsafe/extconf.rb @@ -0,0 +1,10 @@ +require "mkmf-rice" + +# Use the local Rice source — but WITHOUT RICE_RACTOR_SAFE. +# The InstanceRegistry will have no mutex protection. +rice_dir = File.expand_path("../../../../rice", __dir__) +$INCFLAGS.prepend("-I#{rice_dir} ") + +# Intentionally NO -DRICE_RACTOR_SAFE + +create_makefile("ractor_test_unsafe_ext") diff --git a/test/ruby/ext_unsafe/ractor_test_unsafe_ext.cpp b/test/ruby/ext_unsafe/ractor_test_unsafe_ext.cpp new file mode 100644 index 00000000..e6b649c5 --- /dev/null +++ b/test/ruby/ext_unsafe/ractor_test_unsafe_ext.cpp @@ -0,0 +1,51 @@ +// Same extension as ractor_test_ext but compiled WITHOUT -DRICE_RACTOR_SAFE. +// Used to verify that concurrent Ractor access segfaults without the mutex. + +#include +#include + +#include + +namespace { + class Counter { + public: + Counter(int start) : value_(start) {} + int value() const { return value_; } + void increment() { value_++; } + private: + int value_; + }; + + class HeavyObject { + public: + HeavyObject(int size) { + data_.resize(size); + for (int i = 0; i < size; i++) { + data_[i] = i * i; + } + } + + int sum() const { + int s = 0; + for (auto v : data_) { s += v; } + return s; + } + + private: + std::vector data_; + }; +} + +extern "C" +void Init_ractor_test_unsafe_ext() { + rb_ext_ractor_safe(true); + + Rice::define_class("Counter") + .define_constructor(Rice::Constructor()) + .define_method("value", &Counter::value) + .define_method("increment", &Counter::increment); + + Rice::define_class("HeavyObject") + .define_constructor(Rice::Constructor()) + .define_method("sum", &HeavyObject::sum); +} diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 55ef6519..141663c3 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -84,60 +84,75 @@ def test_main_thread_and_ractor assert_equal 1001, r.take end - # --- Heavy concurrency test --- + # --- Heavy concurrency tests --- # - # Calibrates HeavyObject constructor to take ~1 second, then launches - # two Ractors creating objects simultaneously. Each new() spends ~1s - # inside Rice's InstanceRegistry, making concurrent collisions certain. - # Without RICE_RACTOR_SAFE this would segfault. + # Two strategies to maximize InstanceRegistry contention: + # + # 1. Few heavy objects: each HeavyObject.new takes ~1s, so the thread + # spends a long time inside Rice's wrapping code per object. + # + # 2. Many fast objects: calibrate how many Counter.new fit in ~1s, + # then create that many concurrently — thousands of rapid-fire + # registry writes overlapping between Ractors. - TARGET_SECONDS = 1.0 - OBJECTS_PER_RACTOR = 3 + HEAVY_TARGET_SECONDS = 1.0 + HEAVY_OBJECTS_PER_RACTOR = 3 + FAST_TARGET_SECONDS = 1.0 def test_concurrent_heavy_objects size = calibrate_heavy_object_size r1 = Ractor.new(size) do |sz| - OBJECTS_PER_RACTOR.times.map { HeavyObject.new(sz).sum } + HEAVY_OBJECTS_PER_RACTOR.times.map { HeavyObject.new(sz).sum } end r2 = Ractor.new(size) do |sz| - OBJECTS_PER_RACTOR.times.map { HeavyObject.new(sz).sum } + HEAVY_OBJECTS_PER_RACTOR.times.map { HeavyObject.new(sz).sum } end results1 = r1.take results2 = r2.take - assert_equal OBJECTS_PER_RACTOR, results1.size - assert_equal OBJECTS_PER_RACTOR, results2.size + assert_equal HEAVY_OBJECTS_PER_RACTOR, results1.size + assert_equal HEAVY_OBJECTS_PER_RACTOR, results2.size - # All sums must be identical (same size → same computation) expected_sum = results1.first - (results1 + results2).each do |s| - assert_equal expected_sum, s + (results1 + results2).each { |s| assert_equal expected_sum, s } + end + + def test_concurrent_many_fast_objects + count = calibrate_fast_object_count + + r1 = Ractor.new(count) do |n| + n.times { Counter.new(0) } + :ok + end + + r2 = Ractor.new(count) do |n| + n.times { Counter.new(0) } + :ok end + + assert_equal :ok, r1.take + assert_equal :ok, r2.take end private - # Find the size parameter that makes HeavyObject.new take ~TARGET_SECONDS. - # Uses exponential search then binary refinement. + # Find the size parameter that makes HeavyObject.new take ~1 second. def calibrate_heavy_object_size - # Exponential search for upper bound size = 1_000 loop do elapsed = measure_seconds { HeavyObject.new(size) } - break if elapsed >= TARGET_SECONDS + break if elapsed >= HEAVY_TARGET_SECONDS size *= 2 end - # Binary search between size/2 and size lo = size / 2 hi = size - while hi - lo > lo / 10 # ~10% precision is enough + while hi - lo > lo / 10 mid = (lo + hi) / 2 - elapsed = measure_seconds { HeavyObject.new(mid) } - if elapsed < TARGET_SECONDS + if measure_seconds { HeavyObject.new(mid) } < HEAVY_TARGET_SECONDS lo = mid else hi = mid @@ -147,6 +162,16 @@ def calibrate_heavy_object_size (lo + hi) / 2 end + # Find how many Counter.new(0) fit in ~1 second. + def calibrate_fast_object_count + count = 1_000 + loop do + elapsed = measure_seconds { count.times { Counter.new(0) } } + return count if elapsed >= FAST_TARGET_SECONDS + count *= 2 + end + end + def measure_seconds t = Process.clock_gettime(Process::CLOCK_MONOTONIC) yield diff --git a/test/ruby/test_ractor_unsafe.rb b/test/ruby/test_ractor_unsafe.rb new file mode 100644 index 00000000..3053f7d1 --- /dev/null +++ b/test/ruby/test_ractor_unsafe.rb @@ -0,0 +1,85 @@ +require "minitest/autorun" + +# Build the UNSAFE test extension if needed (no RICE_RACTOR_SAFE) +ext_dir = File.expand_path("ext_unsafe", __dir__) +bundle = File.join(ext_dir, "ractor_test_unsafe_ext.bundle") +so = File.join(ext_dir, "ractor_test_unsafe_ext.so") + +unless File.exist?(bundle) || File.exist?(so) + Dir.chdir(ext_dir) do + system(RbConfig.ruby, "extconf.rb", exception: true) + system("make", "clean", exception: true) + system("make", exception: true) + end +end + +class RactorUnsafeTest < Minitest::Test + # Verify that WITHOUT RICE_RACTOR_SAFE, concurrent Ractor access to + # Rice-wrapped objects fails — either by crashing (segfault/abort) or + # by hanging (corrupted std::map causes infinite loop). + # + # This proves the mutex is necessary, not just defensive. + # + # Runs in a subprocess with a timeout so neither a crash nor a hang + # kills the test runner. + # + # The number of objects is calibrated to fill ~1 second of rapid-fire + # Counter.new calls, ensuring sufficient registry contention on any machine. + + TIMEOUT_SECONDS = 30 + + SUBPROCESS_SCRIPT = <<~'RUBY' + $LOAD_PATH.unshift(ARGV[0]) + require "ractor_test_unsafe_ext" + + # Calibrate: how many Counter.new(0) fit in ~1 second? + count = 1_000 + loop do + t = Process.clock_gettime(Process::CLOCK_MONOTONIC) + count.times { Counter.new(0) } + break if Process.clock_gettime(Process::CLOCK_MONOTONIC) - t >= 1.0 + count *= 2 + end + + # Launch concurrent Ractors with calibrated count + r1 = Ractor.new(count) { |n| n.times { Counter.new(0) }; :ok } + r2 = Ractor.new(count) { |n| n.times { Counter.new(0) }; :ok } + r1.take + r2.take + RUBY + + def test_concurrent_access_without_mutex_fails + ext_dir = File.expand_path("ext_unsafe", __dir__) + + pid = spawn( + RbConfig.ruby, "-e", SUBPROCESS_SCRIPT, ext_dir, + out: File::NULL, err: File::NULL + ) + + # Wait with timeout — hang is also a failure mode + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + TIMEOUT_SECONDS + status = nil + + loop do + _, status = Process.wait2(pid, Process::WNOHANG) + break if status + if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline + Process.kill("KILL", pid) + _, status = Process.wait2(pid) + break + end + sleep 0.1 + end + + if status.success? + skip "Race condition did not manifest in this run (non-deterministic)" + elsif status.termsig == 9 && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline - 1 + # We killed it after timeout — it was hanging (corrupted map) + pass # Hang confirms the corruption + else + # Crashed with a signal — segfault, abort, etc. + assert status.signaled?, + "Expected crash or hang without RICE_RACTOR_SAFE, got exit #{status.exitstatus}" + end + end +end From adb9a2cd4ca752e30350d5e0e5a0a7c8ebbb2a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20S=C3=A1nchez=20Yeste?= Date: Fri, 27 Mar 2026 10:02:17 +0100 Subject: [PATCH 4/4] Add documentation and changelog for RICE_RACTOR_SAFE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/why_rice.md: expand Thread Safety section with Ractor support subsection. Clarifies that without the define behavior is unchanged, documents the extconf.rb usage, and notes Ruby 3.4.x compatibility (Ruby 4.x pending validation). - CHANGELOG.md: add unreleased entry for the feature. The #include is conditional (#ifdef RICE_RACTOR_SAFE) inside InstanceRegistry.hpp itself — no changes to rice.hpp, respecting the project's include management policy. --- CHANGELOG.md | 6 ++++++ docs/why_rice.md | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 800bb5ca..6a020685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### New Features + +* Add opt-in Ractor-safe `InstanceRegistry` via `RICE_RACTOR_SAFE` define. When enabled, a `std::recursive_mutex` protects all mutable registry operations, allowing C extensions built with Rice to be used safely from multiple Ruby Ractors (Ruby 3.4.x). Without the define, behavior is unchanged. + ## 4.11.4 (2026-03-13) ### Bug Fixes diff --git a/docs/why_rice.md b/docs/why_rice.md index 0e51129b..7dc6131c 100644 --- a/docs/why_rice.md +++ b/docs/why_rice.md @@ -74,7 +74,24 @@ Rice automatically converts common exceptions and provides a mechanism for conve ## Thread Safety -Rice provides no mechanisms for dealing with thread safety. Many common thread safety issues should be alleviated by YARV, which supports POSIX threads. +Rice provides no mechanisms for dealing with thread safety by default. Many common thread safety issues should be alleviated by YARV, which supports POSIX threads. + +### Ractor Support (opt-in) + +Ruby 3.4 introduced Ractors for true parallel execution. C extensions can declare themselves Ractor-safe via `rb_ext_ractor_safe(true)`, but Rice's internal `InstanceRegistry` — a `std::map` that tracks C++ object wrappers — is not thread-safe. Concurrent access from multiple Ractors corrupts the map, causing segfaults or hangs. + +To enable Ractor-safe operation, define `RICE_RACTOR_SAFE` before compilation: + +```ruby +# In your extconf.rb: +$CXXFLAGS << " -DRICE_RACTOR_SAFE" +``` + +This adds a `std::recursive_mutex` to `InstanceRegistry`, protecting all `lookup`, `add`, `remove`, and `clear` operations. Only `InstanceRegistry` requires this protection — the other Rice registries (`TypeRegistry`, `NativeRegistry`, `HandlerRegistry`, `ModuleRegistry`) are written once during initialization and are read-only at runtime. + +Without the define, the generated code is identical to the current Rice — no mutex, no overhead, no behavior change. + +**Compatibility:** Tested with Ruby 3.4.x Ractors. Ruby 4.x compatibility is pending validation. ## C++ Based API