@@ -146,10 +146,10 @@ namespace greenlet
146
146
int recursion_depth;
147
147
int trash_delete_nesting;
148
148
#if GREENLET_PY311
149
- _PyInterpreterFrame * current_frame;
150
- _PyStackChunk * datastack_chunk;
151
- PyObject ** datastack_top;
152
- PyObject ** datastack_limit;
149
+ _PyInterpreterFrame* current_frame;
150
+ _PyStackChunk* datastack_chunk;
151
+ PyObject** datastack_top;
152
+ PyObject** datastack_limit;
153
153
#endif
154
154
155
155
public:
@@ -170,6 +170,7 @@ namespace greenlet
170
170
void set_new_cframe (_PyCFrame& frame) G_NOEXCEPT;
171
171
#endif
172
172
void will_switch_from (PyThreadState *const origin_tstate) G_NOEXCEPT;
173
+ void did_finish (PyThreadState* tstate) G_NOEXCEPT;
173
174
};
174
175
175
176
class StackState
@@ -213,9 +214,13 @@ namespace greenlet
213
214
inline intptr_t stack_saved () const G_NOEXCEPT;
214
215
inline char * stack_start () const G_NOEXCEPT;
215
216
static inline StackState make_main () G_NOEXCEPT;
217
+ #ifdef GREENLET_USE_STDIO
216
218
friend std::ostream& operator <<(std::ostream& os, const StackState& s);
219
+ #endif
217
220
};
221
+ #ifdef GREENLET_USE_STDIO
218
222
std::ostream& operator <<(std::ostream& os, const StackState& s);
223
+ #endif
219
224
220
225
class SwitchingArgs
221
226
{
@@ -933,14 +938,97 @@ void PythonState::set_new_cframe(_PyCFrame& frame) G_NOEXCEPT
933
938
}
934
939
#endif
935
940
936
-
937
941
const PythonState::OwnedFrame& PythonState::top_frame () const G_NOEXCEPT
938
942
{
939
943
return this ->_top_frame ;
940
944
}
941
945
946
+ void PythonState::did_finish (PyThreadState* tstate) G_NOEXCEPT
947
+ {
948
+ #if GREENLET_PY311
949
+ // See https://github.com/gevent/gevent/issues/1924 and
950
+ // https://github.com/python-greenlet/greenlet/issues/328. In
951
+ // short, Python 3.11 allocates memory for frames as a sort of
952
+ // linked list that's kept as part of PyThreadState in the
953
+ // ``datastack_chunk`` member and friends. These are saved and
954
+ // restored as part of switching greenlets.
955
+ //
956
+ // When we initially switch to a greenlet, we set those to NULL.
957
+ // That causes the frame management code to treat this like a
958
+ // brand new thread and start a fresh list of chunks, beginning
959
+ // with a new "root" chunk. As we make calls in this greenlet,
960
+ // those chunks get added, and as calls return, they get popped.
961
+ // But the frame code (pystate.c) is careful to make sure that the
962
+ // root chunk never gets popped.
963
+ //
964
+ // Thus, when a greenlet exits for the last time, there will be at
965
+ // least a single root chunk that we must be responsible for
966
+ // deallocating.
967
+ //
968
+ // The complex part is that these chunks are allocated and freed
969
+ // using ``_PyObject_VirtualAlloc``/``Free``. Those aren't public
970
+ // functions, and they aren't exported for linking. It so happens
971
+ // that we know they are just thin wrappers around the Arena
972
+ // allocator, so we can use that directly to deallocate in a
973
+ // compatible way.
974
+ //
975
+ // CAUTION: Check this implementation detail on every major version.
976
+ //
977
+ // It might be nice to be able to do this in our destructor, but
978
+ // can we be sure that no one else is using that memory? Plus, as
979
+ // described below, our pointers may not even be valid anymore. As
980
+ // a special case, there is one time that we know we can do this,
981
+ // and that's from the destructor of the associated UserGreenlet
982
+ // (NOT main greenlet)
983
+ PyObjectArenaAllocator alloc;
984
+ _PyStackChunk* chunk = nullptr ;
985
+ if (tstate) {
986
+ // We really did finish, we can never be switched to again.
987
+ chunk = tstate->datastack_chunk ;
988
+ // Unfortunately, we can't do much sanity checking. Our
989
+ // this->datastack_chunk pointer is out of date (evaluation may
990
+ // have popped down through it already) so we can't verify that
991
+ // we deallocate it. I don't think we can even check datastack_top
992
+ // for the same reason.
993
+
994
+ PyObject_GetArenaAllocator (&alloc);
995
+ tstate->datastack_chunk = nullptr ;
996
+ tstate->datastack_limit = nullptr ;
997
+ tstate->datastack_top = nullptr ;
998
+
999
+ }
1000
+ else if (this ->datastack_chunk ) {
1001
+ // The UserGreenlet (NOT the main greenlet!) is being deallocated. If we're
1002
+ // still holding a stack chunk, it's garbage because we know
1003
+ // we can never switch back to let cPython clean it up.
1004
+ // Because the last time we got switched away from, and we
1005
+ // haven't run since then, we know our chain is valid and can
1006
+ // be dealloced.
1007
+ chunk = this ->datastack_chunk ;
1008
+ PyObject_GetArenaAllocator (&alloc);
1009
+ }
1010
+
1011
+ if (alloc.free && chunk) {
1012
+ // In case the arena mechanism has been torn down already.
1013
+ while (chunk) {
1014
+ _PyStackChunk *prev = chunk->previous ;
1015
+ chunk->previous = nullptr ;
1016
+ alloc.free (alloc.ctx , chunk, chunk->size );
1017
+ chunk = prev;
1018
+ }
1019
+ }
1020
+
1021
+ this ->datastack_chunk = nullptr ;
1022
+ this ->datastack_limit = nullptr ;
1023
+ this ->datastack_top = nullptr ;
1024
+ #endif
1025
+ }
1026
+
1027
+
1028
+
942
1029
943
1030
using greenlet::StackState;
1031
+
944
1032
#ifdef GREENLET_USE_STDIO
945
1033
#include < iostream>
946
1034
using std::cerr;
0 commit comments