| # Thread safety annotations for C API functions. | |
| # | |
| # Each line has the form: | |
| # function_name : level | |
| # | |
| # Where level is one of: | |
| # incompatible -- not safe even with external locking | |
| # compatible -- safe if the caller serializes all access with external locks | |
| # distinct -- safe on distinct objects without external synchronization | |
| # shared -- safe for concurrent use on the same object | |
| # atomic -- atomic | |
| # | |
| # Lines beginning with '#' are ignored. | |
| # The function name must match the C domain identifier used in the documentation. | |
| # Synchronization primitives (Doc/c-api/synchronization.rst) | |
| PyMutex_Lock:atomic: | |
| PyMutex_Unlock:atomic: | |
| PyMutex_IsLocked:atomic: | |
| # Dictionary objects (Doc/c-api/dict.rst) | |
| # Type checks - read ob_type pointer, always safe | |
| PyDict_Check:atomic: | |
| PyDict_CheckExact:atomic: | |
| # Creation - pure allocation, no shared state | |
| PyDict_New:atomic: | |
| # Lock-free lookups - use _Py_dict_lookup_threadsafe(), no locking. | |
| # Atomic with simple types. | |
| PyDict_Contains:shared: | |
| PyDict_ContainsString:atomic: | |
| PyDict_GetItemRef:shared: | |
| PyDict_GetItemStringRef:atomic: | |
| PyDict_Size:atomic: | |
| PyDict_GET_SIZE:atomic: | |
| # Borrowed-reference lookups - lock-free dict access but returned | |
| # borrowed reference is unsafe in free-threaded builds without | |
| # external synchronization | |
| PyDict_GetItem:compatible: | |
| PyDict_GetItemWithError:compatible: | |
| PyDict_GetItemString:compatible: | |
| PyDict_SetDefault:compatible: | |
| # Iteration - no locking; returns borrowed refs | |
| PyDict_Next:compatible: | |
| # Single-item mutations - protected by per-object critical section | |
| PyDict_SetItem:shared: | |
| PyDict_SetItemString:atomic: | |
| PyDict_DelItem:shared: | |
| PyDict_DelItemString:atomic: | |
| PyDict_SetDefaultRef:shared: | |
| PyDict_Pop:shared: | |
| PyDict_PopString:atomic: | |
| # Bulk reads - hold per-object lock for duration | |
| PyDict_Clear:atomic: | |
| PyDict_Copy:atomic: | |
| PyDict_Keys:atomic: | |
| PyDict_Values:atomic: | |
| PyDict_Items:atomic: | |
| # Merge/update - lock target dict; also lock source when it is a dict | |
| PyDict_Update:shared: | |
| PyDict_Merge:shared: | |
| PyDict_MergeFromSeq2:shared: | |
| # Watcher registration - no synchronization on interpreter state | |
| PyDict_AddWatcher:compatible: | |
| PyDict_ClearWatcher:compatible: | |
| # Per-dict watcher tags - non-atomic RMW on _ma_watcher_tag; | |
| # safe on distinct dicts only | |
| PyDict_Watch:distinct: | |
| PyDict_Unwatch:distinct: | |
| # List objects (Doc/c-api/list.rst) | |
| # Type checks - read ob_type pointer, always safe | |
| PyList_Check:atomic: | |
| PyList_CheckExact:atomic: | |
| # Creation - pure allocation, no shared state | |
| PyList_New:atomic: | |
| # Size - uses atomic load on free-threaded builds | |
| PyList_Size:atomic: | |
| PyList_GET_SIZE:atomic: | |
| # Strong-reference lookup - lock-free with atomic ops | |
| PyList_GetItemRef:atomic: | |
| # Borrowed-reference lookups - no locking; returned borrowed | |
| # reference is unsafe in free-threaded builds without | |
| # external synchronization | |
| PyList_GetItem:compatible: | |
| PyList_GET_ITEM:compatible: | |
| # Single-item mutations - hold per-object lock for duration; | |
| # appear atomic to lock-free readers | |
| PyList_SetItem:atomic: | |
| PyList_Append:atomic: | |
| # Insert - protected by per-object critical section; shifts | |
| # elements so lock-free readers may observe intermediate states | |
| PyList_Insert:shared: | |
| # Initialization macro - no synchronization; normally only used | |
| # to fill in new lists where there is no previous content | |
| PyList_SET_ITEM:compatible: | |
| # Bulk operations - hold per-object lock for duration | |
| PyList_GetSlice:atomic: | |
| PyList_AsTuple:atomic: | |
| PyList_Clear:atomic: | |
| # Reverse - protected by per-object critical section; swaps | |
| # elements so lock-free readers may observe intermediate states | |
| PyList_Reverse:shared: | |
| # Slice assignment - lock target list; also lock source when it | |
| # is a list | |
| PyList_SetSlice:shared: | |
| # Sort - per-object lock held; the list is emptied before sorting | |
| # so other threads may observe an empty list, but they won't see the | |
| # intermediate states of the sort | |
| PyList_Sort:shared: | |
| # Extend - lock target list; also lock source when it is a | |
| # list, set, or dict | |
| PyList_Extend:shared: | |
| # Creation - pure allocation, no shared state | |
| PyBytes_FromString:atomic: | |
| PyBytes_FromStringAndSize:atomic: | |
| PyBytes_DecodeEscape:atomic: | |
| # Creation from formatting C primitives - pure allocation, no shared state | |
| PyBytes_FromFormat:atomic: | |
| PyBytes_FromFormatV:atomic: | |
| # Creation from object - uses buffer protocol so may call arbitrary code; | |
| # safe as long as the buffer is not mutated by another thread during the operation | |
| PyBytes_FromObject:shared: | |
| # Size - uses atomic load on free-threaded builds | |
| PyBytes_Size:atomic: | |
| PyBytes_GET_SIZE:atomic: | |
| # Raw data - no locking; mutating it is unsafe if the bytes object is shared between threads | |
| PyBytes_AsString:compatible: | |
| PyBytes_AS_STRING:compatible: | |
| PyBytes_AsStringAndSize:compatible: | |
| # Concatenation - uses buffer protocol; safe as long as buffer is not mutated by another thread during the operation | |
| PyBytes_Concat:shared: | |
| PyBytes_ConcatAndDel:shared: | |
| PyBytes_Join:shared: | |
| # Resizing - safe if the object is unique | |
| _PyBytes_Resize:distinct: | |
| # Repr - atomic as bytes are immutable | |
| PyBytes_Repr:atomic: | |
| # Creation from object - may call arbitrary code | |
| PyByteArray_FromObject:shared: | |
| # Creation - pure allocation, no shared state | |
| PyByteArray_FromStringAndSize:atomic: | |
| # Concatenation - uses buffer protocol; safe as long as buffer is not mutated by another thread during the operation | |
| PyByteArray_Concat:shared: | |
| # Size - uses atomic load on free-threaded builds | |
| PyByteArray_Size:atomic: | |
| PyByteArray_GET_SIZE:atomic: | |
| # Raw data - no locking; mutating it is unsafe if the bytearray object is shared between threads | |
| PyByteArray_AsString:compatible: | |
| PyByteArray_AS_STRING:compatible: | |
| # Creation - may iterate the iterable argument, calling arbitrary code. | |
| # Atomic for sets, frozensets, dicts, and frozendicts. | |
| PySet_New:shared: | |
| PyFrozenSet_New:shared: | |
| # Size - uses atomic load on free-threaded builds | |
| PySet_Size:atomic: | |
| PySet_GET_SIZE:atomic: | |
| # Contains - lock-free, atomic with simple types | |
| PySet_Contains:shared: | |
| # Mutations - hold per-object lock for duration | |
| # atomic with simple types | |
| PySet_Add:shared: | |
| PySet_Discard:shared: | |
| # Pop - hold per-object lock for duration | |
| PySet_Pop:atomic: | |
| # Clear - empties the set before clearing | |
| PySet_Clear:atomic: | |
| # Capsule objects (Doc/c-api/capsule.rst) | |
| # Type check - read ob_type pointer, always safe | |
| PyCapsule_CheckExact:atomic: | |
| # Creation - pure allocation, no shared state | |
| PyCapsule_New:atomic: | |
| # Validation - reads pointer and name fields; safe on distinct objects | |
| PyCapsule_IsValid:distinct: | |
| # Getters - read struct fields; safe on distinct objects but | |
| # concurrent access to the same capsule requires external synchronization | |
| PyCapsule_GetPointer:distinct: | |
| PyCapsule_GetName:distinct: | |
| PyCapsule_GetDestructor:distinct: | |
| PyCapsule_GetContext:distinct: | |
| # Setters - write struct fields; safe on distinct objects but | |
| # concurrent access to the same capsule requires external synchronization | |
| PyCapsule_SetPointer:distinct: | |
| PyCapsule_SetName:distinct: | |
| PyCapsule_SetDestructor:distinct: | |
| PyCapsule_SetContext:distinct: | |
| # Import - looks up a capsule from a module attribute and | |
| # calls PyCapsule_GetPointer; may call arbitrary code | |
| PyCapsule_Import:compatible: | |
| # Tuple objects | |
| # Creation - pure allocation, no shared state | |
| PyTuple_New:atomic: | |
| PyTuple_FromArray:atomic: | |
| PyTuple_Pack:atomic: | |
| # Size - tuples are immutable so size never changes | |
| PyTuple_Size:atomic: | |
| PyTuple_GET_SIZE:atomic: | |
| # Borrowed-reference lookups - tuples are immutable so items | |
| # never change, however the tuple must be kept alive while using the borrowed reference | |
| PyTuple_GetItem:compatible: | |
| PyTuple_GET_ITEM:compatible: | |
| # Slice - creates a new tuple from an existing tuple | |
| PyTuple_GetSlice:atomic: | |
| # SetItem - only usable on tuples with refcount 1 | |
| PyTuple_SetItem:compatible: | |
| PyTuple_SET_ITEM:compatible: | |
| # Resize - only usable on tuples with refcount 1 | |
| _PyTuple_Resize:compatible: | |
| # Struct Sequence objects | |
| # Creation | |
| PyStructSequence_NewType:atomic: | |
| PyStructSequence_New:atomic: | |
| # Initialization - modifies the type object in place | |
| PyStructSequence_InitType:distinct: | |
| PyStructSequence_InitType2:distinct: | |
| # Borrowed-reference lookups - same as tuple items | |
| PyStructSequence_GetItem:compatible: | |
| PyStructSequence_GET_ITEM:compatible: | |
| # SetItem - only for filling in brand new instances | |
| PyStructSequence_SetItem:compatible: | |
| PyStructSequence_SET_ITEM:compatible: | |