@@ -189,54 +189,87 @@ template <class RHSSetup> struct OpBinder {
189189 // work in Python (assigning return value to this). This avoids extra
190190 // copies, and additionally ensures that all references to the object keep
191191 // referencing the same object after the operation.
192- // WARNING: It is crucial to explicitly return 'py::object &' here.
193- // Otherwise the py::object is returned by value, which increments the
194- // reference count, which is not only suboptimal but also incorrect since
195- // we have released the GIL via py::gil_scoped_release.
192+ // NOTE: We do NOT use py::call_guard<py::gil_scoped_release>() here
193+ // because a.cast<T &>() must run with the Python thread state attached.
194+ // On free-threaded Python (3.13t+), PyEval_SaveThread() detaches the
195+ // thread state, and a.cast<T &>() may trigger PyCriticalSection_Begin
196+ // which requires the thread state, causing a segfault. Instead, we
197+ // extract the C++ reference first, then release the GIL only around the
198+ // pure C++ operation.
196199 c.def (
197200 " __iadd__" ,
198201 [](py::object &a, Other &b) -> py::object & {
199- a.cast <T &>() += RHSSetup{}(b);
202+ auto &self = a.cast <T &>();
203+ auto &&rhs = RHSSetup{}(b);
204+ {
205+ py::gil_scoped_release release;
206+ self += rhs;
207+ }
200208 return a;
201209 },
202- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
210+ py::is_operator ());
203211 c.def (
204212 " __isub__" ,
205213 [](py::object &a, Other &b) -> py::object & {
206- a.cast <T &>() -= RHSSetup{}(b);
214+ auto &self = a.cast <T &>();
215+ auto &&rhs = RHSSetup{}(b);
216+ {
217+ py::gil_scoped_release release;
218+ self -= rhs;
219+ }
207220 return a;
208221 },
209- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
222+ py::is_operator ());
210223 c.def (
211224 " __imul__" ,
212225 [](py::object &a, Other &b) -> py::object & {
213- a.cast <T &>() *= RHSSetup{}(b);
226+ auto &self = a.cast <T &>();
227+ auto &&rhs = RHSSetup{}(b);
228+ {
229+ py::gil_scoped_release release;
230+ self *= rhs;
231+ }
214232 return a;
215233 },
216- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
234+ py::is_operator ());
217235 c.def (
218236 " __itruediv__" ,
219237 [](py::object &a, Other &b) -> py::object & {
220- a.cast <T &>() /= RHSSetup{}(b);
238+ auto &self = a.cast <T &>();
239+ auto &&rhs = RHSSetup{}(b);
240+ {
241+ py::gil_scoped_release release;
242+ self /= rhs;
243+ }
221244 return a;
222245 },
223- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
246+ py::is_operator ());
224247 if constexpr (!(std::is_same_v<T, Dataset> ||
225248 std::is_same_v<Other, Dataset>)) {
226249 c.def (
227250 " __imod__" ,
228251 [](py::object &a, Other &b) -> py::object & {
229- a.cast <T &>() %= RHSSetup{}(b);
252+ auto &self = a.cast <T &>();
253+ auto &&rhs = RHSSetup{}(b);
254+ {
255+ py::gil_scoped_release release;
256+ self %= rhs;
257+ }
230258 return a;
231259 },
232- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
260+ py::is_operator ());
233261 c.def (
234262 " __ifloordiv__" ,
235263 [](py::object &a, Other &b) -> py::object & {
236- floor_divide_equals (a.cast <T &>(), RHSSetup{}(b));
264+ auto &self = a.cast <T &>();
265+ auto &&rhs = RHSSetup{}(b);
266+ {
267+ py::gil_scoped_release release;
268+ floor_divide_equals (self, rhs);
269+ }
237270 return a;
238271 },
239- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
272+ py::is_operator ());
240273 if constexpr (!(std::is_same_v<T, DataArray> ||
241274 std::is_same_v<Other, DataArray>)) {
242275 c.def (
@@ -419,22 +452,34 @@ void bind_logical(pybind11::class_<T, Ignored...> &c) {
419452 c.def (
420453 " __ior__" ,
421454 [](const py::object &a, const T2 &b) -> const py::object & {
422- a.cast <T &>() |= b;
455+ auto &self = a.cast <T &>();
456+ {
457+ py::gil_scoped_release release;
458+ self |= b;
459+ }
423460 return a;
424461 },
425- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
462+ py::is_operator ());
426463 c.def (
427464 " __ixor__" ,
428465 [](const py::object &a, const T2 &b) -> const py::object & {
429- a.cast <T &>() ^= b;
466+ auto &self = a.cast <T &>();
467+ {
468+ py::gil_scoped_release release;
469+ self ^= b;
470+ }
430471 return a;
431472 },
432- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
473+ py::is_operator ());
433474 c.def (
434475 " __iand__" ,
435476 [](const py::object &a, const T2 &b) -> const py::object & {
436- a.cast <T &>() &= b;
477+ auto &self = a.cast <T &>();
478+ {
479+ py::gil_scoped_release release;
480+ self &= b;
481+ }
437482 return a;
438483 },
439- py::is_operator (), py::call_guard<py::gil_scoped_release>() );
484+ py::is_operator ());
440485}
0 commit comments