rcx 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a820a4875675677ecd561fdabdd91571147cba6e85942e763156f8456bbfd07d
4
- data.tar.gz: 20e63a83133047933f216487125292e919f76bcff9bd4c10ff917ff151651729
3
+ metadata.gz: 67bf53ccbe670ee75a0faa064fed2c9cedb0f91042c52a61f75d2fa2948f36b7
4
+ data.tar.gz: cc83c45e03bd7cb72bf8856385c74c061e3995c50f409ff9fbc9beb9a62c12c2
5
5
  SHA512:
6
- metadata.gz: 828a90a3dc8fc8c79aa13ec72812df58f74e3c7dd4645b7ef25c6e58ac7fd76a831e29e2a6f3a6ef56717ceeb1ee89577eefa6436bc85b1a6343fdeb5d9e348b
7
- data.tar.gz: 6dcda5bb8368a7387c92ca9e1a36c5747be753c69a56805100b1017493bbc6c4c3d7bf62f9d382c42f274974ae5f7a50617c5dd54fc5a1b5f411b392857433a7
6
+ metadata.gz: '08a0a98e5eade9330c4cd599b25264a3ea792ff5fc7067a122176bbf0a95e766cb6b7d0e6bc583e1bdb8535d49f5ac36624453bfa6455b381564b413e73ee5bb'
7
+ data.tar.gz: 19d7231302d29d691af29fd21696e43ed9f31be3b293f48c87fc7cd6b73e58e23307a558693cdb60da49d7d8a2a6c553c35bdbb6def1e0efd4ccf847cc07e2da
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  ## UNRELEASED
2
2
 
3
+ ## v0.3.0 (2025-08-22)
4
+ - Added `rcx::gvl::without_gvl`
5
+
3
6
  ## v0.2.1 (2025-07-10)
4
7
 
5
8
  ## v0.2.0 (2025-07-09)
@@ -14,11 +14,11 @@
14
14
  #include <string>
15
15
  #include <string_view>
16
16
  #include <type_traits>
17
- #include <vector>
18
17
 
19
18
  #include <ruby.h>
20
19
  #include <ruby/encoding.h>
21
20
  #include <ruby/io/buffer.h>
21
+ #include <ruby/thread.h>
22
22
 
23
23
  #define rcx_assert(expr) assert((expr))
24
24
  #define rcx_delete(reason) delete
@@ -1520,6 +1520,82 @@ namespace rcx {
1520
1520
  /// @return Reference to the Ruby environment instance.
1521
1521
  static Ruby &get();
1522
1522
  };
1523
+
1524
+ /// Global VM Lock (GVL) management.
1525
+ ///
1526
+ namespace gvl {
1527
+ /// Flags for controlling GVL release behavior.
1528
+ ///
1529
+ enum class ReleaseFlags : int {
1530
+ /// Default behavior - allow interrupts and no special handling.
1531
+ None = 0,
1532
+ /// Prevent interrupt checking during execution.
1533
+ /// Use this when interrupts could cause problems with your function's execution.
1534
+ IntrFail = 1,
1535
+ /// The unblock function (if provided) is async-signal-safe.
1536
+ UbfAsyncSafe = 2,
1537
+ /// The function is safe to offload to a background thread or work pool.
1538
+ Offloadable = 4
1539
+ };
1540
+
1541
+ /// Bitwise OR operator for ReleaseFlags.
1542
+ constexpr ReleaseFlags operator|(ReleaseFlags lhs, ReleaseFlags rhs) noexcept {
1543
+ return static_cast<ReleaseFlags>(static_cast<int>(lhs) | static_cast<int>(rhs));
1544
+ }
1545
+
1546
+ /// Bitwise AND operator for ReleaseFlags.
1547
+ constexpr ReleaseFlags operator&(ReleaseFlags lhs, ReleaseFlags rhs) noexcept {
1548
+ return static_cast<ReleaseFlags>(static_cast<int>(lhs) & static_cast<int>(rhs));
1549
+ }
1550
+
1551
+ /// Releases the GVL and executes a function.
1552
+ ///
1553
+ /// This function releases the Global VM Lock (GVL) before executing the
1554
+ /// callback, allowing other Ruby threads to run concurrently.
1555
+ ///
1556
+ /// @warning The callback must not call any Ruby C API functions that may touch Ruby
1557
+ /// objects.
1558
+ ///
1559
+ /// @tparam F The type of the callback function.
1560
+ /// @tparam U The type of the unblock function.
1561
+ /// @param callback The function to execute without the GVL.
1562
+ /// @param ubf An optional unblock function to interrupt the callback
1563
+ /// execution. This function can be called from another thread.
1564
+ /// @param flags Control flags for the GVL release behavior.
1565
+ /// @return When the callback returns `void`, this function returns `true` if the callback
1566
+ /// was executed completely, or `false` if it was interrupted by `ubf`.
1567
+ /// When the callback returns a value, this function returns an `std::optional` containing
1568
+ /// the result if the callback was executed completely, or `std::nullopt`
1569
+ /// if it was interrupted.
1570
+ template <std::invocable<> F, std::invocable U>
1571
+ auto without_gvl(F callback, std::optional<U> ubf, ReleaseFlags flags) noexcept(noexcept(
1572
+ callback(), (*ubf)())) -> std::conditional_t<std::is_void_v<std::invoke_result_t<F>>, bool,
1573
+ std::optional<std::invoke_result_t<F>>>;
1574
+
1575
+ /// Releases the GVL and executes a function.
1576
+ ///
1577
+ /// This is an overload of `without_gvl` that does not take an unblock
1578
+ /// function.
1579
+ ///
1580
+ /// @warning The callback must not call any Ruby C API functions that may touch Ruby
1581
+ /// objects.
1582
+ ///
1583
+ /// @tparam F The type of the callback function.
1584
+ /// @param callback The function to execute without the GVL.
1585
+ /// @param flags Control flags for the GVL release behavior.
1586
+ /// @return When the callback returns `void`, this function returns `true` if the callback
1587
+ /// was executed completely, or `false` if it was interrupted.
1588
+ /// When the callback returns a value, this function returns an `std::optional` containing
1589
+ /// the result if the callback was executed completely, or `std::nullopt`
1590
+ /// if it was interrupted.
1591
+ template <std::invocable<> F>
1592
+ auto without_gvl(F &&callback, ReleaseFlags flags) noexcept(noexcept(callback()))
1593
+ -> std::conditional_t<std::is_void_v<std::invoke_result_t<F>>, bool,
1594
+ std::optional<std::invoke_result_t<F>>>;
1595
+
1596
+ /// Checks for pending interrupts.
1597
+ void check_interrupts();
1598
+ }
1523
1599
  }
1524
1600
 
1525
1601
  namespace std {
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include <concepts>
5
5
  #include <memory>
6
+ #include <optional>
6
7
  #include <ranges>
7
8
  #include <stdexcept>
8
9
  #include <string_view>
@@ -10,6 +11,7 @@
10
11
  #include <type_traits>
11
12
  #include <typeinfo>
12
13
  #include <utility>
14
+ #include <variant>
13
15
 
14
16
  #include <ffi.h>
15
17
  #include <rcx/internal/rcx.hpp>
@@ -1414,4 +1416,108 @@ namespace rcx {
1414
1416
  }
1415
1417
  }
1416
1418
  }
1419
+
1420
+ namespace gvl {
1421
+ template <std::invocable<> F, std::invocable<> U>
1422
+ auto without_gvl(F callback, std::optional<U> ubf, ReleaseFlags flags) noexcept(noexcept(
1423
+ callback(), (*ubf)())) -> std::conditional_t<std::is_void_v<std::invoke_result_t<F>>, bool,
1424
+ std::optional<std::invoke_result_t<F>>> {
1425
+
1426
+ using ResultType = std::conditional_t<std::is_void_v<std::invoke_result_t<F>>, std::monostate,
1427
+ std::optional<std::invoke_result_t<F>>>;
1428
+
1429
+ struct CallbackData {
1430
+ F callback;
1431
+ [[no_unique_address]] ResultType result;
1432
+ std::exception_ptr exception;
1433
+ };
1434
+
1435
+ struct UbfData {
1436
+ U ubf;
1437
+ std::exception_ptr exception;
1438
+ };
1439
+
1440
+ CallbackData data{std::move(callback), ResultType{}, nullptr};
1441
+ std::optional<UbfData> ubf_data;
1442
+ if(ubf) {
1443
+ ubf_data.emplace(std::move(*ubf), nullptr);
1444
+ }
1445
+
1446
+ auto callback_wrapper = [](void *RCX_Nonnull arg) -> void * {
1447
+ auto &data = *static_cast<CallbackData * RCX_Nonnull>(arg);
1448
+ try {
1449
+ if constexpr(std::is_void_v<std::invoke_result_t<F>>) {
1450
+ data.callback();
1451
+ } else {
1452
+ data.result = data.callback();
1453
+ }
1454
+ } catch(...) {
1455
+ data.exception = std::current_exception();
1456
+ }
1457
+ return reinterpret_cast<void *>(1); // Non-null to indicate execution
1458
+ };
1459
+
1460
+ using Ubf = void (*RCX_Nullable)(void *RCX_Nonnull);
1461
+ Ubf ubf_wrapper = nullptr;
1462
+ if(ubf_data) {
1463
+ ubf_wrapper = [](void *RCX_Nonnull arg) -> void {
1464
+ auto &data = *static_cast<UbfData * RCX_Nonnull>(arg);
1465
+ try {
1466
+ data.ubf();
1467
+ } catch(...) {
1468
+ data.exception = std::current_exception();
1469
+ }
1470
+ };
1471
+ }
1472
+
1473
+ void *result = rb_nogvl(callback_wrapper, &data, ubf_wrapper,
1474
+ ubf_data ? std::addressof(*ubf_data) : nullptr, static_cast<int>(flags));
1475
+
1476
+ // Check for UBF exceptions first. The callback was cancelled with UBF, which then raised.
1477
+ if(ubf_data && ubf_data->exception) {
1478
+ std::rethrow_exception(ubf_data->exception);
1479
+ }
1480
+
1481
+ // If rb_nogvl returned nullptr, the callback was cancelled.
1482
+ if(result == nullptr) {
1483
+ if constexpr(std::is_void_v<std::invoke_result_t<F>>) {
1484
+ return false;
1485
+ } else {
1486
+ return std::nullopt;
1487
+ }
1488
+ }
1489
+
1490
+ // Re-throw any exception that occurred in the callback.
1491
+ if(data.exception) {
1492
+ std::rethrow_exception(data.exception);
1493
+ }
1494
+
1495
+ // Return the callback result.
1496
+ if constexpr(std::is_void_v<std::invoke_result_t<F>>) {
1497
+ return true;
1498
+ } else {
1499
+ return std::move(data.result);
1500
+ }
1501
+ }
1502
+
1503
+ template <std::invocable<> F, std::invocable<> U>
1504
+ auto without_gvl(F &&callback, U ubf, ReleaseFlags flags) noexcept(noexcept(callback(), ubf()))
1505
+ -> std::conditional_t<std::is_void_v<std::invoke_result_t<F>>, bool,
1506
+ std::optional<std::invoke_result_t<F>>> {
1507
+ return without_gvl(
1508
+ std::forward<F>(callback), std::optional<std::remove_cvref_t<U>>(std::move(ubf)), flags);
1509
+ }
1510
+
1511
+ template <std::invocable<> F>
1512
+ auto without_gvl(F &&callback, ReleaseFlags flags) noexcept(noexcept(callback()))
1513
+ -> std::conditional_t<std::is_void_v<std::invoke_result_t<F>>, bool,
1514
+ std::optional<std::invoke_result_t<F>>> {
1515
+ using DefaultUbf = void (*)();
1516
+ return without_gvl(std::forward<F>(callback), std::optional<DefaultUbf>(std::nullopt), flags);
1517
+ }
1518
+
1519
+ inline void check_interrupts() {
1520
+ detail::protect([]() noexcept { ::rb_thread_check_ints(); });
1521
+ }
1522
+ }
1417
1523
  }
data/lib/rcx/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RCX
2
- VERSION = -'0.2.1'
2
+ VERSION = -'0.3.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rcx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasumi Hanazuki