1010#include " ../headers/math/clamps.hpp"
1111#include " ../headers/math/random.hpp"
1212#include " ../headers/pyThread.hpp"
13+ #include " ../headers/sync/pyBarrier.hpp"
1314#include " ../headers/sync/pyEvent.hpp"
1415#include " ../headers/sync/pyLock.hpp"
1516#include " ../headers/sync/pyRWLock.hpp"
@@ -675,16 +676,28 @@ std::shared_ptr<SpawnedKernel> spawn_from_meta(
675676 auto job = make_kernel_job (self, call_fn, params_keep);
676677 if (pool) {
677678 // Keep SpawnedKernel alive until the task runs or is dropped from the queue.
679+ // Python-owned handles in `job` / `spawned` must only be destroyed while the
680+ // GIL is held (pool workers are bare OS threads).
678681 struct PoolTaskState {
679682 std::shared_ptr<SpawnedKernel> spawned;
680683 std::function<void ()> job;
681684 std::atomic<bool > started{false };
685+
686+ void release_python_state () {
687+ py::gil_scoped_acquire gil;
688+ job = nullptr ;
689+ spawned.reset ();
690+ }
691+
682692 ~PoolTaskState () {
693+ py::gil_scoped_acquire gil;
683694 if (!started.load (std::memory_order_acquire) && spawned) {
684695 spawned->mark_done (std::make_exception_ptr (std::runtime_error (
685696 " cthreads.pool: job dropped (pool stopped before run)"
686697 )));
687698 }
699+ job = nullptr ;
700+ spawned.reset ();
688701 }
689702 };
690703 auto st = std::make_shared<PoolTaskState>();
@@ -699,6 +712,7 @@ std::shared_ptr<SpawnedKernel> spawn_from_meta(
699712 } catch (...) {
700713 st->spawned ->mark_done (std::current_exception ());
701714 }
715+ st->release_python_state ();
702716 });
703717 } else {
704718 // Dedicated path (unchanged): one OS thread via CThread.
@@ -837,8 +851,11 @@ std::uintptr_t sync_native_ptr(py::object obj) {
837851 if (py::isinstance<cthreads::sync::RWLock>(obj)) {
838852 return reinterpret_cast <std::uintptr_t >(&obj.cast <cthreads::sync::RWLock&>());
839853 }
854+ if (py::isinstance<cthreads::sync::Barrier>(obj)) {
855+ return reinterpret_cast <std::uintptr_t >(&obj.cast <cthreads::sync::Barrier&>());
856+ }
840857 throw std::runtime_error (
841- " cthreads.sync_native_ptr: expected Lock, Event, or RWLock " );
858+ " cthreads.sync_native_ptr: expected Lock, Event, RWLock, or Barrier " );
842859}
843860
844861#include " pool.tpp"
@@ -877,7 +894,7 @@ PYBIND11_MODULE(_ext, m) {
877894 " sync_native_ptr" ,
878895 &sync_native_ptr,
879896 py::arg (" obj" ),
880- " Return the native address of a cthreads.sync Lock/Event/RWLock "
897+ " Return the native address of a cthreads.sync Lock/Event/RWLock/Barrier "
881898 " for kernel marshalling."
882899 );
883900
@@ -995,6 +1012,14 @@ PYBIND11_MODULE(_ext, m) {
9951012
9961013 rwlock.attr (" __cthreads_internal__" ) = true ;
9971014
1015+ auto barrier = py::class_<cthreads::sync::Barrier>(sync, " Barrier" )
1016+ .def (py::init<std::size_t >(), py::arg (" parties" ))
1017+ .def (" parties" , &cthreads::sync::Barrier::parties)
1018+ .def (" arrive_and_wait" , &cthreads::sync::Barrier::arrive_and_wait,
1019+ py::call_guard<py::gil_scoped_release>());
1020+
1021+ barrier.attr (" __cthreads_internal__" ) = true ;
1022+
9981023 // Fixed-capacity triple buffers (baseline bindings for primitive/container types).
9991024 // Thread-side writes are typically codegen'd as: buf[i].field = ... (for Threadables)
10001025 // or buf[i] = value (for scalar/container slots).
0 commit comments