6
6
#include "pycore_code.h" // CO_FAST_FREE
7
7
#include "pycore_dict.h" // _PyDict_KeysSize()
8
8
#include "pycore_frame.h" // _PyInterpreterFrame
9
+ #include "pycore_lock.h" // _PySeqLock_*
9
10
#include "pycore_long.h" // _PyLong_IsNegative()
10
11
#include "pycore_memoryobject.h" // _PyMemoryView_FromBufferProc()
11
12
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
@@ -4913,43 +4914,21 @@ static void
4913
4914
update_cache_gil_disabled (struct type_cache_entry * entry , PyObject * name ,
4914
4915
unsigned int version_tag , PyObject * value )
4915
4916
{
4916
- // Similar to linux seqlock: https://en.wikipedia.org/wiki/Seqlock
4917
- // We use a sequence number to lock the writer, an even sequence means we're unlocked, an odd
4918
- // sequence means we're locked.
4919
- // Differs a little bit in that we use CAS on sequence as the lock, instead of a seperate spin lock.
4920
- // If our writer detects that another thread has already done the same write we'll just bail
4921
- // and restore the previous sequence number without doing any updates.
4922
-
4923
- // lock the entry by setting by moving to an odd sequence number
4924
- int prev = entry -> sequence ;
4925
- while (1 ) {
4926
- if (TYPE_CACHE_IS_UPDATING (prev )) {
4927
- // Someone else is currently updating the cache
4928
- _Py_yield ();
4929
- prev = _Py_atomic_load_int32_relaxed (& entry -> sequence );
4930
- } else if (_Py_atomic_compare_exchange_int32 (& entry -> sequence , & prev , prev + 1 )) {
4931
- // We've locked the cache
4932
- break ;
4933
- } else {
4934
- _Py_yield ();
4935
- }
4936
- }
4917
+ _PySeqLock_LockWrite (& entry -> sequence );
4937
4918
4938
4919
// update the entry
4939
4920
if (entry -> name == name &&
4940
4921
entry -> value == value &&
4941
4922
entry -> version == version_tag ) {
4942
- // We reaced with another update, bail and restore previous sequence.
4943
- _Py_atomic_exchange_int32 (& entry -> sequence , prev );
4923
+ // We raced with another update, bail and restore previous sequence.
4924
+ _PySeqLock_AbandonWrite (& entry -> sequence );
4944
4925
return ;
4945
4926
}
4946
4927
4947
4928
update_cache (entry , name , version_tag , value );
4948
4929
4949
4930
// Then update sequence to the next valid value
4950
- int new_sequence = prev + 2 ;
4951
- assert (!TYPE_CACHE_IS_UPDATING (new_sequence ));
4952
- _Py_atomic_exchange_int32 (& entry -> sequence , new_sequence );
4931
+ _PySeqLock_UnlockWrite (& entry -> sequence );
4953
4932
}
4954
4933
4955
4934
#endif
@@ -4959,10 +4938,8 @@ void _PyTypes_AfterFork() {
4959
4938
struct type_cache * cache = get_type_cache ();
4960
4939
for (int i = 0 ; i < 1 << MCACHE_SIZE_EXP ; i ++ ) {
4961
4940
struct type_cache_entry * entry = & cache -> hashtable [i ];
4962
- int sequence = _Py_atomic_load_int_acquire (& entry -> sequence );
4963
- if (TYPE_CACHE_IS_UPDATING (sequence )) {
4941
+ if (_PySeqLock_AfterFork (& entry -> sequence )) {
4964
4942
// Entry was in the process of updating while forking, clear it...
4965
- entry -> sequence = 0 ;
4966
4943
entry -> value = NULL ;
4967
4944
entry -> name = NULL ;
4968
4945
entry -> version = 0 ;
@@ -4986,27 +4963,22 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
4986
4963
#ifdef Py_GIL_DISABLED
4987
4964
// synchronize-with other writing threads by doing an acquire load on the sequence
4988
4965
while (1 ) {
4989
- int sequence = _Py_atomic_load_int_acquire (& entry -> sequence );
4990
- if (!TYPE_CACHE_IS_UPDATING (sequence )) {
4991
- if (_Py_atomic_load_uint32_relaxed (& entry -> version ) == type -> tp_version_tag &&
4992
- _Py_atomic_load_ptr_relaxed (& entry -> name ) == name ) {
4993
- assert (_PyType_HasFeature (type , Py_TPFLAGS_VALID_VERSION_TAG ));
4994
- OBJECT_STAT_INC_COND (type_cache_hits , !is_dunder_name (name ));
4995
- OBJECT_STAT_INC_COND (type_cache_dunder_hits , is_dunder_name (name ));
4996
- PyObject * value = _Py_atomic_load_ptr_relaxed (& entry -> value );
4997
-
4998
- // Synchronize again and validate that the entry hasn't been updated
4999
- // while we were readying the values.
5000
- if (_Py_atomic_load_int_acquire (& entry -> sequence ) == sequence ) {
5001
- return value ;
5002
- }
5003
- } else {
5004
- // Cache miss
5005
- break ;
5006
- }
4966
+ int sequence = _PySeqLock_BeginRead (& entry -> sequence );
4967
+ if (_Py_atomic_load_uint32_relaxed (& entry -> version ) == type -> tp_version_tag &&
4968
+ _Py_atomic_load_ptr_relaxed (& entry -> name ) == name ) {
4969
+ assert (_PyType_HasFeature (type , Py_TPFLAGS_VALID_VERSION_TAG ));
4970
+ OBJECT_STAT_INC_COND (type_cache_hits , !is_dunder_name (name ));
4971
+ OBJECT_STAT_INC_COND (type_cache_dunder_hits , is_dunder_name (name ));
4972
+ PyObject * value = _Py_atomic_load_ptr_relaxed (& entry -> value );
4973
+
4974
+ // If the sequence is still valid then we're done
4975
+ if (_PySeqLock_EndRead (& entry -> sequence , sequence )) {
4976
+ return value ;
4977
+ }
4978
+ } else {
4979
+ // cache miss
4980
+ break ;
5007
4981
}
5008
- // We are in progress of updating the cache, retry
5009
- _Py_yield ();
5010
4982
}
5011
4983
#else
5012
4984
if (entry -> version == type -> tp_version_tag &&
0 commit comments