Skip to content

Commit 8d59c51

Browse files
authored
Merge pull request #280 from brandtbucher/recursion-depth
Restore compatibility with Python 3.11
2 parents 3e534d6 + 6ee7675 commit 8d59c51

File tree

4 files changed

+87
-15
lines changed

4 files changed

+87
-15
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
runs-on: ${{ matrix.os }}
2626
strategy:
2727
matrix:
28-
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10"]
28+
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10", "3.11.0-alpha.4"]
2929
os: [ubuntu-latest, macos-latest]
3030
steps:
3131
- uses: actions/checkout@v2

src/greenlet/greenlet_cpython_compat.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,24 @@ We have to save and restore this as well.
4747
# define GREENLET_USE_CFRAME 0
4848
#endif
4949

50+
#if PY_VERSION_HEX >= 0x30B00A4
51+
/*
52+
Greenlet won't compile on anything older than Python 3.11 alpha 4 (see
53+
https://bugs.python.org/issue46090). Summary of breaking internal changes:
54+
- Python 3.11 alpha 1 changed how frame objects are represented internally.
55+
- https://github.com/python/cpython/pull/30122
56+
- Python 3.11 alpha 3 changed how recursion limits are stored.
57+
- https://github.com/python/cpython/pull/29524
58+
- Python 3.11 alpha 4 changed how exception state is stored. It also includes a
59+
change to help greenlet save and restore the interpreter frame "data stack".
60+
- https://github.com/python/cpython/pull/30122
61+
- https://github.com/python/cpython/pull/30234
62+
*/
63+
# define GREENLET_PY311 1
64+
#else
65+
# define GREENLET_PY311 0
66+
#endif
67+
5068
#ifndef Py_SET_REFCNT
5169
/* Py_REFCNT and Py_SIZE macros are converted to functions
5270
https://bugs.python.org/issue39573 */

src/greenlet/greenlet_greenlet.hpp

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ namespace greenlet
3131
_PyErr_StackItem* exc_info;
3232
_PyErr_StackItem exc_state;
3333
#else
34-
OwnedObject exc_type;
3534
OwnedObject exc_value;
35+
#if !GREENLET_PY311
36+
OwnedObject exc_type;
3637
OwnedObject exc_traceback;
38+
#endif
3739
#endif
3840
public:
3941
ExceptionState();
@@ -133,6 +135,12 @@ namespace greenlet
133135
int use_tracing;
134136
#endif
135137
int recursion_depth;
138+
#if GREENLET_PY311
139+
_interpreter_frame *current_frame;
140+
_PyStackChunk *datastack_chunk;
141+
PyObject **datastack_top;
142+
PyObject **datastack_limit;
143+
#endif
136144

137145
public:
138146
PythonState();
@@ -640,63 +648,79 @@ void ExceptionState::operator>>(PyThreadState *const tstate) G_NOEXCEPT
640648
void ExceptionState::clear() G_NOEXCEPT
641649
{
642650
this->exc_info = nullptr;
643-
this->exc_state.exc_type = nullptr;
644651
this->exc_state.exc_value = nullptr;
652+
#if !GREENLET_PY311
653+
this->exc_state.exc_type = nullptr;
645654
this->exc_state.exc_traceback = nullptr;
655+
#endif
646656
this->exc_state.previous_item = nullptr;
647657
}
648658

649659
int ExceptionState::tp_traverse(visitproc visit, void* arg) G_NOEXCEPT
650660
{
651-
Py_VISIT(this->exc_state.exc_type);
652661
Py_VISIT(this->exc_state.exc_value);
662+
#if !GREENLET_PY311
663+
Py_VISIT(this->exc_state.exc_type);
653664
Py_VISIT(this->exc_state.exc_traceback);
665+
#endif
654666
return 0;
655667
}
656668

657669
void ExceptionState::tp_clear() G_NOEXCEPT
658670
{
659-
Py_CLEAR(this->exc_state.exc_type);
660671
Py_CLEAR(this->exc_state.exc_value);
672+
#if !GREENLET_PY311
673+
Py_CLEAR(this->exc_state.exc_type);
661674
Py_CLEAR(this->exc_state.exc_traceback);
675+
#endif
662676
}
663677
#else
664678
// ********** Python 3.6 and below ********
665679
void ExceptionState::operator<<(const PyThreadState *const tstate) G_NOEXCEPT
666680
{
667-
this->exc_type.steal(tstate->exc_type);
668681
this->exc_value.steal(tstate->exc_value);
682+
#if !GREENLET_PY311
683+
this->exc_type.steal(tstate->exc_type);
669684
this->exc_traceback.steal(tstate->exc_traceback);
685+
#endif
670686
}
671687

672688
void ExceptionState::operator>>(PyThreadState *const tstate) G_NOEXCEPT
673689
{
674-
tstate->exc_type <<= this->exc_type;
675690
tstate->exc_value <<= this->exc_value;
691+
#if !GREENLET_PY311
692+
tstate->exc_type <<= this->exc_type;
676693
tstate->exc_traceback <<= this->exc_traceback;
694+
#endif
677695
this->clear();
678696
}
679697

680698
void ExceptionState::clear() G_NOEXCEPT
681699
{
682-
this->exc_type = nullptr;
683700
this->exc_value = nullptr;
701+
#if !GREENLET_PY311
702+
this->exc_type = nullptr;
684703
this->exc_traceback = nullptr;
704+
#endif
685705
}
686706

687707
int ExceptionState::tp_traverse(visitproc visit, void* arg) G_NOEXCEPT
688708
{
689-
Py_VISIT(this->exc_type.borrow());
690709
Py_VISIT(this->exc_value.borrow());
710+
#if !GREENLET_PY311
711+
Py_VISIT(this->exc_type.borrow());
691712
Py_VISIT(this->exc_traceback.borrow());
713+
#endif
692714
return 0;
693715
}
694716

695717
void ExceptionState::tp_clear() G_NOEXCEPT
696718
{
697-
this->exc_type.CLEAR();
698719
this->exc_value.CLEAR();
720+
#if !GREENLET_PY311
721+
this->exc_type.CLEAR();
699722
this->exc_traceback.CLEAR();
723+
#endif
700724
}
701725
#endif
702726

@@ -710,6 +734,12 @@ PythonState::PythonState()
710734
,use_tracing(0)
711735
#endif
712736
,recursion_depth(0)
737+
#if GREENLET_PY311
738+
,current_frame(nullptr)
739+
,datastack_chunk(nullptr)
740+
,datastack_top(nullptr)
741+
,datastack_limit(nullptr)
742+
#endif
713743
{
714744
#if GREENLET_USE_CFRAME
715745
/*
@@ -767,8 +797,6 @@ PythonState::PythonState()
767797

768798
void PythonState::operator<<(const PyThreadState *const tstate) G_NOEXCEPT
769799
{
770-
this->recursion_depth = tstate->recursion_depth;
771-
this->_top_frame.steal(tstate->frame);
772800
#if GREENLET_PY37
773801
this->_context.steal(tstate->context);
774802
#endif
@@ -786,12 +814,23 @@ void PythonState::operator<<(const PyThreadState *const tstate) G_NOEXCEPT
786814
this->cframe = tstate->cframe;
787815
this->use_tracing = tstate->cframe->use_tracing;
788816
#endif
817+
#if GREENLET_PY311
818+
this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
819+
this->current_frame = tstate->cframe->current_frame;
820+
this->datastack_chunk = tstate->datastack_chunk;
821+
this->datastack_top = tstate->datastack_top;
822+
this->datastack_limit = tstate->datastack_limit;
823+
PyFrameObject *frame = PyThreadState_GetFrame((PyThreadState *)tstate);
824+
Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new reference.
825+
this->_top_frame.steal(frame);
826+
#else
827+
this->recursion_depth = tstate->recursion_depth;
828+
this->_top_frame.steal(tstate->frame);
829+
#endif
789830
}
790831

791832
void PythonState::operator>>(PyThreadState *const tstate) G_NOEXCEPT
792833
{
793-
tstate->recursion_depth = this->recursion_depth;
794-
tstate->frame = this->_top_frame.relinquish_ownership();
795834
#if GREENLET_PY37
796835
tstate->context = this->_context.relinquish_ownership();
797836
/* Incrementing this value invalidates the contextvars cache,
@@ -808,6 +847,17 @@ void PythonState::operator>>(PyThreadState *const tstate) G_NOEXCEPT
808847
*/
809848
tstate->cframe->use_tracing = this->use_tracing;
810849
#endif
850+
#if GREENLET_PY311
851+
tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth;
852+
tstate->cframe->current_frame = this->current_frame;
853+
tstate->datastack_chunk = this->datastack_chunk;
854+
tstate->datastack_top = this->datastack_top;
855+
tstate->datastack_limit = this->datastack_limit;
856+
this->_top_frame.relinquish_ownership();
857+
#else
858+
tstate->frame = this->_top_frame.relinquish_ownership();
859+
tstate->recursion_depth = this->recursion_depth;
860+
#endif
811861
}
812862

813863
void PythonState::will_switch_from(PyThreadState *const origin_tstate) G_NOEXCEPT
@@ -824,7 +874,11 @@ void PythonState::will_switch_from(PyThreadState *const origin_tstate) G_NOEXCEP
824874
void PythonState::set_initial_state(const PyThreadState* const tstate) G_NOEXCEPT
825875
{
826876
this->_top_frame = nullptr;
877+
#if GREENLET_PY311
878+
this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
879+
#else
827880
this->recursion_depth = tstate->recursion_depth;
881+
#endif
828882
}
829883
// TODO: Better state management about when we own the top frame.
830884
int PythonState::tp_traverse(visitproc visit, void* arg, bool own_top_frame) G_NOEXCEPT

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
envlist =
3-
py27,py35,py36,py37,py38,py39,py310,py27-ns,py310-ns,docs
3+
py27,py35,py36,py37,py38,py39,py310,py27-ns,py310-ns,py311,py311-ns,docs
44

55
[testenv]
66
setenv =

0 commit comments

Comments
 (0)