|
| 1 | +# Trashcan |
| 2 | + |
| 3 | +注释已经描述得非常清楚, 无需多言. |
| 4 | + |
| 5 | +```c |
| 6 | +//Include/cpython/object.h |
| 7 | + |
| 8 | +/* Trashcan mechanism, thanks to Christian Tismer. |
| 9 | + |
| 10 | +When deallocating a container object, it's possible to trigger an unbounded |
| 11 | +chain of deallocations, as each Py_DECREF in turn drops the refcount on "the |
| 12 | +next" object in the chain to 0. This can easily lead to stack overflows, |
| 13 | +especially in threads (which typically have less stack space to work with). |
| 14 | + |
| 15 | +A container object can avoid this by bracketing the body of its tp_dealloc |
| 16 | +function with a pair of macros: |
| 17 | + |
| 18 | +static void |
| 19 | +mytype_dealloc(mytype *p) |
| 20 | +{ |
| 21 | + ... declarations go here ... |
| 22 | + |
| 23 | + PyObject_GC_UnTrack(p); // must untrack first |
| 24 | + Py_TRASHCAN_BEGIN(p, mytype_dealloc) |
| 25 | + ... The body of the deallocator goes here, including all calls ... |
| 26 | + ... to Py_DECREF on contained objects. ... |
| 27 | + Py_TRASHCAN_END // there should be no code after this |
| 28 | +} |
| 29 | + |
| 30 | +CAUTION: Never return from the middle of the body! If the body needs to |
| 31 | +"get out early", put a label immediately before the Py_TRASHCAN_END |
| 32 | +call, and goto it. Else the call-depth counter (see below) will stay |
| 33 | +above 0 forever, and the trashcan will never get emptied. |
| 34 | + |
| 35 | +How it works: The BEGIN macro increments a call-depth counter. So long |
| 36 | +as this counter is small, the body of the deallocator is run directly without |
| 37 | +further ado. But if the counter gets large, it instead adds p to a list of |
| 38 | +objects to be deallocated later, skips the body of the deallocator, and |
| 39 | +resumes execution after the END macro. The tp_dealloc routine then returns |
| 40 | +without deallocating anything (and so unbounded call-stack depth is avoided). |
| 41 | + |
| 42 | +When the call stack finishes unwinding again, code generated by the END macro |
| 43 | +notices this, and calls another routine to deallocate all the objects that |
| 44 | +may have been added to the list of deferred deallocations. In effect, a |
| 45 | +chain of N deallocations is broken into (N-1)/(PyTrash_UNWIND_LEVEL-1) pieces, |
| 46 | +with the call stack never exceeding a depth of PyTrash_UNWIND_LEVEL. |
| 47 | + |
| 48 | +Since the tp_dealloc of a subclass typically calls the tp_dealloc of the base |
| 49 | +class, we need to ensure that the trashcan is only triggered on the tp_dealloc |
| 50 | +of the actual class being deallocated. Otherwise we might end up with a |
| 51 | +partially-deallocated object. To check this, the tp_dealloc function must be |
| 52 | +passed as second argument to Py_TRASHCAN_BEGIN(). |
| 53 | +*/ |
| 54 | + |
| 55 | +/* This is the old private API, invoked by the macros before 3.2.4. |
| 56 | + Kept for binary compatibility of extensions using the stable ABI. */ |
| 57 | +PyAPI_FUNC(void) _PyTrash_deposit_object(PyObject*); |
| 58 | +PyAPI_FUNC(void) _PyTrash_destroy_chain(void); |
| 59 | + |
| 60 | +/* This is the old private API, invoked by the macros before 3.9. |
| 61 | + Kept for binary compatibility of extensions using the stable ABI. */ |
| 62 | +PyAPI_FUNC(void) _PyTrash_thread_deposit_object(PyObject*); |
| 63 | +PyAPI_FUNC(void) _PyTrash_thread_destroy_chain(void); |
| 64 | + |
| 65 | +/* Forward declarations for PyThreadState */ |
| 66 | +struct _ts; |
| 67 | + |
| 68 | +/* Python 3.9 private API, invoked by the macros below. */ |
| 69 | +PyAPI_FUNC(int) _PyTrash_begin(struct _ts *tstate, PyObject *op); |
| 70 | +PyAPI_FUNC(void) _PyTrash_end(struct _ts *tstate); |
| 71 | + |
| 72 | +#define PyTrash_UNWIND_LEVEL 50 |
| 73 | + |
| 74 | +// 注意仔细看 Py_TRASHCAN_BEGIN_CONDITION 这个宏的定义, 它不是一个完整的语句块, do 语句的 body 没有结束. |
| 75 | +#define Py_TRASHCAN_BEGIN_CONDITION(op, cond) \ |
| 76 | + do { \ |
| 77 | + PyThreadState *_tstate = NULL; \ |
| 78 | + /* If "cond" is false, then _tstate remains NULL and the deallocator \ |
| 79 | + * is run normally without involving the trashcan */ \ |
| 80 | + if (cond) { \ |
| 81 | + _tstate = PyThreadState_GET(); \ |
| 82 | + if (_PyTrash_begin(_tstate, _PyObject_CAST(op))) { \ |
| 83 | + break; \ |
| 84 | + } \ |
| 85 | + } |
| 86 | + /* The body of the deallocator is here. */ |
| 87 | +#define Py_TRASHCAN_END \ |
| 88 | + if (_tstate) { \ |
| 89 | + _PyTrash_end(_tstate); \ |
| 90 | + } \ |
| 91 | + } while (0); |
| 92 | + |
| 93 | +#define Py_TRASHCAN_BEGIN(op, dealloc) \ |
| 94 | + Py_TRASHCAN_BEGIN_CONDITION(op, \ |
| 95 | + Py_TYPE(op)->tp_dealloc == (destructor)(dealloc)) |
| 96 | + |
| 97 | +/* For backwards compatibility, these macros enable the trashcan |
| 98 | + * unconditionally */ |
| 99 | +#define Py_TRASHCAN_SAFE_BEGIN(op) Py_TRASHCAN_BEGIN_CONDITION(op, 1) |
| 100 | +#define Py_TRASHCAN_SAFE_END(op) Py_TRASHCAN_END |
| 101 | +``` |
| 102 | + |
| 103 | +我最喜欢这样的注释, 对新加的某个特性进行详细的描述. 遗憾的是 CPython 的代码中有许多没有详细注释的代码, 对于刚开始阅读 CPython 代码的人来说非常难以理解. |
| 104 | + |
| 105 | +trashcan 的用法可以查看 tuple 的 tpdealloc 函数. |
0 commit comments