diff --git a/docs/api_core.rst b/docs/api_core.rst index 15b8e649..fcb1a920 100644 --- a/docs/api_core.rst +++ b/docs/api_core.rst @@ -790,6 +790,10 @@ Wrapper classes Return an item iterator that returns ``std::pair`` key-value pairs analogous to ``iter(dict.items())`` in Python. + In free-threaded Python, the :cpp:class:``detail::dict_iterator`` class + acquires a lock to the underlying dictionary to enable the use of the + efficient but thread-unsafe ``PyDict_Next()`` Python C traversal routine. + .. cpp:function:: detail::dict_iterator end() const Return a sentinel that ends the iteration. diff --git a/docs/free_threaded.rst b/docs/free_threaded.rst index 7acbb42c..3e86f51a 100644 --- a/docs/free_threaded.rst +++ b/docs/free_threaded.rst @@ -235,8 +235,8 @@ necessary based on future experience and changes in Python itself. Wrappers ________ -:ref:`Wrapper types ` like :cpp:class:`nb::list ` may be used in -multi-threaded code. Operations like :cpp:func:`nb::list::append() +:ref:`Wrapper types ` like :cpp:class:`nb::list ` may be used +in multi-threaded code. Operations like :cpp:func:`nb::list::append() ` internally acquire locks and behave just like their ordinary Python counterparts. This means that race conditions can still occur without larger-scale synchronization, but such races won't jeopardize the memory safety diff --git a/include/nanobind/nanobind.h b/include/nanobind/nanobind.h index a138ec4f..71987856 100644 --- a/include/nanobind/nanobind.h +++ b/include/nanobind/nanobind.h @@ -48,10 +48,10 @@ #include "nb_error.h" #include "nb_attr.h" #include "nb_cast.h" +#include "nb_misc.h" #include "nb_call.h" #include "nb_func.h" #include "nb_class.h" -#include "nb_misc.h" #if defined(_MSC_VER) # pragma warning(pop) diff --git a/include/nanobind/nb_call.h b/include/nanobind/nb_call.h index 25150b0b..b20bba9d 100644 --- a/include/nanobind/nb_call.h +++ b/include/nanobind/nb_call.h @@ -65,7 +65,7 @@ NB_INLINE void call_init(PyObject **args, PyObject *kwnames, size_t &nargs, } else if constexpr (std::is_same_v) { PyObject *key, *entry; Py_ssize_t pos = 0; - + ft_object_guard guard(value); while (PyDict_Next(value.ptr(), &pos, &key, &entry)) { Py_INCREF(key); Py_INCREF(entry); args[kwargs_offset + nkwargs] = entry; diff --git a/include/nanobind/nb_defs.h b/include/nanobind/nb_defs.h index d6395e69..071e41f9 100644 --- a/include/nanobind/nb_defs.h +++ b/include/nanobind/nb_defs.h @@ -173,6 +173,11 @@ # define NB_TYPE_GET_SLOT_IMPL 1 #endif +#define NB_NONCOPYABLE(X) \ + X(const X &) = delete; \ + X &operator=(const X &) = delete; + + #define NB_MODULE_IMPL(name) \ extern "C" [[maybe_unused]] NB_EXPORT PyObject *PyInit_##name(); \ extern "C" NB_EXPORT PyObject *PyInit_##name() diff --git a/include/nanobind/nb_misc.h b/include/nanobind/nb_misc.h index 50028c09..b29d6b69 100644 --- a/include/nanobind/nb_misc.h +++ b/include/nanobind/nb_misc.h @@ -9,10 +9,6 @@ NAMESPACE_BEGIN(NB_NAMESPACE) -#define NB_NONCOPYABLE(X) \ - X(const X &) = delete; \ - X &operator=(const X &) = delete; - struct gil_scoped_acquire { public: NB_NONCOPYABLE(gil_scoped_acquire) diff --git a/include/nanobind/nb_types.h b/include/nanobind/nb_types.h index 68a16a2a..d2645593 100644 --- a/include/nanobind/nb_types.h +++ b/include/nanobind/nb_types.h @@ -819,24 +819,29 @@ struct fast_iterator { class dict_iterator { public: + NB_NONCOPYABLE(dict_iterator) + using value_type = std::pair; using reference = const value_type; - dict_iterator() : h(), pos(-1) { } - + dict_iterator() = default; dict_iterator(handle h) : h(h), pos(0) { +#if defined(NB_FREE_THREADED) + PyCriticalSection_Begin(&cs, h.ptr()); +#endif increment(); } - dict_iterator& operator++() { - increment(); - return *this; +#if defined(NB_FREE_THREADED) + ~dict_iterator() { + if (h.ptr()) + PyCriticalSection_End(&cs); } +#endif - dict_iterator operator++(int) { - dict_iterator rv = *this; + dict_iterator& operator++() { increment(); - return rv; + return *this; } void increment() { @@ -851,8 +856,12 @@ class dict_iterator { private: handle h; - Py_ssize_t pos; - PyObject *key = nullptr, *value = nullptr; + Py_ssize_t pos = -1; + PyObject *key = nullptr; + PyObject *value = nullptr; +#if defined(NB_FREE_THREADED) + PyCriticalSection cs { }; +#endif }; NB_IMPL_COMP(equal, Py_EQ)