77 * This functionality is intended to be used by the framework itself,
88 * rather than by users of the framework.
99 *
10- * This is version 0.4 of pymetabind. Changelog:
10+ * This is version 0.4+dev of pymetabind. Changelog:
11+ *
12+ * Unreleased: Use critical section API to avoid lock ordering issues.
1113 *
1214 * Version 0.4: Properly return NULL if registry capsule creation fails.
1315 * 2026-01-23 Support concurrent calls to `pymb_get_registry`.
@@ -270,20 +272,47 @@ struct pymb_registry {
270272#endif
271273};
272274
273- #if defined(Py_GIL_DISABLED)
274- PYMB_INLINE void pymb_lock_registry (struct pymb_registry * registry) {
275- PyMutex_Lock (®istry->mutex );
275+ #if !defined(Py_GIL_DISABLED)
276+
277+ struct pymb_lock_ticket {
278+ char unused;
279+ };
280+ PYMB_INLINE void pymb_lock_registry (struct pymb_lock_ticket * ticket,
281+ struct pymb_registry * registry) {
282+ (void ) ticket;
283+ (void ) registry;
284+ }
285+ PYMB_INLINE void pymb_unlock_registry (struct pymb_lock_ticket * ticket) {
286+ (void ) ticket;
276287}
277- PYMB_INLINE void pymb_unlock_registry (struct pymb_registry * registry) {
278- PyMutex_Unlock (®istry->mutex );
288+
289+ #elif PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1 has PyCriticalSection_BeginMutex
290+
291+ struct pymb_lock_ticket {
292+ PyCriticalSection cs;
293+ };
294+ PYMB_INLINE void pymb_lock_registry (struct pymb_lock_ticket * ticket,
295+ struct pymb_registry * registry) {
296+ PyCriticalSection_BeginMutex (&ticket->cs , ®istry->mutex );
279297}
298+ PYMB_INLINE void pymb_unlock_registry (struct pymb_lock_ticket * ticket) {
299+ PyCriticalSection_End (&ticket->cs );
300+ }
301+
280302#else
281- PYMB_INLINE void pymb_lock_registry (struct pymb_registry * registry) {
282- (void ) registry;
303+
304+ struct pymb_lock_ticket {
305+ struct pymb_registry * registry;
306+ };
307+ PYMB_INLINE void pymb_lock_registry (struct pymb_lock_ticket * ticket,
308+ struct pymb_registry * registry) {
309+ PyMutex_Lock (®istry->mutex );
310+ ticket->registry = registry;
283311}
284- PYMB_INLINE void pymb_unlock_registry (struct pymb_registry * registry ) {
285- ( void ) registry;
312+ PYMB_INLINE void pymb_unlock_registry (struct pymb_lock_ticket * ticket ) {
313+ PyMutex_Unlock (&ticket-> registry -> mutex ) ;
286314}
315+
287316#endif
288317
289318struct pymb_binding ;
@@ -908,7 +937,9 @@ PYMB_FUNC void pymb_add_framework(struct pymb_registry* registry,
908937 framework->link .next = NULL ;
909938 framework->link .prev = NULL ;
910939 framework->registry = registry;
911- pymb_lock_registry (registry);
940+
941+ struct pymb_lock_ticket ticket;
942+ pymb_lock_registry (&ticket, registry);
912943 PYMB_LIST_FOREACH (struct pymb_framework *, other, registry->frameworks) {
913944 // Intern `abi_extra` strings so they can be compared by pointer
914945 if (other->abi_extra && framework->abi_extra &&
@@ -930,7 +961,7 @@ PYMB_FUNC void pymb_add_framework(struct pymb_registry* registry,
930961 framework->add_foreign_binding (binding);
931962 }
932963 }
933- pymb_unlock_registry (registry );
964+ pymb_unlock_registry (&ticket );
934965}
935966
936967/*
@@ -1036,14 +1067,15 @@ PYMB_FUNC void pymb_add_binding(struct pymb_binding* binding,
10361067 }
10371068 Py_DECREF (binding->capsule ); // keep only a borrowed reference
10381069
1039- pymb_lock_registry (registry);
1070+ struct pymb_lock_ticket ticket;
1071+ pymb_lock_registry (&ticket, registry);
10401072 pymb_list_append (®istry->bindings , &binding->link );
10411073 PYMB_LIST_FOREACH (struct pymb_framework *, other, registry->frameworks) {
10421074 if (other != binding->framework ) {
10431075 other->add_foreign_binding (binding);
10441076 }
10451077 }
1046- pymb_unlock_registry (registry );
1078+ pymb_unlock_registry (&ticket );
10471079 return ;
10481080
10491081 error:
@@ -1083,11 +1115,12 @@ PYMB_FUNC void pymb_remove_binding_internal(struct pymb_binding* binding,
10831115
10841116 // Since we need to obtain it anyway, use the registry lock to serialize
10851117 // concurrent attempts to remove the same binding
1086- pymb_lock_registry (registry);
1118+ struct pymb_lock_ticket ticket;
1119+ pymb_lock_registry (&ticket, registry);
10871120 if (!binding->capsule ) {
10881121 // Binding was concurrently removed from multiple places; the first
10891122 // one to get the registry lock wins.
1090- pymb_unlock_registry (registry );
1123+ pymb_unlock_registry (&ticket );
10911124 return ;
10921125 }
10931126
@@ -1130,7 +1163,7 @@ PYMB_FUNC void pymb_remove_binding_internal(struct pymb_binding* binding,
11301163 other->remove_foreign_binding (binding);
11311164 }
11321165 }
1133- pymb_unlock_registry (registry );
1166+ pymb_unlock_registry (&ticket );
11341167
11351168#if !defined(Py_GIL_DISABLED)
11361169 // On GIL builds, there's no need to delay deallocation
0 commit comments