private-attribute-cpp 2.1.4__tar.gz → 2.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {private_attribute_cpp-2.1.4/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.6}/PKG-INFO +1 -1
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/private_attribute.cpp +377 -322
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6/private_attribute_cpp.egg-info}/PKG-INFO +1 -1
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/private_attribute_cpp.egg-info/SOURCES.txt +1 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/setup.py +1 -1
- private_attribute_cpp-2.1.6/tests/test_gc_collect.py +145 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/LICENSE +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/MANIFEST.in +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/README.md +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/picosha2.h +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/private_attribute.pyi +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/private_attribute_cpp.egg-info/top_level.txt +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/setup.cfg +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_attr_classify_owner.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_class_private_attrs.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_private_abc.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_private_attrs.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_private_func.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_private_inheritance.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_private_method.py +0 -0
- {private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_private_wrap.py +0 -0
{private_attribute_cpp-2.1.4/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: private_attribute_cpp
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.6
|
|
4
4
|
Summary: A Python package that provides a way to define private attributes in C++ implementation.
|
|
5
5
|
Home-page: https://github.com/Locked-chess-official/private_attribute_cpp
|
|
6
6
|
Author: HuangHaoHua
|
|
@@ -203,6 +203,7 @@ namespace {
|
|
|
203
203
|
};
|
|
204
204
|
static std::unordered_map<uintptr_t, std::unordered_map<uintptr_t, std::shared_ptr<std::shared_mutex>>>
|
|
205
205
|
all_object_mutex, all_type_subclass_mutex;
|
|
206
|
+
static std::mutex object_creatmutex_mutex, type_createmutex_mutex, type_parenttype_createmutex_mutex;
|
|
206
207
|
static std::unordered_map<uintptr_t, std::vector<uintptr_t>> all_type_parent_id;
|
|
207
208
|
// all type tp_getattro map
|
|
208
209
|
static std::unordered_map<uintptr_t, getattrofunc> all_type_getattro;
|
|
@@ -219,15 +220,63 @@ namespace {
|
|
|
219
220
|
// defaults as "not a real original" and keep walking upward.
|
|
220
221
|
static traverseproc captured_subtype_traverse = NULL;
|
|
221
222
|
static inquiry captured_subtype_clear = NULL;
|
|
222
|
-
|
|
223
|
-
static std::unordered_map<uintptr_t, destructor> all_type_finalize;
|
|
223
|
+
static std::unordered_map<uintptr_t, destructor> all_type_del;
|
|
224
224
|
|
|
225
225
|
static std::shared_mutex all_register_new_metaclass_mutex;
|
|
226
226
|
static std::unordered_map<uintptr_t, PyObjectStorage> all_register_type_weak_ref;
|
|
227
|
+
static std::vector<PyObjectStorage> store_module_self;
|
|
227
228
|
};
|
|
228
229
|
};
|
|
229
230
|
|
|
230
|
-
static void
|
|
231
|
+
static inline void
|
|
232
|
+
object_create_mutex(uintptr_t final_id, uintptr_t obj_id) noexcept
|
|
233
|
+
{
|
|
234
|
+
// get object_creatmutex_mutex
|
|
235
|
+
std::lock_guard<std::mutex> lock(AllData::object_creatmutex_mutex);
|
|
236
|
+
if (::AllData::all_object_attr[final_id].find(obj_id) == ::AllData::all_object_attr[final_id].end()) {
|
|
237
|
+
::AllData::all_object_attr[final_id][obj_id] = {};
|
|
238
|
+
}
|
|
239
|
+
if (::AllData::all_object_mutex.find(final_id) == ::AllData::all_object_mutex.end()) {
|
|
240
|
+
::AllData::all_object_mutex[final_id] = {};
|
|
241
|
+
}
|
|
242
|
+
if (::AllData::all_object_mutex[final_id].find(obj_id) == ::AllData::all_object_mutex[final_id].end()) {
|
|
243
|
+
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
244
|
+
::AllData::all_object_mutex[final_id][obj_id] = lock;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static inline void
|
|
249
|
+
type_create_mutex(uintptr_t typ_id) noexcept {
|
|
250
|
+
std::lock_guard<std::mutex> lock(AllData::type_createmutex_mutex);
|
|
251
|
+
if (::AllData::type_attr_dict.find(typ_id) == ::AllData::type_attr_dict.end()) {
|
|
252
|
+
::AllData::type_attr_dict[typ_id] = {};
|
|
253
|
+
}
|
|
254
|
+
if (::AllData::all_type_mutex.find(typ_id) == ::AllData::all_type_mutex.end()) {
|
|
255
|
+
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
256
|
+
::AllData::all_type_mutex[typ_id] = lock;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static inline void
|
|
261
|
+
type_parenttype_create_mutex(uintptr_t final_id, uintptr_t typ_id) noexcept
|
|
262
|
+
{
|
|
263
|
+
std::lock_guard<std::mutex> lock(AllData::type_parenttype_createmutex_mutex);
|
|
264
|
+
if (::AllData::all_type_subclass_attr.find(final_id) == ::AllData::all_type_subclass_attr.end()) {
|
|
265
|
+
::AllData::all_type_subclass_attr[final_id] = {};
|
|
266
|
+
}
|
|
267
|
+
if (::AllData::all_type_subclass_attr[final_id].find(typ_id) == ::AllData::all_type_subclass_attr[final_id].end()) {
|
|
268
|
+
::AllData::all_type_subclass_attr[final_id][typ_id] = {};
|
|
269
|
+
}
|
|
270
|
+
if (::AllData::all_type_subclass_mutex.find(final_id) == ::AllData::all_type_subclass_mutex.end()) {
|
|
271
|
+
::AllData::all_type_subclass_mutex[final_id] = {};
|
|
272
|
+
}
|
|
273
|
+
if (::AllData::all_type_subclass_mutex[final_id].find(typ_id) == ::AllData::all_type_subclass_mutex[final_id].end()) {
|
|
274
|
+
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
275
|
+
::AllData::all_type_subclass_mutex[final_id][typ_id] = lock;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
static inline void
|
|
231
280
|
clean_all_storages() noexcept
|
|
232
281
|
{
|
|
233
282
|
AllData::cache.clear();
|
|
@@ -247,8 +296,9 @@ clean_all_storages() noexcept
|
|
|
247
296
|
AllData::all_type_setattro.clear();
|
|
248
297
|
AllData::all_type_traverse.clear();
|
|
249
298
|
AllData::all_type_clear.clear();
|
|
250
|
-
AllData::
|
|
299
|
+
AllData::all_type_del.clear();
|
|
251
300
|
AllData::all_register_type_weak_ref.clear();
|
|
301
|
+
AllData::store_module_self.clear();
|
|
252
302
|
}
|
|
253
303
|
|
|
254
304
|
struct FinalObject
|
|
@@ -563,16 +613,7 @@ id_getattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
|
|
|
563
613
|
PyErr_SetString(PyExc_TypeError, "type not found");
|
|
564
614
|
return NULL;
|
|
565
615
|
}
|
|
566
|
-
|
|
567
|
-
::AllData::all_object_attr[final_id][obj_id] = {};
|
|
568
|
-
}
|
|
569
|
-
if (::AllData::all_object_mutex.find(final_id) == ::AllData::all_object_mutex.end()) {
|
|
570
|
-
::AllData::all_object_mutex[final_id] = {};
|
|
571
|
-
}
|
|
572
|
-
if (::AllData::all_object_mutex[final_id].find(obj_id) == ::AllData::all_object_mutex[final_id].end()) {
|
|
573
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
574
|
-
::AllData::all_object_mutex[final_id][obj_id] = lock;
|
|
575
|
-
}
|
|
616
|
+
object_create_mutex(final_id, obj_id);
|
|
576
617
|
PyObject* result = NULL;
|
|
577
618
|
if (final_object.status != -1) {
|
|
578
619
|
result = final_object.result;
|
|
@@ -699,16 +740,7 @@ id_setattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
|
|
|
699
740
|
PyErr_SetString(PyExc_TypeError, "type not found");
|
|
700
741
|
return -1;
|
|
701
742
|
}
|
|
702
|
-
|
|
703
|
-
::AllData::all_object_attr[final_id][obj_id] = {};
|
|
704
|
-
}
|
|
705
|
-
if (::AllData::all_object_mutex.find(final_id) == ::AllData::all_object_mutex.end()) {
|
|
706
|
-
::AllData::all_object_mutex[final_id] = {};
|
|
707
|
-
}
|
|
708
|
-
if (::AllData::all_object_mutex[final_id].find(obj_id) == ::AllData::all_object_mutex[final_id].end()) {
|
|
709
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
710
|
-
::AllData::all_object_mutex[final_id][obj_id] = lock;
|
|
711
|
-
}
|
|
743
|
+
object_create_mutex(final_id, obj_id);
|
|
712
744
|
// first: call __set__ method
|
|
713
745
|
PyObject* result = NULL;
|
|
714
746
|
if (final_object.status != -1) {
|
|
@@ -764,13 +796,7 @@ type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexc
|
|
|
764
796
|
return -1;
|
|
765
797
|
}
|
|
766
798
|
if (final_id == typ_id) {
|
|
767
|
-
|
|
768
|
-
::AllData::type_attr_dict[typ_id] = {};
|
|
769
|
-
}
|
|
770
|
-
if (::AllData::all_type_mutex.find(typ_id) == ::AllData::all_type_mutex.end()) {
|
|
771
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
772
|
-
::AllData::all_type_mutex[typ_id] = lock;
|
|
773
|
-
}
|
|
799
|
+
type_create_mutex(typ_id);
|
|
774
800
|
{
|
|
775
801
|
std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[typ_id]);
|
|
776
802
|
::AllData::type_attr_dict[typ_id][final_key] = value;
|
|
@@ -779,19 +805,7 @@ type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexc
|
|
|
779
805
|
} else {
|
|
780
806
|
// The accessed class is a subclass that stores its own value for a
|
|
781
807
|
// parent's private name separately: all_type_subclass_attr[final_id][typ_id].
|
|
782
|
-
|
|
783
|
-
::AllData::all_type_subclass_attr[final_id] = {};
|
|
784
|
-
}
|
|
785
|
-
if (::AllData::all_type_subclass_attr[final_id].find(typ_id) == ::AllData::all_type_subclass_attr[final_id].end()) {
|
|
786
|
-
::AllData::all_type_subclass_attr[final_id][typ_id] = {};
|
|
787
|
-
}
|
|
788
|
-
if (::AllData::all_type_subclass_mutex.find(final_id) == ::AllData::all_type_subclass_mutex.end()) {
|
|
789
|
-
::AllData::all_type_subclass_mutex[final_id] = {};
|
|
790
|
-
}
|
|
791
|
-
if (::AllData::all_type_subclass_mutex[final_id].find(typ_id) == ::AllData::all_type_subclass_mutex[final_id].end()) {
|
|
792
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
793
|
-
::AllData::all_type_subclass_mutex[final_id][typ_id] = lock;
|
|
794
|
-
}
|
|
808
|
+
type_parenttype_create_mutex(final_id, typ_id);
|
|
795
809
|
{
|
|
796
810
|
std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[final_id][typ_id]);
|
|
797
811
|
::AllData::all_type_subclass_attr[final_id][typ_id][final_key] = value;
|
|
@@ -830,16 +844,7 @@ id_delattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
|
|
|
830
844
|
PyErr_SetString(PyExc_TypeError, "type not found");
|
|
831
845
|
return -1;
|
|
832
846
|
}
|
|
833
|
-
|
|
834
|
-
::AllData::all_object_attr[final_id][obj_id] = {};
|
|
835
|
-
}
|
|
836
|
-
if (::AllData::all_object_mutex.find(final_id) == ::AllData::all_object_mutex.end()) {
|
|
837
|
-
::AllData::all_object_mutex[final_id] = {};
|
|
838
|
-
}
|
|
839
|
-
if (::AllData::all_object_mutex[final_id].find(obj_id) == ::AllData::all_object_mutex[final_id].end()) {
|
|
840
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
841
|
-
::AllData::all_object_mutex[final_id][obj_id] = lock;
|
|
842
|
-
}
|
|
847
|
+
object_create_mutex(final_id, obj_id);
|
|
843
848
|
// first: find attribute on type to find "__delete__"
|
|
844
849
|
PyObject* result = NULL;
|
|
845
850
|
if (final_object.status == 0) {
|
|
@@ -897,13 +902,7 @@ type_delattr(PyObject* typ, const std::string& attr_name) noexcept
|
|
|
897
902
|
return -1;
|
|
898
903
|
}
|
|
899
904
|
if (typ_id == final_id) {
|
|
900
|
-
|
|
901
|
-
::AllData::type_attr_dict[typ_id] = {};
|
|
902
|
-
}
|
|
903
|
-
if (::AllData::all_type_mutex.find(typ_id) == ::AllData::all_type_mutex.end()) {
|
|
904
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
905
|
-
::AllData::all_type_mutex[typ_id] = lock;
|
|
906
|
-
}
|
|
905
|
+
type_create_mutex(typ_id);
|
|
907
906
|
std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[typ_id]);
|
|
908
907
|
if (::AllData::type_attr_dict[typ_id].find(final_key) == ::AllData::type_attr_dict[typ_id].end()) {
|
|
909
908
|
const char* type_name = get_name_from_tp_name((PyTypeObject*)typ);
|
|
@@ -915,19 +914,7 @@ type_delattr(PyObject* typ, const std::string& attr_name) noexcept
|
|
|
915
914
|
}
|
|
916
915
|
::AllData::type_attr_dict[typ_id].erase(final_key);
|
|
917
916
|
} else {
|
|
918
|
-
|
|
919
|
-
::AllData::all_type_subclass_attr[final_id] = {};
|
|
920
|
-
}
|
|
921
|
-
if (::AllData::all_type_subclass_attr[final_id].find(typ_id) == ::AllData::all_type_subclass_attr[final_id].end()) {
|
|
922
|
-
::AllData::all_type_subclass_attr[final_id][typ_id] = {};
|
|
923
|
-
}
|
|
924
|
-
if (::AllData::all_type_subclass_mutex.find(final_id) == ::AllData::all_type_subclass_mutex.end()) {
|
|
925
|
-
::AllData::all_type_subclass_mutex[final_id] = {};
|
|
926
|
-
}
|
|
927
|
-
if (::AllData::all_type_subclass_mutex[final_id].find(typ_id) == ::AllData::all_type_subclass_mutex[final_id].end()) {
|
|
928
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
929
|
-
::AllData::all_type_subclass_mutex[final_id][typ_id] = lock;
|
|
930
|
-
}
|
|
917
|
+
type_parenttype_create_mutex(final_id, typ_id);
|
|
931
918
|
std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[final_id][typ_id]);
|
|
932
919
|
if (::AllData::all_type_subclass_attr[final_id][typ_id].find(final_key) == ::AllData::all_type_subclass_attr[final_id][typ_id].end()) {
|
|
933
920
|
const char* type_name = get_name_from_tp_name((PyTypeObject*)typ);
|
|
@@ -1364,6 +1351,7 @@ typedef struct {
|
|
|
1364
1351
|
static void PrivateAttr_object_init_private_dict(uintptr_t obj_id, uintptr_t type_id) noexcept;
|
|
1365
1352
|
static int PrivateAttr_tp_setattro(PyObject* self, PyObject* name, PyObject* value) noexcept;
|
|
1366
1353
|
static void PrivateAttr_tp_finalize(PyObject* self) noexcept;
|
|
1354
|
+
static void clear_object_related(uintptr_t id_self, uintptr_t typ_id) noexcept;
|
|
1367
1355
|
|
|
1368
1356
|
static PyObject*
|
|
1369
1357
|
PrivateAttr_tp_getattro(PyObject* self, PyObject* name) noexcept
|
|
@@ -1441,60 +1429,6 @@ PrivateAttr_tp_setattro(PyObject* self, PyObject* name, PyObject* value) noexcep
|
|
|
1441
1429
|
return PyObject_GenericSetAttr(self, name, value);
|
|
1442
1430
|
}
|
|
1443
1431
|
|
|
1444
|
-
static void
|
|
1445
|
-
PrivateAttr_tp_finalize(PyObject* self) noexcept
|
|
1446
|
-
{
|
|
1447
|
-
uintptr_t id_self = (uintptr_t)self;
|
|
1448
|
-
PyTypeObject* typ = Py_TYPE(self);
|
|
1449
|
-
uintptr_t typ_id = (uintptr_t)typ;
|
|
1450
|
-
Py_ssize_t original_ref = Py_REFCNT(self);
|
|
1451
|
-
if (::AllData::all_type_finalize.find(typ_id) != ::AllData::all_type_finalize.end()){
|
|
1452
|
-
if (::AllData::all_type_finalize[typ_id]) {
|
|
1453
|
-
::AllData::all_type_finalize[typ_id](self);
|
|
1454
|
-
ensure_tp(typ);
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
if (original_ref != Py_REFCNT(self)) {
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
|
-
std::vector<uintptr_t> parent_ids;
|
|
1461
|
-
if (::AllData::all_type_parent_id.find(typ_id) != ::AllData::all_type_parent_id.end()){
|
|
1462
|
-
parent_ids = ::AllData::all_type_parent_id[typ_id];
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
{
|
|
1466
|
-
// first: clear ::AllData::all_object_attr and ::AllData::all_object_mutex on this typ_id
|
|
1467
|
-
if (::AllData::all_object_attr.find(typ_id) != ::AllData::all_object_attr.end()){
|
|
1468
|
-
auto& all_object_attr = ::AllData::all_object_attr[typ_id];
|
|
1469
|
-
if (all_object_attr.find(id_self) != all_object_attr.end()){
|
|
1470
|
-
all_object_attr.erase(id_self);
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
if (::AllData::all_object_mutex.find(typ_id) != ::AllData::all_object_mutex.end()){
|
|
1474
|
-
auto& all_object_mutex = ::AllData::all_object_mutex[typ_id];
|
|
1475
|
-
if (all_object_mutex.find(id_self) != all_object_mutex.end()){
|
|
1476
|
-
all_object_mutex.erase(id_self);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
// second: clear the above in parent types
|
|
1480
|
-
for (auto& parent_id : parent_ids){
|
|
1481
|
-
if (::AllData::all_object_attr.find(parent_id) != ::AllData::all_object_attr.end()){
|
|
1482
|
-
auto& all_object_attr = ::AllData::all_object_attr[parent_id];
|
|
1483
|
-
if (all_object_attr.find(id_self) != all_object_attr.end()){
|
|
1484
|
-
all_object_attr.erase(id_self);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
if (::AllData::all_object_mutex.find(parent_id) != ::AllData::all_object_mutex.end()){
|
|
1488
|
-
auto& all_object_mutex = ::AllData::all_object_mutex[parent_id];
|
|
1489
|
-
if (all_object_mutex.find(id_self) != all_object_mutex.end()){
|
|
1490
|
-
all_object_mutex.erase(id_self);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
clear_obj(id_self);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
1432
|
// shared helper for clearing object-related data (used by tp_clear and tp_finalize)
|
|
1499
1433
|
static void
|
|
1500
1434
|
clear_object_related(uintptr_t id_self, uintptr_t typ_id) noexcept
|
|
@@ -1606,6 +1540,61 @@ PrivateAttr_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
|
1606
1540
|
}
|
|
1607
1541
|
}
|
|
1608
1542
|
|
|
1543
|
+
// Instance-dict visit (mirror of subtype_traverse's dictoffset block).
|
|
1544
|
+
// The saved "original" for a private-attribute class is subtype_traverse,
|
|
1545
|
+
// but because the class's LIVE tp_traverse has been replaced with this
|
|
1546
|
+
// wrapper, subtype_traverse skips its own slot-walking loop, keeps base ==
|
|
1547
|
+
// typ, and its `type->tp_dictoffset != base->tp_dictoffset` check sees equal
|
|
1548
|
+
// offsets -> it never visits the instance __dict__. The instance __dict__
|
|
1549
|
+
// (e.g. `self.__dict__['x'] = self` self-cycles) would then be invisible to
|
|
1550
|
+
// the collector and leak. Do the visit explicitly here instead.
|
|
1551
|
+
//
|
|
1552
|
+
// The managed-dict API is version-dependent: 3.13+ exposes the public
|
|
1553
|
+
// PyObject_VisitManagedDict, 3.12 only the internal _PyObject_VisitManagedDict,
|
|
1554
|
+
// and <=3.11 has no managed dict at all (classic positive tp_dictoffset,
|
|
1555
|
+
// visited through _PyObject_GetDictPtr like subtype_traverse does).
|
|
1556
|
+
#if PY_VERSION_HEX >= 0x030D0000
|
|
1557
|
+
if ((typ->tp_flags & Py_TPFLAGS_MANAGED_DICT)
|
|
1558
|
+
&& typ->tp_dictoffset == -1) {
|
|
1559
|
+
int err = PyObject_VisitManagedDict(self, visit, arg);
|
|
1560
|
+
if (err) {
|
|
1561
|
+
return err;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
#elif PY_VERSION_HEX >= 0x030C0000
|
|
1565
|
+
if ((typ->tp_flags & Py_TPFLAGS_MANAGED_DICT)
|
|
1566
|
+
&& typ->tp_dictoffset == -1) {
|
|
1567
|
+
int err = _PyObject_VisitManagedDict(self, visit, arg);
|
|
1568
|
+
if (err) {
|
|
1569
|
+
return err;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
#else
|
|
1573
|
+
PyObject** dictptr = _PyObject_GetDictPtr(self);
|
|
1574
|
+
if (dictptr && *dictptr) {
|
|
1575
|
+
int err = visit(*dictptr, arg);
|
|
1576
|
+
if (err) {
|
|
1577
|
+
return err;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
#endif
|
|
1581
|
+
|
|
1582
|
+
// Instance->type link (mirror of subtype_traverse's heap-type visit).
|
|
1583
|
+
// subtype_traverse(self) does Py_VISIT(Py_TYPE(self)) only when the
|
|
1584
|
+
// resolved base traverse does NOT belong to a heap type. Because the
|
|
1585
|
+
// instance's LIVE tp_traverse has been replaced with this wrapper,
|
|
1586
|
+
// subtype_traverse keeps base == typ (heap), so that Py_VISIT is skipped
|
|
1587
|
+
// and a cycle like A ->(tp_dict)-> a and a ->(ob_type)-> A is invisible
|
|
1588
|
+
// to the collector. Visit the instance's type explicitly when it is a heap
|
|
1589
|
+
// type, matching subtype_traverse's intent. (This is the same fix applied
|
|
1590
|
+
// to the metaclass wrapper PrivateAttrType_tp_traverse.)
|
|
1591
|
+
{
|
|
1592
|
+
PyTypeObject* meta = Py_TYPE(self);
|
|
1593
|
+
if (meta && (meta->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
|
|
1594
|
+
Py_VISIT((PyObject*)meta);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1609
1598
|
// traverse per-object private attributes for this type and its parents
|
|
1610
1599
|
std::vector<uintptr_t> parent_ids;
|
|
1611
1600
|
if (::AllData::all_type_parent_id.find(typ_id) != ::AllData::all_type_parent_id.end()){
|
|
@@ -1656,11 +1645,99 @@ PrivateAttr_tp_clear(PyObject* self) noexcept
|
|
|
1656
1645
|
if (orig) orig(self);
|
|
1657
1646
|
}
|
|
1658
1647
|
|
|
1648
|
+
// Instance-dict clear (mirror of subtype_clear's dict handling). Same
|
|
1649
|
+
// reasoning as the traverse side: subtype_clear can't run its dict block
|
|
1650
|
+
// once the LIVE tp_clear has been replaced, so clear the instance dict
|
|
1651
|
+
// explicitly to break __dict__ self-cycles. Version-dependent APIs, see
|
|
1652
|
+
// the matching traverse block above.
|
|
1653
|
+
#if PY_VERSION_HEX >= 0x030D0000
|
|
1654
|
+
if ((typ->tp_flags & Py_TPFLAGS_MANAGED_DICT)
|
|
1655
|
+
&& typ->tp_dictoffset == -1) {
|
|
1656
|
+
PyObject_ClearManagedDict(self);
|
|
1657
|
+
}
|
|
1658
|
+
#elif PY_VERSION_HEX >= 0x030C0000
|
|
1659
|
+
if ((typ->tp_flags & Py_TPFLAGS_MANAGED_DICT)
|
|
1660
|
+
&& typ->tp_dictoffset == -1) {
|
|
1661
|
+
_PyObject_ClearManagedDict(self);
|
|
1662
|
+
}
|
|
1663
|
+
#else
|
|
1664
|
+
PyObject** dictptr = _PyObject_GetDictPtr(self);
|
|
1665
|
+
if (dictptr && *dictptr) {
|
|
1666
|
+
Py_CLEAR(*dictptr);
|
|
1667
|
+
}
|
|
1668
|
+
#endif
|
|
1669
|
+
|
|
1659
1670
|
// clear our per-object data
|
|
1660
1671
|
clear_object_related(id_self, typ_id);
|
|
1661
1672
|
return 0;
|
|
1662
1673
|
}
|
|
1663
1674
|
|
|
1675
|
+
class KeepPythonException {
|
|
1676
|
+
private:
|
|
1677
|
+
PyObject* typ;
|
|
1678
|
+
PyObject* exc;
|
|
1679
|
+
PyObject* tb;
|
|
1680
|
+
public:
|
|
1681
|
+
KeepPythonException() noexcept {
|
|
1682
|
+
// under 3.12 use PyErr_Fetch
|
|
1683
|
+
#if PY_VERSION_HEX < 0x030C0000
|
|
1684
|
+
PyErr_Fetch(&typ, &exc, &tb);
|
|
1685
|
+
#else
|
|
1686
|
+
exc = PyErr_GetRaisedException();
|
|
1687
|
+
#endif
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
~KeepPythonException() noexcept {
|
|
1691
|
+
// under 3.12 use PyErr_Restore
|
|
1692
|
+
#if PY_VERSION_HEX < 0x030C0000
|
|
1693
|
+
PyErr_Restore(typ, exc, tb);
|
|
1694
|
+
#else
|
|
1695
|
+
PyErr_SetRaisedException(exc);
|
|
1696
|
+
#endif
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
static void
|
|
1701
|
+
PrivateAttr_tp_finalize(PyObject* self) noexcept
|
|
1702
|
+
{
|
|
1703
|
+
KeepPythonException _;
|
|
1704
|
+
uintptr_t id_self = (uintptr_t)self;
|
|
1705
|
+
PyTypeObject* typ = Py_TYPE(self);
|
|
1706
|
+
uintptr_t typ_id = (uintptr_t)typ;
|
|
1707
|
+
|
|
1708
|
+
Py_ssize_t refcnt = Py_REFCNT(self);
|
|
1709
|
+
// Chain to the original tp_finalize if there is one.
|
|
1710
|
+
if (::AllData::all_type_del.find(typ_id) != ::AllData::all_type_del.end()) {
|
|
1711
|
+
destructor orig = ::AllData::all_type_del[typ_id];
|
|
1712
|
+
if (orig && orig != PrivateAttr_tp_finalize) {
|
|
1713
|
+
orig(self);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
if (Py_REFCNT(self) > refcnt) {
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
clear_object_related(id_self, typ_id);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
static destructor
|
|
1723
|
+
get_need_tp_finalize(PyTypeObject* cls) noexcept
|
|
1724
|
+
{
|
|
1725
|
+
PyTypeObject* base = cls->tp_base;
|
|
1726
|
+
while (base) {
|
|
1727
|
+
if (base->tp_finalize != PrivateAttr_tp_finalize) {
|
|
1728
|
+
return base->tp_finalize;
|
|
1729
|
+
} else if (::AllData::all_type_del.find((uintptr_t)base) != ::AllData::all_type_del.end()) {
|
|
1730
|
+
if (::AllData::all_type_del[(uintptr_t)base]) {
|
|
1731
|
+
return ::AllData::all_type_del[(uintptr_t)base];
|
|
1732
|
+
}
|
|
1733
|
+
base = base->tp_base;
|
|
1734
|
+
} else {
|
|
1735
|
+
base = base->tp_base;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return NULL;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1664
1741
|
// get_type_need_tp_* : the metaclass counterparts of get_need_tp_*.
|
|
1665
1742
|
// get_need_tp_* -> find the original slots of *types created by* the
|
|
1666
1743
|
// metaclasses (wrapped with the instance-level
|
|
@@ -1668,13 +1745,12 @@ PrivateAttr_tp_clear(PyObject* self) noexcept
|
|
|
1668
1745
|
// get_type_need_tp_* -> find the original slots of the *metaclasses*
|
|
1669
1746
|
// themselves (wrapped with the metaclass-level
|
|
1670
1747
|
// PrivateAttrType_getattr / PrivateAttrType_setattr /
|
|
1671
|
-
//
|
|
1748
|
+
// PrivateAttrType_tp_traverse /
|
|
1672
1749
|
// PrivateAttrType_tp_clear).
|
|
1673
1750
|
static getattrofunc get_type_need_tp_getattro(PyTypeObject* cls) noexcept;
|
|
1674
1751
|
static setattrofunc get_type_need_tp_setattro(PyTypeObject* cls) noexcept;
|
|
1675
1752
|
static traverseproc get_type_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
1676
1753
|
static inquiry get_type_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
1677
|
-
static destructor get_type_need_tp_finalize(PyTypeObject* cls) noexcept;
|
|
1678
1754
|
|
|
1679
1755
|
// metaclass-level (type object) traverse/clear: call original if any, and handle AllData-stored type attributes
|
|
1680
1756
|
static int
|
|
@@ -1689,14 +1765,15 @@ PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
|
1689
1765
|
}
|
|
1690
1766
|
PrivateAttrRecursionGuard guard((uintptr_t)self);
|
|
1691
1767
|
|
|
1692
|
-
//
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1768
|
+
// Recover the original traverse via get_type_need_tp_traverse, skipping
|
|
1769
|
+
// any delegating default (subtype_traverse / PrivateAttr_tp_traverse / our
|
|
1770
|
+
// own wrapper). For a type object the only correct "real" traverse is
|
|
1771
|
+
// CPython's type_traverse; when no real one exists (plain class objects
|
|
1772
|
+
// delegate through subtype_traverse, which would follow the LIVE tp_traverse
|
|
1773
|
+
// slot straight back into this wrapper), we rely on the AllData traversal
|
|
1774
|
+
// below to reach the class's hard self-cycle (tp_dict / tp_mro / tp_bases)
|
|
1775
|
+
// through the type-level private attributes we visit.
|
|
1776
|
+
{
|
|
1700
1777
|
traverseproc orig = get_type_need_tp_traverse(typ);
|
|
1701
1778
|
if (orig) {
|
|
1702
1779
|
int res = orig(self, visit, arg);
|
|
@@ -1704,6 +1781,24 @@ PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept
|
|
|
1704
1781
|
}
|
|
1705
1782
|
}
|
|
1706
1783
|
|
|
1784
|
+
// Mirror CPython's subtype_traverse for the instance->type link.
|
|
1785
|
+
// subtype_traverse(self) does Py_VISIT(Py_TYPE(self)) when the instance is
|
|
1786
|
+
// a heap type and the resolved base traverse does not belong to a heap
|
|
1787
|
+
// type, so cycles between a class object and its (heap) metaclass are
|
|
1788
|
+
// visible to the collector. type_traverse alone does NOT visit ob_type:
|
|
1789
|
+
// it only walks tp_dict / tp_mro / tp_bases, so without this step a cycle
|
|
1790
|
+
// like M ->(dict attr)-> C and C ->(ob_type)-> M is invisible to GC
|
|
1791
|
+
// and leaks. self here is a TYPE object; its Py_TYPE is the metaclass
|
|
1792
|
+
// (which is a heap type when registered/created by type()). We only emit
|
|
1793
|
+
// the visit when the metaclass is a heap type, matching subtype_traverse's
|
|
1794
|
+
// condition and avoiding the trivial/static type link.
|
|
1795
|
+
{
|
|
1796
|
+
PyTypeObject* meta = Py_TYPE(self);
|
|
1797
|
+
if (meta && (meta->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
|
|
1798
|
+
Py_VISIT((PyObject*)meta);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1707
1802
|
// Traverse PyObjects stored in AllData for this type:
|
|
1708
1803
|
// - direct type attributes ::AllData::type_attr_dict[typ_id]
|
|
1709
1804
|
// - attributes stored under parents for this type ::AllData::all_type_subclass_attr[parent_id][typ_id]
|
|
@@ -1779,13 +1874,21 @@ PrivateAttrType_tp_clear(PyObject* self) noexcept
|
|
|
1779
1874
|
}
|
|
1780
1875
|
PrivateAttrRecursionGuard guard((uintptr_t)self);
|
|
1781
1876
|
|
|
1782
|
-
//
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1877
|
+
// Recover the original clear via get_type_need_tp_clear, skipping any
|
|
1878
|
+
// delegating default (subtype_clear / PrivateAttr_tp_clear / our own
|
|
1879
|
+
// wrapper). subtype_clear on a type object would clear the class's own
|
|
1880
|
+
// tp_dict (its namespace), which must never happen here; when the walker
|
|
1881
|
+
// finds no real clear, the AllData cleanup below still breaks the cycle by
|
|
1882
|
+
// clearing the type-level private attributes we stored (their PyObjectStorage
|
|
1883
|
+
// DECREFs release the references that keep the class's tp_dict / tp_mro /
|
|
1884
|
+
// ht_module alive), after which CPython's own type_clear runs on the class
|
|
1885
|
+
// once the collector re-visits it.
|
|
1886
|
+
{
|
|
1787
1887
|
inquiry orig = get_type_need_tp_clear(typ);
|
|
1788
|
-
if (orig)
|
|
1888
|
+
if (orig) {
|
|
1889
|
+
int result = orig(self);
|
|
1890
|
+
if (result) return result;
|
|
1891
|
+
}
|
|
1789
1892
|
}
|
|
1790
1893
|
|
|
1791
1894
|
// Clear AllData entries associated with this type. PyObjectStorage destructor
|
|
@@ -1866,10 +1969,11 @@ static PyTypeObject PrivateAttrType = {
|
|
|
1866
1969
|
(getattrofunc)PrivateAttrType_getattr, // tp_getattro
|
|
1867
1970
|
(setattrofunc)PrivateAttrType_setattr, // tp_setattro
|
|
1868
1971
|
0, // tp_as_buffer
|
|
1869
|
-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
|
1972
|
+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
|
1973
|
+
| Py_TPFLAGS_HAVE_GC, // tp_flags
|
|
1870
1974
|
"metaclass for private attributes", // tp_doc
|
|
1871
|
-
|
|
1872
|
-
|
|
1975
|
+
PrivateAttrType_tp_traverse, // tp_travers
|
|
1976
|
+
PrivateAttrType_tp_clear, // tp_clear
|
|
1873
1977
|
0, // tp_richcompare
|
|
1874
1978
|
0, // tp_weaklistoffset
|
|
1875
1979
|
0, // tp_iter
|
|
@@ -1939,13 +2043,7 @@ type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
|
|
|
1939
2043
|
} else {
|
|
1940
2044
|
key = default_random_string(type_id, name);
|
|
1941
2045
|
}
|
|
1942
|
-
|
|
1943
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
1944
|
-
::AllData::all_type_mutex[type_id] = lock;
|
|
1945
|
-
}
|
|
1946
|
-
if (::AllData::type_attr_dict.find(type_id) == ::AllData::type_attr_dict.end()) {
|
|
1947
|
-
::AllData::type_attr_dict[type_id] = {};
|
|
1948
|
-
}
|
|
2046
|
+
type_create_mutex(type_id);
|
|
1949
2047
|
std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[type_id]);
|
|
1950
2048
|
auto& item_set = ::AllData::type_attr_dict[type_id];
|
|
1951
2049
|
if (item_set.find(key) != item_set.end()) {
|
|
@@ -1980,13 +2078,7 @@ type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
|
|
|
1980
2078
|
} else {
|
|
1981
2079
|
key = default_random_string(now_visited_id, name);
|
|
1982
2080
|
}
|
|
1983
|
-
|
|
1984
|
-
::AllData::all_type_subclass_mutex[parent_id] = {};
|
|
1985
|
-
}
|
|
1986
|
-
if (::AllData::all_type_subclass_mutex[parent_id].find(now_visited_id) == ::AllData::all_type_subclass_mutex[parent_id].end()) {
|
|
1987
|
-
std::shared_ptr<std::shared_mutex> lock(new std::shared_mutex());
|
|
1988
|
-
::AllData::all_type_subclass_mutex[parent_id][now_visited_id] = lock;
|
|
1989
|
-
}
|
|
2081
|
+
type_parenttype_create_mutex(parent_id, now_visited_id);
|
|
1990
2082
|
std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[parent_id][now_visited_id]);
|
|
1991
2083
|
if (now_mro_dict[now_visited_id].find(key) != now_mro_dict[now_visited_id].end()) {
|
|
1992
2084
|
PyObject* obj = now_mro_dict[now_visited_id][key];
|
|
@@ -2010,17 +2102,12 @@ type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
|
|
|
2010
2102
|
} else {
|
|
2011
2103
|
key = default_random_string(parent_id, name);
|
|
2012
2104
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[parent_id]);
|
|
2020
|
-
if (item_set.find(key) != item_set.end()) {
|
|
2021
|
-
PyObject* obj = item_set[key];
|
|
2022
|
-
return obj;
|
|
2023
|
-
}
|
|
2105
|
+
type_create_mutex(parent_id);
|
|
2106
|
+
auto& item_set = ::AllData::type_attr_dict[parent_id];
|
|
2107
|
+
std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[parent_id]);
|
|
2108
|
+
if (item_set.find(key) != item_set.end()) {
|
|
2109
|
+
PyObject* obj = item_set[key];
|
|
2110
|
+
return obj;
|
|
2024
2111
|
}
|
|
2025
2112
|
}
|
|
2026
2113
|
}
|
|
@@ -2497,7 +2584,10 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2497
2584
|
need_value = value;
|
|
2498
2585
|
}
|
|
2499
2586
|
if (data.private_attrs_vector_string.find(attr_name) != data.private_attrs_vector_string.end()) {
|
|
2500
|
-
|
|
2587
|
+
// PyObjectStorage assignment INCREFs the value (RAII); the
|
|
2588
|
+
// reference is released when need_remove_itself is cleared
|
|
2589
|
+
// (PrivateAttrCreationData::clear). No manual INCREF here,
|
|
2590
|
+
// otherwise the value leaks (+1) when the class is destroyed.
|
|
2501
2591
|
data.need_remove_itself[attr_name] = need_value;
|
|
2502
2592
|
PyDict_DelItem(data.attrs_copy, key);
|
|
2503
2593
|
continue;
|
|
@@ -2510,7 +2600,7 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
|
|
|
2510
2600
|
if (data.need_remove_subclass.find(need_remove_subclass_id) == data.need_remove_subclass.end()) {
|
|
2511
2601
|
data.need_remove_subclass[need_remove_subclass_id] = {};
|
|
2512
2602
|
}
|
|
2513
|
-
|
|
2603
|
+
// PyObjectStorage assignment INCREFs (RAII); no manual INCREF.
|
|
2514
2604
|
data.need_remove_subclass[need_remove_subclass_id][attr_name] = need_value;
|
|
2515
2605
|
PyDict_DelItem(data.attrs_copy, key);
|
|
2516
2606
|
continue;
|
|
@@ -2567,18 +2657,16 @@ static getattrofunc get_need_tp_getattro(PyTypeObject* cls) noexcept;
|
|
|
2567
2657
|
static setattrofunc get_need_tp_setattro(PyTypeObject* cls) noexcept;
|
|
2568
2658
|
static traverseproc get_need_tp_traverse(PyTypeObject* cls) noexcept;
|
|
2569
2659
|
static inquiry get_need_tp_clear(PyTypeObject* cls) noexcept;
|
|
2570
|
-
static destructor get_need_tp_finalize(PyTypeObject* cls) noexcept;
|
|
2571
2660
|
|
|
2572
2661
|
// instance-level traverse/clear for types that use private attributes
|
|
2573
2662
|
static int PrivateAttr_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept;
|
|
2574
2663
|
static int PrivateAttr_tp_clear(PyObject* self) noexcept;
|
|
2664
|
+
// shared helper to clear object related data (avoid double clear)
|
|
2665
|
+
static void clear_object_related(uintptr_t id_self, uintptr_t typ_id) noexcept;
|
|
2575
2666
|
// metaclass-level wrappers (if needed)
|
|
2576
2667
|
static int PrivateAttrType_tp_traverse(PyObject* self, visitproc visit, void* arg) noexcept;
|
|
2577
2668
|
static int PrivateAttrType_tp_clear(PyObject* self) noexcept;
|
|
2578
2669
|
|
|
2579
|
-
// shared helper to clear object related data (avoid double clear)
|
|
2580
|
-
static void clear_object_related(uintptr_t id_self, uintptr_t typ_id) noexcept;
|
|
2581
|
-
|
|
2582
2670
|
static void
|
|
2583
2671
|
ensure_tp(PyTypeObject* type_instance) noexcept
|
|
2584
2672
|
{
|
|
@@ -2657,20 +2745,12 @@ ensure_tp(PyTypeObject* type_instance) noexcept
|
|
|
2657
2745
|
}
|
|
2658
2746
|
{
|
|
2659
2747
|
if (type_instance->tp_finalize != PrivateAttr_tp_finalize) {
|
|
2660
|
-
if (!type_instance->tp_finalize && ::AllData::
|
|
2661
|
-
::AllData::
|
|
2748
|
+
if (!type_instance->tp_finalize && ::AllData::all_type_del.find(type_id) == ::AllData::all_type_del.end()) {
|
|
2749
|
+
::AllData::all_type_del[type_id] = get_need_tp_finalize(type_instance);
|
|
2662
2750
|
} else {
|
|
2663
|
-
::AllData::
|
|
2751
|
+
::AllData::all_type_del[type_id] = type_instance->tp_finalize;
|
|
2664
2752
|
}
|
|
2665
2753
|
type_instance->tp_finalize = PrivateAttr_tp_finalize;
|
|
2666
|
-
} else if (::AllData::all_type_finalize.find(type_id) == ::AllData::all_type_finalize.end()) {
|
|
2667
|
-
PyTypeObject* base = type_instance->tp_base;
|
|
2668
|
-
uintptr_t base_id = (uintptr_t)(base);
|
|
2669
|
-
if (::AllData::all_type_finalize.find(base_id) != ::AllData::all_type_finalize.end()) {
|
|
2670
|
-
::AllData::all_type_finalize[type_id] = ::AllData::all_type_finalize[base_id];
|
|
2671
|
-
} else if (base && base->tp_finalize && base->tp_finalize != PrivateAttr_tp_finalize) {
|
|
2672
|
-
::AllData::all_type_finalize[type_id] = base->tp_finalize;
|
|
2673
|
-
}
|
|
2674
2754
|
}
|
|
2675
2755
|
}
|
|
2676
2756
|
}
|
|
@@ -3031,25 +3111,6 @@ get_need_tp_setattro(PyTypeObject* cls) noexcept
|
|
|
3031
3111
|
return NULL;
|
|
3032
3112
|
}
|
|
3033
3113
|
|
|
3034
|
-
static destructor
|
|
3035
|
-
get_need_tp_finalize(PyTypeObject* cls) noexcept
|
|
3036
|
-
{
|
|
3037
|
-
PyTypeObject* base = cls->tp_base;
|
|
3038
|
-
while (base) {
|
|
3039
|
-
if (base->tp_finalize != PrivateAttr_tp_finalize) {
|
|
3040
|
-
return base->tp_finalize;
|
|
3041
|
-
} else if (::AllData::all_type_finalize.find((uintptr_t)base) != ::AllData::all_type_finalize.end()) {
|
|
3042
|
-
if (::AllData::all_type_finalize[(uintptr_t)base]) {
|
|
3043
|
-
return ::AllData::all_type_finalize[(uintptr_t)base];
|
|
3044
|
-
}
|
|
3045
|
-
base = base->tp_base;
|
|
3046
|
-
} else {
|
|
3047
|
-
base = base->tp_base;
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3050
|
-
return NULL;
|
|
3051
|
-
}
|
|
3052
|
-
|
|
3053
3114
|
static traverseproc
|
|
3054
3115
|
get_need_tp_traverse(PyTypeObject* cls) noexcept
|
|
3055
3116
|
{
|
|
@@ -3092,10 +3153,6 @@ get_need_tp_clear(PyTypeObject* cls) noexcept
|
|
|
3092
3153
|
return NULL;
|
|
3093
3154
|
}
|
|
3094
3155
|
|
|
3095
|
-
// NOTE: register_finalize is defined later in this file; forward-declare it
|
|
3096
|
-
// here because get_type_need_tp_finalize() needs to compare against it.
|
|
3097
|
-
static void register_finalize(PyObject* cls) noexcept;
|
|
3098
|
-
|
|
3099
3156
|
// ========================================================================
|
|
3100
3157
|
// get_type_need_tp_* : find the ORIGINAL slot implementation for METACLASS
|
|
3101
3158
|
// slots.
|
|
@@ -3105,7 +3162,6 @@ static void register_finalize(PyObject* cls) noexcept;
|
|
|
3105
3162
|
// wrappers:
|
|
3106
3163
|
// tp_getattro -> PrivateAttrType_getattr
|
|
3107
3164
|
// tp_setattro -> PrivateAttrType_setattr
|
|
3108
|
-
// tp_finalize -> register_finalize
|
|
3109
3165
|
// tp_traverse -> PrivateAttrType_tp_traverse
|
|
3110
3166
|
// tp_clear -> PrivateAttrType_tp_clear
|
|
3111
3167
|
//
|
|
@@ -3155,65 +3211,81 @@ get_type_need_tp_setattro(PyTypeObject* cls) noexcept
|
|
|
3155
3211
|
return NULL;
|
|
3156
3212
|
}
|
|
3157
3213
|
|
|
3158
|
-
static destructor
|
|
3159
|
-
get_type_need_tp_finalize(PyTypeObject* cls) noexcept
|
|
3160
|
-
{
|
|
3161
|
-
PyTypeObject* base = cls->tp_base;
|
|
3162
|
-
while (base) {
|
|
3163
|
-
if (base->tp_finalize != register_finalize) {
|
|
3164
|
-
return base->tp_finalize;
|
|
3165
|
-
} else if (::AllData::all_type_finalize.find((uintptr_t)base) != ::AllData::all_type_finalize.end()) {
|
|
3166
|
-
if (::AllData::all_type_finalize[(uintptr_t)base]) {
|
|
3167
|
-
return ::AllData::all_type_finalize[(uintptr_t)base];
|
|
3168
|
-
}
|
|
3169
|
-
base = base->tp_base;
|
|
3170
|
-
} else {
|
|
3171
|
-
base = base->tp_base;
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
return NULL;
|
|
3175
|
-
}
|
|
3176
|
-
|
|
3177
3214
|
static traverseproc
|
|
3178
3215
|
get_type_need_tp_traverse(PyTypeObject* cls) noexcept
|
|
3179
3216
|
{
|
|
3217
|
+
// cls is a type object (an instance of a metaclass). CPython only invokes
|
|
3218
|
+
// a real traverse on a tracked type object directly; otherwise it delegates
|
|
3219
|
+
// through subtype_traverse. So walk the metaclass base chain and skip every
|
|
3220
|
+
// delegating default (subtype_traverse, PrivateAttr_tp_traverse, our own
|
|
3221
|
+
// wrapper) AND NULL slots, returning the nearest real implementation.
|
|
3222
|
+
//
|
|
3223
|
+
// For a plain metaclass chain the walk is
|
|
3224
|
+
// cls -> ... -> PrivateAttrBase -> object -> (tp_base == NULL)
|
|
3225
|
+
// and object->tp_traverse is NULL, so the loop keeps walking to the end and
|
|
3226
|
+
// falls through to the PyType_Type.tp_traverse fallback below: for a type
|
|
3227
|
+
// object the ONLY correct real traverse is CPython's own type_traverse,
|
|
3228
|
+
// which is what makes the class's hard self-cycle (tp_dict / tp_mro /
|
|
3229
|
+
// tp_bases) visible to the cyclic GC. Without that fallback the class
|
|
3230
|
+
// becomes uncollectable. For a REGISTERED metaclass chain the saved-map
|
|
3231
|
+
// branch may find a real implementation before the fallback is reached.
|
|
3180
3232
|
PyTypeObject* base = cls->tp_base;
|
|
3181
3233
|
while (base) {
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3234
|
+
traverseproc slot = base->tp_traverse;
|
|
3235
|
+
if (slot
|
|
3236
|
+
&& slot != PrivateAttrType_tp_traverse
|
|
3237
|
+
&& slot != ::AllData::captured_subtype_traverse
|
|
3238
|
+
&& slot != PrivateAttr_tp_traverse) {
|
|
3239
|
+
return slot;
|
|
3240
|
+
}
|
|
3241
|
+
auto it = ::AllData::all_type_traverse.find((uintptr_t)base);
|
|
3242
|
+
if (it != ::AllData::all_type_traverse.end()) {
|
|
3243
|
+
traverseproc saved = it->second;
|
|
3244
|
+
if (saved
|
|
3245
|
+
&& saved != ::AllData::captured_subtype_traverse
|
|
3246
|
+
&& saved != PrivateAttr_tp_traverse
|
|
3247
|
+
&& saved != PrivateAttrType_tp_traverse) {
|
|
3188
3248
|
return saved;
|
|
3189
3249
|
}
|
|
3190
|
-
base = base->tp_base;
|
|
3191
|
-
} else {
|
|
3192
|
-
base = base->tp_base;
|
|
3193
3250
|
}
|
|
3251
|
+
base = base->tp_base;
|
|
3194
3252
|
}
|
|
3195
|
-
|
|
3253
|
+
// Fallback to CPython's real type_traverse (PyType_Type.tp_traverse). A
|
|
3254
|
+
// NULL slot is never a real implementation, so exhausting the chain here
|
|
3255
|
+
// means no custom traverse exists and the only correct original for a type
|
|
3256
|
+
// object is type_traverse itself.
|
|
3257
|
+
return PyType_Type.tp_traverse;
|
|
3196
3258
|
}
|
|
3197
3259
|
|
|
3198
3260
|
static inquiry
|
|
3199
3261
|
get_type_need_tp_clear(PyTypeObject* cls) noexcept
|
|
3200
3262
|
{
|
|
3263
|
+
// Same as get_type_need_tp_traverse: skip every delegating default
|
|
3264
|
+
// (subtype_clear, PrivateAttr_tp_clear, our own wrapper) AND NULL slots,
|
|
3265
|
+
// then fall back to CPython's real type_clear (PyType_Type.tp_clear) so a
|
|
3266
|
+
// plain metaclass chain still breaks the class's hard self-cycle.
|
|
3201
3267
|
PyTypeObject* base = cls->tp_base;
|
|
3202
3268
|
while (base) {
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3269
|
+
inquiry slot = base->tp_clear;
|
|
3270
|
+
if (slot
|
|
3271
|
+
&& slot != PrivateAttrType_tp_clear
|
|
3272
|
+
&& slot != ::AllData::captured_subtype_clear
|
|
3273
|
+
&& slot != PrivateAttr_tp_clear) {
|
|
3274
|
+
return slot;
|
|
3275
|
+
}
|
|
3276
|
+
auto it = ::AllData::all_type_clear.find((uintptr_t)base);
|
|
3277
|
+
if (it != ::AllData::all_type_clear.end()) {
|
|
3278
|
+
inquiry saved = it->second;
|
|
3279
|
+
if (saved
|
|
3280
|
+
&& saved != ::AllData::captured_subtype_clear
|
|
3281
|
+
&& saved != PrivateAttr_tp_clear
|
|
3282
|
+
&& saved != PrivateAttrType_tp_clear) {
|
|
3209
3283
|
return saved;
|
|
3210
3284
|
}
|
|
3211
|
-
base = base->tp_base;
|
|
3212
|
-
} else {
|
|
3213
|
-
base = base->tp_base;
|
|
3214
3285
|
}
|
|
3286
|
+
base = base->tp_base;
|
|
3215
3287
|
}
|
|
3216
|
-
return
|
|
3288
|
+
return PyType_Type.tp_clear;
|
|
3217
3289
|
}
|
|
3218
3290
|
|
|
3219
3291
|
static int
|
|
@@ -3308,10 +3380,17 @@ PrivateAttrType_finalize(PyObject* cls) noexcept
|
|
|
3308
3380
|
if (::AllData::all_type_setattro.find(typ_id) != ::AllData::all_type_setattro.end()) {
|
|
3309
3381
|
::AllData::all_type_setattro.erase(typ_id);
|
|
3310
3382
|
}
|
|
3311
|
-
if (::AllData::all_type_finalize.find(typ_id) != ::AllData::all_type_finalize.end()) {
|
|
3312
|
-
::AllData::all_type_finalize.erase(typ_id);
|
|
3313
|
-
}
|
|
3314
3383
|
::AllData::all_type_mutex.erase(typ_id);
|
|
3384
|
+
// Clear the per-type buckets of the object-attr tables (created in
|
|
3385
|
+
// PrivateAttrType_postprocess / PrivateAttr_object_init_private_dict);
|
|
3386
|
+
// otherwise every class that ever got instances leaves an empty bucket
|
|
3387
|
+
// behind and all_object_attr / all_object_mutex grow forever.
|
|
3388
|
+
if (::AllData::all_object_attr.find(typ_id) != ::AllData::all_object_attr.end()) {
|
|
3389
|
+
::AllData::all_object_attr.erase(typ_id);
|
|
3390
|
+
}
|
|
3391
|
+
if (::AllData::all_object_mutex.find(typ_id) != ::AllData::all_object_mutex.end()) {
|
|
3392
|
+
::AllData::all_object_mutex.erase(typ_id);
|
|
3393
|
+
}
|
|
3315
3394
|
clear_obj(typ_id);
|
|
3316
3395
|
}
|
|
3317
3396
|
|
|
@@ -3521,21 +3600,6 @@ postprocess_for_PrivateAttr(PyObject* /*self*/, PyObject* args) noexcept
|
|
|
3521
3600
|
Py_RETURN_NONE;
|
|
3522
3601
|
}
|
|
3523
3602
|
|
|
3524
|
-
static void
|
|
3525
|
-
register_finalize(PyObject* cls) noexcept
|
|
3526
|
-
{
|
|
3527
|
-
Py_ssize_t original_ref = Py_REFCNT(cls);
|
|
3528
|
-
PyTypeObject* base = Py_TYPE(cls)->tp_base;
|
|
3529
|
-
while (base && base->tp_finalize == register_finalize) {
|
|
3530
|
-
base = base->tp_base;
|
|
3531
|
-
}
|
|
3532
|
-
if (base && base->tp_finalize) base->tp_finalize(cls);
|
|
3533
|
-
if (Py_REFCNT(cls) != original_ref) {
|
|
3534
|
-
return;
|
|
3535
|
-
}
|
|
3536
|
-
PrivateAttrType_finalize(cls);
|
|
3537
|
-
}
|
|
3538
|
-
|
|
3539
3603
|
#define METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(name) do {\
|
|
3540
3604
|
if (::AllData::all_type_##name.find(id) != ::AllData::all_type_##name.end()) {\
|
|
3541
3605
|
::AllData::all_type_##name.erase(id);\
|
|
@@ -3551,6 +3615,13 @@ metaclass_weakref_callback(PyObject* self, PyObject* /*weakref*/) noexcept
|
|
|
3551
3615
|
}
|
|
3552
3616
|
uintptr_t id = (uintptr_t)pointer;
|
|
3553
3617
|
|
|
3618
|
+
// Clean up the class-level AllData owned by this registered metaclass
|
|
3619
|
+
// (type_attr_dict / all_type_subclass_attr / object buckets / caches).
|
|
3620
|
+
// This replaces the old tp_finalize hook (register_finalize) which has
|
|
3621
|
+
// been removed: a weakref callback fires on every death path (refcount and
|
|
3622
|
+
// cyclic GC) without making the metaclass "finalizable".
|
|
3623
|
+
PrivateAttrType_finalize((PyObject*)id);
|
|
3624
|
+
|
|
3554
3625
|
std::unique_lock lock(::AllData::all_register_new_metaclass_mutex);
|
|
3555
3626
|
|
|
3556
3627
|
auto it = ::AllData::all_register_type_weak_ref.find(id);
|
|
@@ -3562,7 +3633,7 @@ metaclass_weakref_callback(PyObject* self, PyObject* /*weakref*/) noexcept
|
|
|
3562
3633
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(setattro);
|
|
3563
3634
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(traverse);
|
|
3564
3635
|
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(clear);
|
|
3565
|
-
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(
|
|
3636
|
+
METACLASS_WEAKREF_CALLBACK_REMOVE_ITEM(del);
|
|
3566
3637
|
|
|
3567
3638
|
Py_RETURN_NONE;
|
|
3568
3639
|
}
|
|
@@ -3654,48 +3725,37 @@ register_metaclass_head(PyObject* metaclass) noexcept
|
|
|
3654
3725
|
}
|
|
3655
3726
|
::AllData::all_register_type_weak_ref[id] = ref;
|
|
3656
3727
|
PyTypeObject* mt = (PyTypeObject*)metaclass;
|
|
3728
|
+
// Save the ORIGINAL slots before wrapping. Always resolve through the
|
|
3729
|
+
// get_type_need_tp_* walkers so a delegating default (CPython's
|
|
3730
|
+
// subtype_traverse/subtype_clear, which a plain heap metaclass inherits)
|
|
3731
|
+
// is never stored as the "original". The walkers skip those defaults and
|
|
3732
|
+
// fall back to PyType_Type.tp_* (the real implementation for a type
|
|
3733
|
+
// object). If an entry already exists for this id, keep it (idempotent).
|
|
3657
3734
|
// save and replace tp_getattro
|
|
3658
3735
|
if (mt->tp_getattro != PrivateAttrType_getattr) {
|
|
3659
|
-
if (
|
|
3736
|
+
if (::AllData::all_type_getattro.find(id) == ::AllData::all_type_getattro.end()) {
|
|
3660
3737
|
::AllData::all_type_getattro[id] = get_type_need_tp_getattro(mt);
|
|
3661
|
-
} else {
|
|
3662
|
-
::AllData::all_type_getattro[id] = mt->tp_getattro;
|
|
3663
3738
|
}
|
|
3664
3739
|
mt->tp_getattro = PrivateAttrType_getattr;
|
|
3665
3740
|
}
|
|
3666
3741
|
// save and replace tp_setattro
|
|
3667
3742
|
if (mt->tp_setattro != PrivateAttrType_setattr) {
|
|
3668
|
-
if (
|
|
3743
|
+
if (::AllData::all_type_setattro.find(id) == ::AllData::all_type_setattro.end()) {
|
|
3669
3744
|
::AllData::all_type_setattro[id] = get_type_need_tp_setattro(mt);
|
|
3670
|
-
} else {
|
|
3671
|
-
::AllData::all_type_setattro[id] = mt->tp_setattro;
|
|
3672
3745
|
}
|
|
3673
3746
|
mt->tp_setattro = PrivateAttrType_setattr;
|
|
3674
3747
|
}
|
|
3675
|
-
// save and replace tp_finalize
|
|
3676
|
-
if (mt->tp_finalize != register_finalize) {
|
|
3677
|
-
if (!mt->tp_finalize && ::AllData::all_type_finalize.find(id) == ::AllData::all_type_finalize.end()) {
|
|
3678
|
-
::AllData::all_type_finalize[id] = get_type_need_tp_finalize(mt);
|
|
3679
|
-
} else {
|
|
3680
|
-
::AllData::all_type_finalize[id] = mt->tp_finalize;
|
|
3681
|
-
}
|
|
3682
|
-
mt->tp_finalize = register_finalize;
|
|
3683
|
-
}
|
|
3684
3748
|
// save and replace tp_traverse
|
|
3685
3749
|
if (mt->tp_traverse != PrivateAttrType_tp_traverse) {
|
|
3686
|
-
if (
|
|
3750
|
+
if (::AllData::all_type_traverse.find(id) == ::AllData::all_type_traverse.end()) {
|
|
3687
3751
|
::AllData::all_type_traverse[id] = get_type_need_tp_traverse(mt);
|
|
3688
|
-
} else {
|
|
3689
|
-
::AllData::all_type_traverse[id] = mt->tp_traverse;
|
|
3690
3752
|
}
|
|
3691
3753
|
mt->tp_traverse = PrivateAttrType_tp_traverse;
|
|
3692
3754
|
}
|
|
3693
3755
|
// save and replace tp_clear
|
|
3694
3756
|
if (mt->tp_clear != PrivateAttrType_tp_clear) {
|
|
3695
|
-
if (
|
|
3757
|
+
if (::AllData::all_type_clear.find(id) == ::AllData::all_type_clear.end()) {
|
|
3696
3758
|
::AllData::all_type_clear[id] = get_type_need_tp_clear(mt);
|
|
3697
|
-
} else {
|
|
3698
|
-
::AllData::all_type_clear[id] = mt->tp_clear;
|
|
3699
3759
|
}
|
|
3700
3760
|
mt->tp_clear = PrivateAttrType_tp_clear;
|
|
3701
3761
|
}
|
|
@@ -3744,43 +3804,31 @@ ensure_metaclass_tp(PyObject* /*self*/, PyObject* metaclass) noexcept
|
|
|
3744
3804
|
uintptr_t id = (uintptr_t)metaclass;
|
|
3745
3805
|
if (::AllData::all_register_type_weak_ref.find(id) != ::AllData::all_register_type_weak_ref.end()) {
|
|
3746
3806
|
PyTypeObject* mt = (PyTypeObject*)metaclass;
|
|
3807
|
+
// Same as register_metaclass_head: always resolve the ORIGINAL slots
|
|
3808
|
+
// through the get_type_need_tp_* walkers (never store a delegating
|
|
3809
|
+
// default such as subtype_traverse/subtype_clear). Idempotent when an
|
|
3810
|
+
// entry already exists.
|
|
3747
3811
|
if (mt->tp_getattro != PrivateAttrType_getattr) {
|
|
3748
|
-
if (
|
|
3812
|
+
if (::AllData::all_type_getattro.find(id) == ::AllData::all_type_getattro.end()) {
|
|
3749
3813
|
::AllData::all_type_getattro[id] = get_type_need_tp_getattro(mt);
|
|
3750
|
-
} else {
|
|
3751
|
-
::AllData::all_type_getattro[id] = mt->tp_getattro;
|
|
3752
3814
|
}
|
|
3753
3815
|
mt->tp_getattro = PrivateAttrType_getattr;
|
|
3754
3816
|
}
|
|
3755
3817
|
if (mt->tp_setattro != PrivateAttrType_setattr) {
|
|
3756
|
-
if (
|
|
3818
|
+
if (::AllData::all_type_setattro.find(id) == ::AllData::all_type_setattro.end()) {
|
|
3757
3819
|
::AllData::all_type_setattro[id] = get_type_need_tp_setattro(mt);
|
|
3758
|
-
} else {
|
|
3759
|
-
::AllData::all_type_setattro[id] = mt->tp_setattro;
|
|
3760
3820
|
}
|
|
3761
3821
|
mt->tp_setattro = PrivateAttrType_setattr;
|
|
3762
3822
|
}
|
|
3763
|
-
if (mt->tp_finalize != register_finalize) {
|
|
3764
|
-
if (!mt->tp_finalize && ::AllData::all_type_finalize.find(id) == ::AllData::all_type_finalize.end()) {
|
|
3765
|
-
::AllData::all_type_finalize[id] = get_type_need_tp_finalize(mt);
|
|
3766
|
-
} else {
|
|
3767
|
-
::AllData::all_type_finalize[id] = mt->tp_finalize;
|
|
3768
|
-
}
|
|
3769
|
-
mt->tp_finalize = register_finalize;
|
|
3770
|
-
}
|
|
3771
3823
|
if (mt->tp_traverse != PrivateAttrType_tp_traverse) {
|
|
3772
|
-
if (
|
|
3824
|
+
if (::AllData::all_type_traverse.find(id) == ::AllData::all_type_traverse.end()) {
|
|
3773
3825
|
::AllData::all_type_traverse[id] = get_type_need_tp_traverse(mt);
|
|
3774
|
-
} else {
|
|
3775
|
-
::AllData::all_type_traverse[id] = mt->tp_traverse;
|
|
3776
3826
|
}
|
|
3777
3827
|
mt->tp_traverse = PrivateAttrType_tp_traverse;
|
|
3778
3828
|
}
|
|
3779
3829
|
if (mt->tp_clear != PrivateAttrType_tp_clear) {
|
|
3780
|
-
if (
|
|
3830
|
+
if (::AllData::all_type_clear.find(id) == ::AllData::all_type_clear.end()) {
|
|
3781
3831
|
::AllData::all_type_clear[id] = get_type_need_tp_clear(mt);
|
|
3782
|
-
} else {
|
|
3783
|
-
::AllData::all_type_clear[id] = mt->tp_clear;
|
|
3784
3832
|
}
|
|
3785
3833
|
mt->tp_clear = PrivateAttrType_tp_clear;
|
|
3786
3834
|
}
|
|
@@ -4133,6 +4181,12 @@ atexit_register_clean_func(void) noexcept
|
|
|
4133
4181
|
PyMODINIT_FUNC
|
|
4134
4182
|
PyInit_private_attribute(void) noexcept
|
|
4135
4183
|
{
|
|
4184
|
+
// check if AllData::store_module_self has first value
|
|
4185
|
+
if (AllData::store_module_self.size() >= 1) {
|
|
4186
|
+
PyObject* m = AllData::store_module_self[0];
|
|
4187
|
+
Py_INCREF(m);
|
|
4188
|
+
return m;
|
|
4189
|
+
}
|
|
4136
4190
|
if (init_all_slots() <0 ||
|
|
4137
4191
|
atexit_register_clean_func() < 0 ||
|
|
4138
4192
|
PyType_Ready(&PrivateWrapType) < 0 ||
|
|
@@ -4142,19 +4196,28 @@ PyInit_private_attribute(void) noexcept
|
|
|
4142
4196
|
PyType_Ready(&PrivateTempType) < 0) {
|
|
4143
4197
|
return NULL;
|
|
4144
4198
|
}
|
|
4199
|
+
// Eagerly create PrivateAttrBase so that CPython's subtype_traverse /
|
|
4200
|
+
// subtype_clear pointers are captured at import time (see
|
|
4201
|
+
// PrivateAttrCaptureGuard and ::AllData::captured_subtype_*), instead of
|
|
4202
|
+
// waiting for the first lazy attribute access to the module.
|
|
4203
|
+
PyObject* private_attr_base = PrivateModule_get_PrivateAttrBase(NULL, NULL);
|
|
4204
|
+
if (!private_attr_base) {
|
|
4205
|
+
return NULL;
|
|
4206
|
+
}
|
|
4145
4207
|
PyObject* all = PyList_New(0);
|
|
4146
4208
|
if (!all) {
|
|
4209
|
+
Py_DECREF(private_attr_base);
|
|
4147
4210
|
return NULL;
|
|
4148
4211
|
}
|
|
4149
4212
|
PyObject* m = PyModule_Create(&def);
|
|
4150
4213
|
if (!m) {
|
|
4151
4214
|
Py_DECREF(all);
|
|
4215
|
+
Py_DECREF(private_attr_base);
|
|
4152
4216
|
return NULL;
|
|
4153
4217
|
}
|
|
4154
4218
|
#ifdef Py_GIL_DISABLED
|
|
4155
4219
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
4156
4220
|
#endif
|
|
4157
|
-
|
|
4158
4221
|
PyModule_AddObject(m, "__all__", all);
|
|
4159
4222
|
/* all contains:
|
|
4160
4223
|
* PrivateWrapProxy
|
|
@@ -4176,16 +4239,8 @@ PyInit_private_attribute(void) noexcept
|
|
|
4176
4239
|
PyList_AppendString(all, "ensure_metaclass");
|
|
4177
4240
|
Py_SET_TYPE(m, &PrivateModuleType);
|
|
4178
4241
|
|
|
4179
|
-
// Eagerly create PrivateAttrBase so that CPython's subtype_traverse /
|
|
4180
|
-
// subtype_clear pointers are captured at import time (see
|
|
4181
|
-
// PrivateAttrCaptureGuard and ::AllData::captured_subtype_*), instead of
|
|
4182
|
-
// waiting for the first lazy attribute access to the module.
|
|
4183
|
-
PyObject* private_attr_base = PrivateModule_get_PrivateAttrBase(NULL, NULL);
|
|
4184
|
-
if (!private_attr_base) {
|
|
4185
|
-
Py_DECREF(m);
|
|
4186
|
-
return NULL;
|
|
4187
|
-
}
|
|
4188
4242
|
Py_DECREF(private_attr_base);
|
|
4243
|
+
AllData::store_module_self.push_back(m); // store module self
|
|
4189
4244
|
|
|
4190
4245
|
return m;
|
|
4191
4246
|
}
|
{private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6/private_attribute_cpp.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: private_attribute_cpp
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.6
|
|
4
4
|
Summary: A Python package that provides a way to define private attributes in C++ implementation.
|
|
5
5
|
Home-page: https://github.com/Locked-chess-official/private_attribute_cpp
|
|
6
6
|
Author: HuangHaoHua
|
|
@@ -12,6 +12,7 @@ private_attribute_cpp.egg-info/not-zip-safe
|
|
|
12
12
|
private_attribute_cpp.egg-info/top_level.txt
|
|
13
13
|
tests/test_attr_classify_owner.py
|
|
14
14
|
tests/test_class_private_attrs.py
|
|
15
|
+
tests/test_gc_collect.py
|
|
15
16
|
tests/test_private_abc.py
|
|
16
17
|
tests/test_private_attrs.py
|
|
17
18
|
tests/test_private_func.py
|
|
@@ -18,7 +18,7 @@ readme = open('README.md').read()
|
|
|
18
18
|
|
|
19
19
|
setup(
|
|
20
20
|
name='private_attribute_cpp',
|
|
21
|
-
version='2.1.
|
|
21
|
+
version='2.1.6',
|
|
22
22
|
author="HuangHaoHua",
|
|
23
23
|
author_email="13140752715@example.com",
|
|
24
24
|
description='A Python package that provides a way to define private attributes in C++ implementation.',
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""GC regression tests for the metaclass type_traverse/type_clear fix.
|
|
2
|
+
|
|
3
|
+
Validates that class objects created by the private-attribute machinery are
|
|
4
|
+
actually reclaimed by the cyclic GC (previously they leaked because the
|
|
5
|
+
metaclass traverse resolved to the instance-level delegating defaults instead
|
|
6
|
+
of CPython's real type_traverse/type_clear).
|
|
7
|
+
|
|
8
|
+
Run: PYTHONPATH=. python test_gc_collect.py
|
|
9
|
+
"""
|
|
10
|
+
import gc
|
|
11
|
+
import sys
|
|
12
|
+
import unittest
|
|
13
|
+
import weakref
|
|
14
|
+
|
|
15
|
+
import private_attribute
|
|
16
|
+
from private_attribute import PrivateAttrBase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_class(i, name="C"):
|
|
20
|
+
class C(PrivateAttrBase):
|
|
21
|
+
__private_attrs__ = ("_x",)
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self._x = i
|
|
24
|
+
def get(self):
|
|
25
|
+
return self._x
|
|
26
|
+
C.__name__ = name
|
|
27
|
+
return C
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def alive_after_del_collect(maker, n):
|
|
31
|
+
"""Create n classes, drop them, collect; return how many are still alive."""
|
|
32
|
+
refs = []
|
|
33
|
+
for i in range(n):
|
|
34
|
+
C = maker(i, f"Probe{i}")
|
|
35
|
+
refs.append(weakref.ref(C))
|
|
36
|
+
del C
|
|
37
|
+
for _ in range(3):
|
|
38
|
+
gc.collect()
|
|
39
|
+
return sum(1 for w in refs if w() is not None)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestClassCollect(unittest.TestCase):
|
|
43
|
+
def test_empty_class_collected(self):
|
|
44
|
+
def maker(i, name):
|
|
45
|
+
class E(PrivateAttrBase):
|
|
46
|
+
pass
|
|
47
|
+
E.__name__ = name
|
|
48
|
+
return E
|
|
49
|
+
self.assertEqual(alive_after_del_collect(maker, 20), 0)
|
|
50
|
+
|
|
51
|
+
def test_class_with_private_attrs_collected(self):
|
|
52
|
+
self.assertEqual(alive_after_del_collect(make_class, 20), 0)
|
|
53
|
+
|
|
54
|
+
def test_class_with_private_value_freed(self):
|
|
55
|
+
class Holder:
|
|
56
|
+
pass
|
|
57
|
+
refs = []
|
|
58
|
+
for i in range(20):
|
|
59
|
+
h = Holder()
|
|
60
|
+
class H(PrivateAttrBase):
|
|
61
|
+
__private_attrs__ = ("_cv",)
|
|
62
|
+
_cv = h
|
|
63
|
+
refs.append(weakref.ref(h))
|
|
64
|
+
del h, H
|
|
65
|
+
for _ in range(3):
|
|
66
|
+
gc.collect()
|
|
67
|
+
self.assertEqual(sum(1 for w in refs if w() is not None), 0)
|
|
68
|
+
|
|
69
|
+
def test_mutual_class_cycle_collected(self):
|
|
70
|
+
def cyc():
|
|
71
|
+
class A(PrivateAttrBase):
|
|
72
|
+
__private_attrs__ = ("_ref",)
|
|
73
|
+
_ref = None
|
|
74
|
+
def set_ref(cls, v):
|
|
75
|
+
cls._ref = v
|
|
76
|
+
class B(PrivateAttrBase):
|
|
77
|
+
__private_attrs__ = ("_ref",)
|
|
78
|
+
_ref = None
|
|
79
|
+
def set_ref(cls, v):
|
|
80
|
+
cls._ref = v
|
|
81
|
+
A.set_ref(A, B)
|
|
82
|
+
B.set_ref(B, A)
|
|
83
|
+
wa = weakref.ref(A)
|
|
84
|
+
wb = weakref.ref(B)
|
|
85
|
+
del A, B
|
|
86
|
+
for _ in range(3):
|
|
87
|
+
gc.collect()
|
|
88
|
+
return wa() is not None, wb() is not None
|
|
89
|
+
a, b = cyc()
|
|
90
|
+
self.assertFalse(a)
|
|
91
|
+
self.assertFalse(b)
|
|
92
|
+
|
|
93
|
+
def test_self_referencing_class_collected(self):
|
|
94
|
+
def cyc():
|
|
95
|
+
class S(PrivateAttrBase):
|
|
96
|
+
__private_attrs__ = ("_self_ref",)
|
|
97
|
+
_self_ref = None
|
|
98
|
+
def set_self(cls, v):
|
|
99
|
+
cls._self_ref = v
|
|
100
|
+
S.set_self(S, S)
|
|
101
|
+
w = weakref.ref(S)
|
|
102
|
+
del S
|
|
103
|
+
for _ in range(3):
|
|
104
|
+
gc.collect()
|
|
105
|
+
return w() is not None
|
|
106
|
+
self.assertFalse(cyc())
|
|
107
|
+
|
|
108
|
+
def test_nested_class_collected(self):
|
|
109
|
+
def nested():
|
|
110
|
+
class Outer(PrivateAttrBase):
|
|
111
|
+
__private_attrs__ = ("_inner",)
|
|
112
|
+
def make(self):
|
|
113
|
+
class Inner(PrivateAttrBase):
|
|
114
|
+
__private_attrs__ = ("_v",)
|
|
115
|
+
def __init__(self):
|
|
116
|
+
self._v = 1
|
|
117
|
+
self._inner = Inner
|
|
118
|
+
return Inner
|
|
119
|
+
o = Outer()
|
|
120
|
+
Inner = o.make()
|
|
121
|
+
wi = weakref.ref(Inner)
|
|
122
|
+
wo = weakref.ref(o)
|
|
123
|
+
del o, Inner
|
|
124
|
+
for _ in range(3):
|
|
125
|
+
gc.collect()
|
|
126
|
+
return wi() is not None, wo() is not None
|
|
127
|
+
inner, outer = nested()
|
|
128
|
+
self.assertFalse(inner)
|
|
129
|
+
self.assertFalse(outer)
|
|
130
|
+
|
|
131
|
+
def test_private_attr_semantics_still_work(self):
|
|
132
|
+
class C(PrivateAttrBase):
|
|
133
|
+
__private_attrs__ = ("_x",)
|
|
134
|
+
def __init__(self):
|
|
135
|
+
self._x = 7
|
|
136
|
+
def get(self):
|
|
137
|
+
return self._x
|
|
138
|
+
c = C()
|
|
139
|
+
self.assertEqual(c.get(), 7)
|
|
140
|
+
with self.assertRaises(AttributeError):
|
|
141
|
+
c._x
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_attr_classify_owner.py
RENAMED
|
File without changes
|
{private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_class_private_attrs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{private_attribute_cpp-2.1.4 → private_attribute_cpp-2.1.6}/tests/test_private_inheritance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|