Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-greenlet for openSUSE:Factory
checked in at 2024-01-12 23:44:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-greenlet (Old)
and /work/SRC/openSUSE:Factory/.python-greenlet.new.21961 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-greenlet"
Fri Jan 12 23:44:40 2024 rev:48 rq:1138145 version:3.0.3
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-greenlet/python-greenlet.changes
2024-01-09 20:48:40.602089627 +0100
+++
/work/SRC/openSUSE:Factory/.python-greenlet.new.21961/python-greenlet.changes
2024-01-12 23:44:59.769652238 +0100
@@ -1,0 +2,10 @@
+Wed Jan 10 22:14:16 UTC 2024 - Ben Greiner <[email protected]>
+
+- Update to 3.0.3
+ * Python 3.12: Restore the full ability to walk the stack of a
+ suspended greenlet; previously only the innermost frame was
+ exposed. See issue 388. Fix by Joshua Oreman in PR 393.
+- Disable building the docs: Now requires the furo theme, which is
+ not available.
+
+-------------------------------------------------------------------
Old:
----
greenlet-3.0.2.tar.gz
New:
----
greenlet-3.0.3.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-greenlet.spec ++++++
--- /var/tmp/diff_new_pack.pIyojo/_old 2024-01-12 23:45:00.457677420 +0100
+++ /var/tmp/diff_new_pack.pIyojo/_new 2024-01-12 23:45:00.457677420 +0100
@@ -17,9 +17,12 @@
#
+# Requires python-furo
+%bcond_with docs
+
%{?sle15_python_module_pythons}
Name: python-greenlet
-Version: 3.0.2
+Version: 3.0.3
Release: 0
Summary: Lightweight in-process concurrent programming
License: MIT
@@ -27,7 +30,7 @@
URL: https://github.com/python-greenlet/greenlet
Source0:
https://files.pythonhosted.org/packages/source/g/greenlet/greenlet-%{version}.tar.gz
Source9: python-greenlet-rpmlintrc
-BuildRequires: %{python_module devel}
+BuildRequires: %{python_module devel >= 3.7}
BuildRequires: %{python_module objgraph}
BuildRequires: %{python_module pip}
BuildRequires: %{python_module psutil}
@@ -36,7 +39,10 @@
BuildRequires: c++_compiler
BuildRequires: fdupes
BuildRequires: python-rpm-macros
+%if %{with docs}
BuildRequires: python3-Sphinx
+BuildRequires: python3-furo
+%endif
%python_subpackages
%description
@@ -56,13 +62,16 @@
%prep
%autosetup -p1 -n greenlet-%{version}
+sed -i '1{/env python/d}' src/greenlet/tests/test_version.py
%build
export CFLAGS="%{optflags} -fno-tree-dominator-opts -fno-strict-aliasing"
%pyproject_wheel
+%if %{with docs}
export PYTHONPATH=$PWD/src
cd docs && make html && rm _build/html/.buildinfo
+%endif
%install
%pyproject_install
@@ -76,9 +85,12 @@
%files %{python_files}
%doc AUTHORS CHANGES.rst README.rst
+%if %{with docs}
%doc docs/_build/html/
+%endif
%license LICENSE*
-%{python_sitearch}/greenlet*
+%{python_sitearch}/greenlet
+%{python_sitearch}/greenlet-%{version}.dist-info
%files %{python_files devel}
%doc AUTHORS
++++++ greenlet-3.0.2.tar.gz -> greenlet-3.0.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/.github/workflows/tests.yml
new/greenlet-3.0.3/.github/workflows/tests.yml
--- old/greenlet-3.0.2/.github/workflows/tests.yml 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/.github/workflows/tests.yml 2023-12-21
22:57:40.000000000 +0100
@@ -89,6 +89,14 @@
- name: Doctest
run: |
sphinx-build -b doctest -d docs/_build/doctrees2 docs
docs/_build/doctest2
+ - name: Lint
+ if: matrix.python-version == '3.10' && startsWith(runner.os, 'Linux')
+ # We only need to do this on one version.
+ # We do this here rather than a separate job to avoid the compilation
overhead.
+ run: |
+ pip install -U pylint
+ python -m pylint --rcfile=.pylintrc greenlet
+
- name: Publish package to PyPI (mac)
# We cannot 'uses: pypa/[email protected]' because
# that's apparently a container action, and those don't run on
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/.gitignore
new/greenlet-3.0.3/.gitignore
--- old/greenlet-3.0.2/.gitignore 2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/.gitignore 1970-01-01 01:00:00.000000000 +0100
@@ -1,14 +0,0 @@
-*.so
-*.pyd
-*.pyc
-*.pyo
-build/
-dist/
-.tox/
-wheelhouse/
-greenlet.egg-info/
-/docs/_build
-__pycache__/
-/.ropeproject/
-/MANIFEST
-benchmarks/*.json
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/.pylintrc new/greenlet-3.0.3/.pylintrc
--- old/greenlet-3.0.2/.pylintrc 2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/.pylintrc 2023-12-21 22:57:40.000000000 +0100
@@ -1,9 +1,85 @@
[MASTER]
-load-plugins=pylint.extensions.bad_builtin
+load-plugins=pylint.extensions.bad_builtin,
+ pylint.extensions.code_style,
+ pylint.extensions.dict_init_mutate,
+ pylint.extensions.dunder,
+ pylint.extensions.comparison_placement,
+ pylint.extensions.confusing_elif,
+ pylint.extensions.for_any_all,
+ pylint.extensions.consider_refactoring_into_while_condition,
+ pylint.extensions.check_elif,
+ pylint.extensions.eq_without_hash,
+ pylint.extensions.overlapping_exceptions,
+
+# pylint.extensions.comparetozero,
+# Takes out ``if x == 0:`` and wants you to write ``if not x:``
+# but in many cases, the == 0 is actually much more clear.
+
+# pylint.extensions.mccabe,
+# We have too many too-complex methods. We should enable this and fix them
+# one by one.
+
+# pylint.extensions.redefined_variable_type,
+# We use that pattern during initialization.
+
+# magic_value wants you to not use arbitrary strings and numbers
+# inline in the code. But it's overzealous and has way too many false
+# positives. Trust people to do the most readable thing.
+# pylint.extensions.magic_value
+
+# Empty comment would be good, except it detects blank lines within
+# a single comment block.
+#
+# Those are often used to separate paragraphs, like here.
+# pylint.extensions.empty_comment,
+
+# consider_ternary_expression is a nice check, but is also overzealous.
+# Trust the human to do the readable thing.
+# pylint.extensions.consider_ternary_expression,
+
+# redefined_loop_name tends to catch us with things like
+# for name in (a, b, c): name = name + '_column' ...
+# pylint.extensions.redefined_loop_name,
+
+# This wants you to turn ``x in (1, 2)`` into ``x in {1, 2}``.
+# They both result in the LOAD_CONST bytecode, one a tuple one a
+# frozenset. In theory a set lookup using hashing is faster than
+# a linear scan of a tuple; but if the tuple is small, it can often
+# actually be faster to scan the tuple.
+# pylint.extensions.set_membership,
+
# Fix zope.cachedescriptors.property.Lazy; the property-classes doesn't seem to
# do anything.
#
https://stackoverflow.com/questions/51160955/pylint-how-to-specify-a-self-defined-property-decorator-with-property-classes
-init-hook = "import astroid.bases;
astroid.bases.POSSIBLE_PROPERTIES.add('Lazy')"
+# For releases prior to 2.14.2, this needs to be a one-line, quoted string.
After that,
+# a multi-line string.
+# - Make zope.cachedescriptors.property.Lazy look like a property;
+# fixes pylint thinking it is a method.
+# - Run in Pure Python mode (ignore C extensions that respect this);
+# fixes some issues with zope.interface, like IFoo.providedby(ob)
+# claiming not to have the right number of parameters...except no, it does
not.
+init-hook =
+ import astroid.bases
+ astroid.bases.POSSIBLE_PROPERTIES.add('Lazy')
+ astroid.bases.POSSIBLE_PROPERTIES.add('LazyOnClass')
+ astroid.bases.POSSIBLE_PROPERTIES.add('readproperty')
+ astroid.bases.POSSIBLE_PROPERTIES.add('non_overridable')
+ import os
+ os.environ['PURE_PYTHON'] = ("1")
+ # Ending on a quoted string
+ # breaks pylint 2.14.5 (it strips the trailing quote. This is
+ # probably because it tries to handle one-line quoted strings as well as
multi-blocks).
+ # The parens around it fix the issue.
+
+extension-pkg-whitelist=greenlet._greenlet
+
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+# gevent: The changes for Python 3.7 in _ssl3.py lead to infinite recursion
+# in pylint 2.3.1/astroid 2.2.5 in that file unless we this this to 1
+# from the default of 100.
+limit-inference-results=1
[MESSAGES CONTROL]
@@ -49,31 +125,55 @@
# Pylint 2.4 adds self-assigning-variable. But we do *that* to avoid
unused-import when we
# "export" the variable and don't have a __all__.
# Pylint 2.6+ adds some python-3-only things that don't apply:
raise-missing-from, super-with-arguments, consider-using-f-string,
redundant-u-string-prefix
+# unnecessary-lambda-assignment: New check introduced in v2.14.0
+# unnecessary-dunder-call: New check introduced in v2.14.0
+# consider-using-assignment-expr: wants you to use the walrus operator.
+# It hits way too much and its not clear they would be improvements.
+# confusing-consecutive-elif: Are they though?
disable=wrong-import-position,
wrong-import-order,
missing-docstring,
ungrouped-imports,
invalid-name,
+ protected-access,
too-few-public-methods,
+ exec-used,
global-statement,
+ multiple-statements,
locally-disabled,
+ cyclic-import,
too-many-arguments,
+ redefined-builtin,
useless-suppression,
duplicate-code,
+ undefined-all-variable,
+ inconsistent-return-statements,
+ useless-return,
useless-object-inheritance,
import-outside-toplevel,
self-assigning-variable,
- consider-using-f-string
+ raise-missing-from,
+ super-with-arguments,
+ consider-using-f-string,
+ consider-using-assignment-expr,
+ redundant-u-string-prefix,
+ unnecessary-lambda-assignment,
+ unnecessary-dunder-call,
+ use-dict-literal,
+ confusing-consecutive-elif,
+
+enable=consider-using-augmented-assign
[FORMAT]
-max-line-length=100
+# duplicated from setup.cfg
+max-line-length=160
max-module-lines=1100
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
#notes=FIXME,XXX,TODO
-# Disable that, we don't want them to fail the lint CI job.
+# Disable that, we don't want them in the report (???)
notes=
[VARIABLES]
@@ -85,14 +185,8 @@
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
-generated-members=REQUEST,acl_users,aq_parent,providedBy
-
-
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-# XXX: deprecated in 2.14; replaced with ignored-checks-for-mixins.
-# The defaults for that value seem to be what we want
-#ignore-mixin-members=yes
+# gevent: this is helpful for py3/py2 code.
+generated-members=exc_clear
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set). This can work
@@ -104,25 +198,18 @@
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
-#ignored-modules=gevent._corecffi,gevent.os,os,greenlet,threading,gevent.libev.corecffi,gevent.socket,gevent.core,gevent.testing.support
+ignored-modules=gevent._corecffi,gevent.os,os,greenlet,threading,gevent.libev.corecffi,gevent.socket,gevent.core,gevent.testing.support
[DESIGN]
max-attributes=12
max-parents=10
[BASIC]
+bad-functions=input
# Prospector turns ot unsafe-load-any-extension by default, but
# pylint leaves it off. This is the proximal cause of the
# undefined-all-variable crash.
unsafe-load-any-extension = yes
-property-classes=zope.cachedescriptors.property.Lazy,zope.cachedescriptors.property.Cached
-extension-pkg-allow-list=greenlet._greenlet
-
-[CLASSES]
-# List of interface methods to ignore, separated by a comma. This is used for
-# instance to not check methods defines in Zope's Interface base class.
-
-
# Local Variables:
# mode: conf
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/.readthedocs.yml
new/greenlet-3.0.3/.readthedocs.yml
--- old/greenlet-3.0.2/.readthedocs.yml 2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/.readthedocs.yml 2023-12-21 22:57:40.000000000 +0100
@@ -7,11 +7,20 @@
# Build documentation in the docs/ directory with Sphinx
sphinx:
+ builder: html
configuration: docs/conf.py
-# Optionally set the version of Python and requirements required to build your
docs
+
+# Set the version of Python and requirements required to build your
+# docs
+
+build:
+ # os is required for some reason
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+
python:
- version: 3
install:
- method: pip
path: .
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/CHANGES.rst
new/greenlet-3.0.3/CHANGES.rst
--- old/greenlet-3.0.2/CHANGES.rst 2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/CHANGES.rst 2023-12-21 22:57:40.000000000 +0100
@@ -2,6 +2,15 @@
Changes
=========
+3.0.3 (2023-12-21)
+==================
+
+- Python 3.12: Restore the full ability to walk the stack of a suspended
+ greenlet; previously only the innermost frame was exposed. See `issue 388
+ <https://github.com/python-greenlet/greenlet/issues/388>`_. Fix by
+ Joshua Oreman in `PR 393
+ <https://github.com/python-greenlet/greenlet/pull/393/>`_.
+
3.0.2 (2023-12-08)
==================
@@ -236,7 +245,7 @@
====================
Platforms
-~~~~~~~~~
+---------
- Add experimental, untested support for 64-bit Windows on ARM using
MSVC. See `PR 271 <https://github.com/python-greenlet/greenlet/pull/271>`_.
@@ -386,7 +395,7 @@
- (Documentation) Publish the change log to https://greenlet.readthedocs.io
Supported Platforms
-~~~~~~~~~~~~~~~~~~~
+-------------------
- Drop support for Python 2.4, 2.5, 2.6, 3.0, 3.1, 3.2 and 3.4.
The project metadata now includes the ``python_requires`` data to
@@ -396,7 +405,7 @@
<https://github.com/python-greenlet/greenlet/pull/197>`_.
Packaging Changes
-~~~~~~~~~~~~~~~~~
+-----------------
- Require setuptools to build from source.
- Stop asking setuptools to build both .tar.gz and .zip
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/PKG-INFO new/greenlet-3.0.3/PKG-INFO
--- old/greenlet-3.0.2/PKG-INFO 2023-12-08 20:53:34.632305900 +0100
+++ new/greenlet-3.0.3/PKG-INFO 2023-12-21 22:57:41.091845500 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: greenlet
-Version: 3.0.2
+Version: 3.0.3
Summary: Lightweight in-process concurrent programming
Home-page: https://greenlet.readthedocs.io/
Author: Alexey Borzenkov
@@ -31,14 +31,11 @@
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
+Provides-Extra: docs
+Provides-Extra: test
License-File: LICENSE
License-File: LICENSE.PSF
License-File: AUTHORS
-Provides-Extra: docs
-Requires-Dist: Sphinx; extra == "docs"
-Provides-Extra: test
-Requires-Dist: objgraph; extra == "test"
-Requires-Dist: psutil; extra == "test"
.. This file is included into docs/history.rst
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/benchmarks/chain.py
new/greenlet-3.0.3/benchmarks/chain.py
--- old/greenlet-3.0.2/benchmarks/chain.py 2023-12-08 20:53:34.000000000
+0100
+++ new/greenlet-3.0.3/benchmarks/chain.py 2023-12-21 22:57:40.000000000
+0100
@@ -5,10 +5,24 @@
along.
"""
+import os
import pyperf
import greenlet
-
+# This is obsolete now, we always expose frames for Python 3.12.
+# See https://github.com/python-greenlet/greenlet/pull/393/
+# for a complete discussion of performance.
+EXPOSE_FRAMES = 'EXPOSE_FRAMES' in os.environ
+
+# Exposing
+# 100 frames Mean +- std dev: 5.62 us +- 0.10 us
+# 200 frames Mean +- std dev: 14.0 us +- 0.6 us
+# 300 frames Mean +- std dev: 22.7 us +- 0.4 us
+#
+# Non-exposing
+# 100 frames Mean +- std dev: 3.64 us +- 0.06 us -> 1.54/1.98us
+# 200 frames Mean +- std dev: 9.49 us +- 0.13 us -> 1.47/4.51us
+# 300 frames Mean +- std dev: 15.7 us +- 0.3 us -> 1.45/7us
def link(next_greenlet):
value = greenlet.getcurrent().parent.switch()
@@ -23,6 +37,7 @@
start_node = greenlet.getcurrent()
for _ in range(CHAIN_GREENLET_COUNT):
g = greenlet.greenlet(link)
+ g.gr_frames_always_exposed = EXPOSE_FRAMES
g.switch(start_node)
start_node = g
x = start_node.switch(0)
@@ -51,7 +66,8 @@
return end - begin
SWITCH_INNER_LOOPS = 10000
-def bm_switch(loops):
+def bm_switch_shallow(loops):
+ # pylint:disable=attribute-defined-outside-init
class G(greenlet.greenlet):
other = None
def run(self):
@@ -60,15 +76,63 @@
o.switch()
begin = pyperf.perf_counter()
+
for _ in range(loops):
gl1 = G()
gl2 = G()
+ gl1.gr_frames_always_exposed = EXPOSE_FRAMES
+ gl2.gr_frames_always_exposed = EXPOSE_FRAMES
gl1.other = gl2
gl2.other = gl1
gl1.switch()
+
+ gl1.switch()
+ gl2.switch()
+ gl1.other = gl2.other = None
+ assert gl1.dead
+ assert gl2.dead
+
end = pyperf.perf_counter()
return end - begin
+def bm_switch_deep(loops, _MAX_DEPTH=200):
+ # pylint:disable=attribute-defined-outside-init
+ class G(greenlet.greenlet):
+ other = None
+ def run(self):
+ for _ in range(SWITCH_INNER_LOOPS):
+ self.recur_then_switch()
+
+ def recur_then_switch(self, depth=_MAX_DEPTH):
+ if not depth:
+ self.other.switch()
+ else:
+ self.recur_then_switch(depth - 1)
+
+ begin = pyperf.perf_counter()
+
+ for _ in range(loops):
+ gl1 = G()
+ gl2 = G()
+ gl1.gr_frames_always_exposed = EXPOSE_FRAMES
+ gl2.gr_frames_always_exposed = EXPOSE_FRAMES
+ gl1.other = gl2
+ gl2.other = gl1
+ gl1.switch()
+
+ gl1.switch()
+ gl2.switch()
+ gl1.other = gl2.other = None
+ assert gl1.dead
+ assert gl2.dead
+
+ end = pyperf.perf_counter()
+ return end - begin
+
+def bm_switch_deeper(loops):
+ return bm_switch_deep(loops, 400)
+
+
CREATE_INNER_LOOPS = 10
def bm_create(loops):
gl = greenlet.greenlet
@@ -87,8 +151,58 @@
end = pyperf.perf_counter()
return end - begin
+
+
+
+def _bm_recur_frame(loops, RECUR_DEPTH):
+
+ def recur(depth):
+ if not depth:
+ return greenlet.getcurrent().parent.switch(greenlet.getcurrent())
+ return recur(depth - 1)
+
+
+ begin = pyperf.perf_counter()
+ for _ in range(loops):
+
+ for _ in range(CHAIN_GREENLET_COUNT):
+ g = greenlet.greenlet(recur)
+ g.gr_frames_always_exposed = EXPOSE_FRAMES
+ g2 = g.switch(RECUR_DEPTH)
+ assert g2 is g, (g2, g)
+ f = g2.gr_frame
+ assert f is not None, "frame is none"
+ count = 0
+ while f:
+ count += 1
+ f = f.f_back
+ # This assertion fails with the released versions of greenlet
+ # on Python 3.12
+ #assert count == RECUR_DEPTH + 1, (count, RECUR_DEPTH)
+ # Switch back so it can be collected; otherwise they build
+ # up forever.
+ g.switch()
+ # fall off the end of it and back to us.
+ del g
+ del g2
+ del f
+
+
+ end = pyperf.perf_counter()
+ return end - begin
+
+def bm_recur_frame_2(loops):
+ return _bm_recur_frame(loops, 2)
+
+def bm_recur_frame_20(loops):
+ return _bm_recur_frame(loops, 20)
+
+def bm_recur_frame_200(loops):
+ return _bm_recur_frame(loops, 200)
+
if __name__ == '__main__':
runner = pyperf.Runner()
+
runner.bench_time_func(
'create a greenlet',
bm_create,
@@ -96,12 +210,23 @@
)
runner.bench_time_func(
- 'switch between two greenlets',
- bm_switch,
+ 'switch between two greenlets (shallow)',
+ bm_switch_shallow,
inner_loops=SWITCH_INNER_LOOPS
)
runner.bench_time_func(
+ 'switch between two greenlets (deep)',
+ bm_switch_deep,
+ inner_loops=SWITCH_INNER_LOOPS
+ )
+
+ runner.bench_time_func(
+ 'switch between two greenlets (deeper)',
+ bm_switch_deeper,
+ inner_loops=SWITCH_INNER_LOOPS
+ )
+ runner.bench_time_func(
'getcurrent single thread',
bm_getcurrent,
inner_loops=GETCURRENT_INNER_LOOPS
@@ -110,3 +235,17 @@
'chain(%s)' % CHAIN_GREENLET_COUNT,
bm_chain,
)
+
+ runner.bench_time_func(
+ 'read 2 nested frames',
+ bm_recur_frame_2,
+ )
+
+ runner.bench_time_func(
+ 'read 20 nested frames',
+ bm_recur_frame_20,
+ )
+ runner.bench_time_func(
+ 'read 200 nested frames',
+ bm_recur_frame_200,
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/docs/_static/custom.css
new/greenlet-3.0.3/docs/_static/custom.css
--- old/greenlet-3.0.2/docs/_static/custom.css 1970-01-01 01:00:00.000000000
+0100
+++ new/greenlet-3.0.3/docs/_static/custom.css 2023-12-21 22:57:40.000000000
+0100
@@ -0,0 +1,99 @@
+/* Font definitions */
+@font-face {
+ font-family: 'JetBrains Mono';
+ src:
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Bold-Italic.woff2')
format('woff2'),
+
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Bold-Italic.woff')
format('woff');
+ font-weight: 700;
+ font-style: italic;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'JetBrains Mono';
+ src:
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Bold.woff2')
format('woff2'),
+
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Bold.woff')
format('woff');
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'JetBrains Mono';
+ src:
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-ExtraBold-Italic.woff2')
format('woff2'),
+
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-ExtraBold-Italic.woff')
format('woff');
+ font-weight: 800;
+ font-style: italic;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'JetBrains Mono';
+ src:
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-ExtraBold.woff2')
format('woff2'),
+
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-ExtraBold.woff')
format('woff');
+ font-weight: 800;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'JetBrains Mono';
+ src:
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Italic.woff2')
format('woff2'),
+
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Italic.woff')
format('woff');
+ font-weight: 400;
+ font-style: italic;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'JetBrains Mono';
+ src:
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Medium-Italic.woff2')
format('woff2'),
+
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Medium-Italic.woff')
format('woff');
+ font-weight: 500;
+ font-style: italic;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'JetBrains Mono';
+ src:
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Medium.woff2')
format('woff2'),
+
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Medium.woff')
format('woff');
+ font-weight: 500;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'JetBrains Mono';
+ src:
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Regular.woff2')
format('woff2'),
+
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Regular.woff')
format('woff');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+
+article {
+/* Furo theme makes this 1.5 which uses soo much space */
+ line-height: 1.1;
+}
+
+a {
+ text-decoration: none;
+}
+
+.admonition-opinion p.admonition-title {
+ background-color: rgba(255, 150, 235, 0.44);
+}
+
+div.admonition-opinion.admonition {
+ border-left: .2rem solid rgba(255, 150, 235, 0.44);
+}
+
+
+.admonition-design-options p.admonition-title {
+ background-color: rgba(173, 28, 237, 0.44);
+}
+
+div.admonition-design-options.admonition {
+ border-left: .2rem solid rgba(173, 28, 237, 0.44);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/docs/api.rst
new/greenlet-3.0.3/docs/api.rst
--- old/greenlet-3.0.2/docs/api.rst 2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/docs/api.rst 2023-12-21 22:57:40.000000000 +0100
@@ -32,7 +32,6 @@
.. autoattribute:: gr_context
-
The :class:`contextvars.Context` in which ``g`` will run.
Writable; defaults to ``None``, reflecting that a greenlet
starts execution in an empty context unless told otherwise.
@@ -56,6 +55,17 @@
for suspended greenlets; it is None if the greenlet is dead, not
yet started, or currently executing.
+ .. note:: Greenlet stack introspection is fragile on CPython 3.12
+ and later. The frame objects of a suspended greenlet are not safe
+ to access as-is, but must be adjusted by the greenlet package in
+ order to make traversing ``f_back`` links not crash the interpreter,
+ and restored to their original state when resuming the
+ greenlet. The intent is to handle this transparently, but it
+ does introduce additional overhead to switching greenlets,
+ and there may be obscure usage patterns that can still crash
+ the interpreter; if you find one of these, please report it
+ to the maintainer.
+
.. autoattribute:: parent
The parent greenlet. This is writable, but it is not allowed to create
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/docs/conf.py
new/greenlet-3.0.3/docs/conf.py
--- old/greenlet-3.0.2/docs/conf.py 2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/docs/conf.py 2023-12-21 22:57:40.000000000 +0100
@@ -17,9 +17,17 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
-import pkg_resources
sys.path.append(os.path.abspath('../src/'))
-rqmt = pkg_resources.require('greenlet')[0]
+try:
+ from importlib import metadata
+except ImportError:
+ # Building the docs on 3.7. Which we don't do,
+ # except for running doctests.
+ glet_version = '0.0.0'
+else:
+ glet_version = metadata.version('greenlet')
+
+
# -- General configuration
-----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -58,9 +66,9 @@
# built documents.
#
# The short X.Y version.
-version = '%s.%s' % tuple(rqmt.version.split('.')[:2])
+version = glet_version
# The full version, including alpha/beta/rc tags.
-release = rqmt.version
+release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -101,7 +109,23 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+
+html_theme = "furo"
+html_css_files = [
+ 'custom.css',
+]
+
+html_theme_options = {
+ "sidebar_hide_name": True, # Because we show a logo
+
+ 'light_css_variables': {
+ "color-brand-primary": "#7c9a5e",
+ "color-brand-content": "#7c9a5e",
+ "color-foreground-border": "#b7d897",
+ 'font-stack': '"SF Pro",-apple-system,BlinkMacSystemFont,"Segoe
UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
+ 'font-stack--monospace': '"JetBrainsMono", "JetBrains Mono",
"JetBrains Mono Regular", "JetBrainsMono-Regular", ui-monospace, profont,
monospace',
+ },
+}
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -130,7 +154,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = [] # ['_static']
+html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
@@ -252,8 +276,9 @@
#texinfo_show_urls = 'footnote'
intersphinx_mapping = {
- 'https://docs.python.org/': None,
- 'https://www.gevent.org/': None,
+ 'python': ('https://docs.python.org/', None),
+ 'gevent': ('https://www.gevent.org/', None),
+
}
@@ -266,7 +291,7 @@
#'members': None,
'show-inheritance': None,
}
-autodoc_member_order = 'bysource'
+autodoc_member_order = 'groupwise'
autoclass_content = 'both'
extlinks = {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/setup.py new/greenlet-3.0.3/setup.py
--- old/greenlet-3.0.2/setup.py 2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/setup.py 2023-12-21 22:57:40.000000000 +0100
@@ -247,6 +247,7 @@
extras_require={
'docs': [
'Sphinx',
+ 'furo',
],
'test': [
'objgraph',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/TGreenlet.cpp
new/greenlet-3.0.3/src/greenlet/TGreenlet.cpp
--- old/greenlet-3.0.2/src/greenlet/TGreenlet.cpp 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/TGreenlet.cpp 2023-12-21
22:57:40.000000000 +0100
@@ -168,6 +168,7 @@
current->exception_state << tstate;
this->python_state.will_switch_from(tstate);
switching_thread_state = this;
+ current->expose_frames();
}
assert(this->args() || PyErr_Occurred());
// If this is the first switch into a greenlet, this will
@@ -606,5 +607,108 @@
return this->stack_state.active() && !this->python_state.top_frame();
}
+#if GREENLET_PY312
+void GREENLET_NOINLINE(Greenlet::expose_frames)()
+{
+ if (!this->python_state.top_frame()) {
+ return;
+ }
+
+ _PyInterpreterFrame* last_complete_iframe = nullptr;
+ _PyInterpreterFrame* iframe = this->python_state.top_frame()->f_frame;
+ while (iframe) {
+ // We must make a copy before looking at the iframe contents,
+ // since iframe might point to a portion of the greenlet's C stack
+ // that was spilled when switching greenlets.
+ _PyInterpreterFrame iframe_copy;
+ this->stack_state.copy_from_stack(&iframe_copy, iframe,
sizeof(*iframe));
+ if (!_PyFrame_IsIncomplete(&iframe_copy)) {
+ // If the iframe were OWNED_BY_CSTACK then it would always be
+ // incomplete. Since it's not incomplete, it's not on the C stack
+ // and we can access it through the original `iframe` pointer
+ // directly. This is important since GetFrameObject might
+ // lazily _create_ the frame object and we don't want the
+ // interpreter to lose track of it.
+ assert(iframe_copy.owner != FRAME_OWNED_BY_CSTACK);
+
+ // We really want to just write:
+ // PyFrameObject* frame = _PyFrame_GetFrameObject(iframe);
+ // but _PyFrame_GetFrameObject calls _PyFrame_MakeAndSetFrameObject
+ // which is not a visible symbol in libpython. The easiest
+ // way to get a public function to call it is using
+ // PyFrame_GetBack, which is defined as follows:
+ // assert(frame != NULL);
+ // assert(!_PyFrame_IsIncomplete(frame->f_frame));
+ // PyFrameObject *back = frame->f_back;
+ // if (back == NULL) {
+ // _PyInterpreterFrame *prev = frame->f_frame->previous;
+ // prev = _PyFrame_GetFirstComplete(prev);
+ // if (prev) {
+ // back = _PyFrame_GetFrameObject(prev);
+ // }
+ // }
+ // return (PyFrameObject*)Py_XNewRef(back);
+ if (!iframe->frame_obj) {
+ PyFrameObject dummy_frame;
+ _PyInterpreterFrame dummy_iframe;
+ dummy_frame.f_back = nullptr;
+ dummy_frame.f_frame = &dummy_iframe;
+ // force the iframe to be considered complete without
+ // needing to check its code object:
+ dummy_iframe.owner = FRAME_OWNED_BY_GENERATOR;
+ dummy_iframe.previous = iframe;
+ assert(!_PyFrame_IsIncomplete(&dummy_iframe));
+ // Drop the returned reference immediately; the iframe
+ // continues to hold a strong reference
+ Py_XDECREF(PyFrame_GetBack(&dummy_frame));
+ assert(iframe->frame_obj);
+ }
+
+ // This is a complete frame, so make the last one of those we saw
+ // point at it, bypassing any incomplete frames (which may have
+ // been on the C stack) in between the two. We're overwriting
+ // last_complete_iframe->previous and need that to be reversible,
+ // so we store the original previous ptr in the frame object
+ // (which we must have created on a previous iteration through
+ // this loop). The frame object has a bunch of storage that is
+ // only used when its iframe is OWNED_BY_FRAME_OBJECT, which only
+ // occurs when the frame object outlives the frame's execution,
+ // which can't have happened yet because the frame is currently
+ // executing as far as the interpreter is concerned. So, we can
+ // reuse it for our own purposes.
+ assert(iframe->owner == FRAME_OWNED_BY_THREAD
+ || iframe->owner == FRAME_OWNED_BY_GENERATOR);
+ if (last_complete_iframe) {
+ assert(last_complete_iframe->frame_obj);
+ memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
+ &last_complete_iframe->previous, sizeof(void *));
+ last_complete_iframe->previous = iframe;
+ }
+ last_complete_iframe = iframe;
+ }
+ // Frames that are OWNED_BY_FRAME_OBJECT are linked via the
+ // frame's f_back while all others are linked via the iframe's
+ // previous ptr. Since all the frames we traverse are running
+ // as far as the interpreter is concerned, we don't have to
+ // worry about the OWNED_BY_FRAME_OBJECT case.
+ iframe = iframe_copy.previous;
+ }
+
+ // Give the outermost complete iframe a null previous pointer to
+ // account for any potential incomplete/C-stack iframes between it
+ // and the actual top-of-stack
+ if (last_complete_iframe) {
+ assert(last_complete_iframe->frame_obj);
+ memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
+ &last_complete_iframe->previous, sizeof(void *));
+ last_complete_iframe->previous = nullptr;
+ }
+}
+#else
+void Greenlet::expose_frames()
+{
+
+}
+#endif
}; // namespace greenlet
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/TPythonState.cpp
new/greenlet-3.0.3/src/greenlet/TPythonState.cpp
--- old/greenlet-3.0.2/src/greenlet/TPythonState.cpp 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/TPythonState.cpp 2023-12-21
22:57:40.000000000 +0100
@@ -25,9 +25,6 @@
,datastack_top(nullptr)
,datastack_limit(nullptr)
#endif
-#if GREENLET_PY312
- ,_prev_frame(nullptr)
-#endif
{
#if GREENLET_USE_CFRAME
/*
@@ -147,12 +144,6 @@
// reference.
this->_top_frame.steal(frame);
#if GREENLET_PY312
- if (frame) {
- this->_prev_frame = frame->f_frame->previous;
- frame->f_frame->previous = nullptr;
- }
- #endif
- #if GREENLET_PY312
this->trash_delete_nesting = tstate->trash.delete_nesting;
#else // not 312
this->trash_delete_nesting = tstate->trash_delete_nesting;
@@ -164,6 +155,28 @@
#endif // GREENLET_PY311
}
+#if GREENLET_PY312
+void GREENLET_NOINLINE(PythonState::unexpose_frames)()
+{
+ if (!this->top_frame()) {
+ return;
+ }
+
+ // See GreenletState::expose_frames() and the comment on
frames_were_exposed
+ // for more information about this logic.
+ _PyInterpreterFrame *iframe = this->_top_frame->f_frame;
+ while (iframe != nullptr) {
+ _PyInterpreterFrame *prev_exposed = iframe->previous;
+ assert(iframe->frame_obj);
+ memcpy(&iframe->previous, &iframe->frame_obj->_f_frame_data[0],
+ sizeof(void *));
+ iframe = prev_exposed;
+ }
+}
+#else
+void PythonState::unexpose_frames()
+{}
+#endif
void PythonState::operator>>(PyThreadState *const tstate) noexcept
{
@@ -187,13 +200,7 @@
#if GREENLET_PY312
tstate->py_recursion_remaining = tstate->py_recursion_limit -
this->py_recursion_depth;
tstate->c_recursion_remaining = C_RECURSION_LIMIT -
this->c_recursion_depth;
- // We're just going to throw this object away anyway, go ahead and
- // do it now.
- PyFrameObject* frame = this->_top_frame.relinquish_ownership();
- if (frame && frame->f_frame) {
- frame->f_frame->previous = this->_prev_frame;
- }
- this->_prev_frame = nullptr;
+ this->unexpose_frames();
#else // \/ 3.11
tstate->recursion_remaining = tstate->recursion_limit -
this->recursion_depth;
#endif // GREENLET_PY312
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/TStackState.cpp
new/greenlet-3.0.3/src/greenlet/TStackState.cpp
--- old/greenlet-3.0.2/src/greenlet/TStackState.cpp 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/TStackState.cpp 2023-12-21
22:57:40.000000000 +0100
@@ -226,6 +226,39 @@
}
}
+void StackState::copy_from_stack(void* vdest, const void* vsrc, size_t n) const
+{
+ char* dest = static_cast<char*>(vdest);
+ const char* src = static_cast<const char*>(vsrc);
+ if (src + n <= this->_stack_start
+ || src >= this->_stack_start + this->_stack_saved
+ || this->_stack_saved == 0) {
+ // Nothing we're copying was spilled from the stack
+ memcpy(dest, src, n);
+ return;
+ }
+
+ if (src < this->_stack_start) {
+ // Copy the part before the saved stack.
+ // We know src + n > _stack_start due to the test above.
+ const size_t nbefore = this->_stack_start - src;
+ memcpy(dest, src, nbefore);
+ dest += nbefore;
+ src += nbefore;
+ n -= nbefore;
+ }
+ // We know src >= _stack_start after the before-copy, and
+ // src < _stack_start + _stack_saved due to the first if condition
+ size_t nspilled = std::min<size_t>(n, this->_stack_start +
this->_stack_saved - src);
+ memcpy(dest, this->stack_copy + (src - this->_stack_start), nspilled);
+ dest += nspilled;
+ src += nspilled;
+ n -= nspilled;
+ if (n > 0) {
+ // Copy the part after the saved stack
+ memcpy(dest, src, n);
+ }
+}
}; // namespace greenlet
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/__init__.py
new/greenlet-3.0.3/src/greenlet/__init__.py
--- old/greenlet-3.0.2/src/greenlet/__init__.py 2023-12-08 20:53:34.000000000
+0100
+++ new/greenlet-3.0.3/src/greenlet/__init__.py 2023-12-21 22:57:40.000000000
+0100
@@ -25,7 +25,7 @@
###
# Metadata
###
-__version__ = '3.0.2'
+__version__ = '3.0.3'
from ._greenlet import _C_API # pylint:disable=no-name-in-module
###
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/greenlet.cpp
new/greenlet-3.0.3/src/greenlet/greenlet.cpp
--- old/greenlet-3.0.2/src/greenlet/greenlet.cpp 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/greenlet.cpp 2023-12-21
22:57:40.000000000 +0100
@@ -762,6 +762,7 @@
return top_frame.acquire_or_None();
}
+
static PyObject*
green_getstate(PyGreenlet* self)
{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/greenlet_greenlet.hpp
new/greenlet-3.0.3/src/greenlet/greenlet_greenlet.hpp
--- old/greenlet-3.0.2/src/greenlet/greenlet_greenlet.hpp 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/greenlet_greenlet.hpp 2023-12-21
22:57:40.000000000 +0100
@@ -117,11 +117,20 @@
PyObject** datastack_top;
PyObject** datastack_limit;
#endif
-#if GREENLET_PY312
- _PyInterpreterFrame* _prev_frame;
-#endif
+ // The PyInterpreterFrame list on 3.12+ contains some entries that are
+ // on the C stack, which can't be directly accessed while a greenlet is
+ // suspended. In order to keep greenlet gr_frame introspection working,
+ // we adjust stack switching to rewrite the interpreter frame list
+ // to skip these C-stack frames; we call this "exposing" the greenlet's
+ // frames because it makes them valid to work with in Python. Then when
+ // the greenlet is resumed we need to remember to reverse the operation
+ // we did. The C-stack frames are "entry frames" which are a low-level
+ // interpreter detail; they're not needed for introspection, but do
+ // need to be present for the eval loop to work.
+ void unexpose_frames();
public:
+
PythonState();
// You can use this for testing whether we have a frame
// or not. It returns const so they can't modify it.
@@ -137,6 +146,7 @@
#if GREENLET_USE_CFRAME
void set_new_cframe(_PyCFrame& frame) noexcept;
#endif
+
inline void may_switch_away() noexcept;
inline void will_switch_from(PyThreadState *const origin_tstate)
noexcept;
void did_finish(PyThreadState* tstate) noexcept;
@@ -186,6 +196,14 @@
#ifdef GREENLET_USE_STDIO
friend std::ostream& operator<<(std::ostream& os, const StackState& s);
#endif
+
+ // Fill in [dest, dest + n) with the values that would be at
+ // [src, src + n) while this greenlet is running. This is like memcpy
+ // except that if the greenlet is suspended it accounts for the portion
+ // of the greenlet's stack that was spilled to the heap. `src` may
+ // be on this greenlet's stack, or on the heap, but not on a different
+ // greenlet's stack.
+ void copy_from_stack(void* dest, const void* src, size_t n) const;
};
#ifdef GREENLET_USE_STDIO
std::ostream& operator<<(std::ostream& os, const StackState& s);
@@ -377,6 +395,19 @@
// was running in was known to have exited.
void deallocing_greenlet_in_thread(const ThreadState* current_state);
+ // Must be called on 3.12+ before exposing a suspended greenlet's
+ // frames to user code. This rewrites the linked list of interpreter
+ // frames to skip the ones that are being stored on the C stack (which
+ // can't be safely accessed while the greenlet is suspended because
+ // that stack space might be hosting a different greenlet), and
+ // sets PythonState::frames_were_exposed so we remember to restore
+ // the original list before resuming the greenlet. The C-stack frames
+ // are a low-level interpreter implementation detail; while they're
+ // important to the bytecode eval loop, they're superfluous for
+ // introspection purposes.
+ void expose_frames();
+
+
// TODO: Figure out how to make these non-public.
inline void slp_restore_state() noexcept;
inline int slp_save_state(char *const stackref) noexcept;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/greenlet-3.0.2/src/greenlet/tests/_test_extension_cpp.cpp
new/greenlet-3.0.3/src/greenlet/tests/_test_extension_cpp.cpp
--- old/greenlet-3.0.2/src/greenlet/tests/_test_extension_cpp.cpp
2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/tests/_test_extension_cpp.cpp
2023-12-21 22:57:40.000000000 +0100
@@ -100,6 +100,15 @@
return NULL;
}
+static PyObject*
+py_test_call(PyObject* self, PyObject* arg)
+{
+ PyObject* noargs = PyTuple_New(0);
+ PyObject* ret = PyObject_Call(arg, noargs, nullptr);
+ Py_DECREF(noargs);
+ return ret;
+}
+
/* test_exception_switch_and_do_in_g2(g2func)
@@ -173,6 +182,12 @@
METH_VARARGS,
"Throws standard C++ exception. Calling this function directly should
abort the process."
},
+ {"test_call",
+ (PyCFunction)&py_test_call,
+ METH_O,
+ "Call the given callable. Unlike calling it directly, this creates a "
+ "new C-level stack frame, which may be helpful in testing."
+ },
{NULL, NULL, 0, NULL}
};
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/tests/leakcheck.py
new/greenlet-3.0.3/src/greenlet/tests/leakcheck.py
--- old/greenlet-3.0.2/src/greenlet/tests/leakcheck.py 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/tests/leakcheck.py 2023-12-21
22:57:40.000000000 +0100
@@ -220,6 +220,7 @@
def _growth_after(self):
# Grab post snapshot
+ # pylint:disable=no-member
if 'urlparse' in sys.modules:
sys.modules['urlparse'].clear_cache()
if 'urllib.parse' in sys.modules:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/greenlet-3.0.2/src/greenlet/tests/test_contextvars.py
new/greenlet-3.0.3/src/greenlet/tests/test_contextvars.py
--- old/greenlet-3.0.2/src/greenlet/tests/test_contextvars.py 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/tests/test_contextvars.py 2023-12-21
22:57:40.000000000 +0100
@@ -47,6 +47,7 @@
callback()
def _test_context(self, propagate_by):
+ # pylint:disable=too-many-branches
ID_VAR.set(0)
callback = getcurrent().switch
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/tests/test_gc.py
new/greenlet-3.0.3/src/greenlet/tests/test_gc.py
--- old/greenlet-3.0.2/src/greenlet/tests/test_gc.py 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/tests/test_gc.py 2023-12-21
22:57:40.000000000 +0100
@@ -23,7 +23,7 @@
def test_circular_greenlet(self):
class circular_greenlet(greenlet.greenlet):
- pass
+ self = None
o = circular_greenlet()
o.self = o
o = weakref.ref(o)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/greenlet-3.0.2/src/greenlet/tests/test_generator_nested.py
new/greenlet-3.0.3/src/greenlet/tests/test_generator_nested.py
--- old/greenlet-3.0.2/src/greenlet/tests/test_generator_nested.py
2023-12-08 20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/tests/test_generator_nested.py
2023-12-21 22:57:40.000000000 +0100
@@ -149,7 +149,7 @@
# XXX Test to make sure we are working as a generator expression
def test_genlet_simple(self):
- for g in [g1, g2, g3]:
+ for g in g1, g2, g3:
seen = []
for _ in range(3):
for j in g(5, seen):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/tests/test_greenlet.py
new/greenlet-3.0.3/src/greenlet/tests/test_greenlet.py
--- old/greenlet-3.0.2/src/greenlet/tests/test_greenlet.py 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/tests/test_greenlet.py 2023-12-21
22:57:40.000000000 +0100
@@ -18,6 +18,12 @@
# We manually manage locks in many tests
# pylint:disable=consider-using-with
# pylint:disable=too-many-public-methods
+# This module is quite large.
+# TODO: Refactor into separate test files. For example,
+# put all the regression tests that used to produce
+# crashes in test_greenlet_no_crash; put tests that DO deliberately crash
+# the interpreter into test_greenlet_crash.
+# pylint:disable=too-many-lines
class SomeError(Exception):
pass
@@ -412,7 +418,7 @@
class mygreenlet(RawGreenlet):
def __getattribute__(self, name):
try:
- raise Exception()
+ raise Exception # pylint:disable=broad-exception-raised
except: # pylint:disable=bare-except
pass
return RawGreenlet.__getattribute__(self, name)
@@ -840,23 +846,76 @@
self.assertEqual(seen, [42, 24])
def test_can_access_f_back_of_suspended_greenlet(self):
- # On Python 3.12, they added a ->previous field to
- # _PyInterpreterFrame that has to be cleared when a frame is inactive.
- # If we got that wrong, this immediately crashes.
+ # This tests our frame rewriting to work around Python 3.12+ having
+ # some interpreter frames on the C stack. It will crash in the absence
+ # of that logic.
main = greenlet.getcurrent()
- def Hub():
- main.switch()
+ def outer():
+ inner()
- hub = RawGreenlet(Hub)
+ def inner():
+ main.switch(sys._getframe(0))
+
+ hub = RawGreenlet(outer)
# start it
hub.switch()
+
+ # start another greenlet to make sure we aren't relying on
+ # anything in `hub` still being on the C stack
+ unrelated = RawGreenlet(lambda: None)
+ unrelated.switch()
+
# now it is suspended
self.assertIsNotNone(hub.gr_frame)
+ self.assertEqual(hub.gr_frame.f_code.co_name, "inner")
+ self.assertIsNotNone(hub.gr_frame.f_back)
+ self.assertEqual(hub.gr_frame.f_back.f_code.co_name, "outer")
# The next line is what would crash
- self.assertIsNone(hub.gr_frame.f_back)
+ self.assertIsNone(hub.gr_frame.f_back.f_back)
+
+ def test_get_stack_with_nested_c_calls(self):
+ from functools import partial
+ from . import _test_extension_cpp
+
+ def recurse(v):
+ if v > 0:
+ return v * _test_extension_cpp.test_call(partial(recurse, v -
1))
+ return greenlet.getcurrent().parent.switch()
+
+ gr = RawGreenlet(recurse)
+ gr.switch(5)
+ frame = gr.gr_frame
+ for i in range(5):
+ self.assertEqual(frame.f_locals["v"], i)
+ frame = frame.f_back
+ self.assertEqual(frame.f_locals["v"], 5)
+ self.assertIsNone(frame.f_back)
+ self.assertEqual(gr.switch(10), 1200) # 1200 = 5! * 10
+
+ def test_frames_always_exposed(self):
+ # On Python 3.12 this will crash if we don't set the
+ # gr_frames_always_exposed attribute. More background:
+ # https://github.com/python-greenlet/greenlet/issues/388
+ main = greenlet.getcurrent()
+
+ def outer():
+ inner(sys._getframe(0))
+
+ def inner(frame):
+ main.switch(frame)
+ gr = RawGreenlet(outer)
+ frame = gr.switch()
+ # Do something else to clobber the part of the C stack used by `gr`,
+ # so we can't skate by on "it just happened to still be there"
+ unrelated = RawGreenlet(lambda: None)
+ unrelated.switch()
+
+ self.assertEqual(frame.f_code.co_name, "outer")
+ # The next line crashes on 3.12 if we haven't exposed the frames.
+ self.assertIsNone(frame.f_back)
class TestGreenletSetParentErrors(TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/tests/test_throw.py
new/greenlet-3.0.3/src/greenlet/tests/test_throw.py
--- old/greenlet-3.0.2/src/greenlet/tests/test_throw.py 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/tests/test_throw.py 2023-12-21
22:57:40.000000000 +0100
@@ -67,8 +67,7 @@
main.switch("f1 ready to catch")
except IndexError:
return "caught"
- else:
- return "normal exit"
+ return "normal exit"
def f2():
main.switch("from f2")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet/tests/test_weakref.py
new/greenlet-3.0.3/src/greenlet/tests/test_weakref.py
--- old/greenlet-3.0.2/src/greenlet/tests/test_weakref.py 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet/tests/test_weakref.py 2023-12-21
22:57:40.000000000 +0100
@@ -1,6 +1,6 @@
import gc
import weakref
-import unittest
+
import greenlet
from . import TestCase
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet.egg-info/PKG-INFO
new/greenlet-3.0.3/src/greenlet.egg-info/PKG-INFO
--- old/greenlet-3.0.2/src/greenlet.egg-info/PKG-INFO 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet.egg-info/PKG-INFO 2023-12-21
22:57:41.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: greenlet
-Version: 3.0.2
+Version: 3.0.3
Summary: Lightweight in-process concurrent programming
Home-page: https://greenlet.readthedocs.io/
Author: Alexey Borzenkov
@@ -31,14 +31,11 @@
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
+Provides-Extra: docs
+Provides-Extra: test
License-File: LICENSE
License-File: LICENSE.PSF
License-File: AUTHORS
-Provides-Extra: docs
-Requires-Dist: Sphinx; extra == "docs"
-Provides-Extra: test
-Requires-Dist: objgraph; extra == "test"
-Requires-Dist: psutil; extra == "test"
.. This file is included into docs/history.rst
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet.egg-info/SOURCES.txt
new/greenlet-3.0.3/src/greenlet.egg-info/SOURCES.txt
--- old/greenlet-3.0.2/src/greenlet.egg-info/SOURCES.txt 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet.egg-info/SOURCES.txt 2023-12-21
22:57:41.000000000 +0100
@@ -1,5 +1,4 @@
.clang-format
-.gitignore
.pylintrc
.readthedocs.yml
AUTHORS
@@ -37,6 +36,7 @@
docs/python_threads.rst
docs/switching.rst
docs/tracing.rst
+docs/_static/custom.css
src/greenlet/TBrokenGreenlet.cpp
src/greenlet/TExceptionState.cpp
src/greenlet/TGreenlet.cpp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.0.2/src/greenlet.egg-info/requires.txt
new/greenlet-3.0.3/src/greenlet.egg-info/requires.txt
--- old/greenlet-3.0.2/src/greenlet.egg-info/requires.txt 2023-12-08
20:53:34.000000000 +0100
+++ new/greenlet-3.0.3/src/greenlet.egg-info/requires.txt 2023-12-21
22:57:41.000000000 +0100
@@ -1,6 +1,7 @@
[docs]
Sphinx
+furo
[test]
objgraph