pax_global_header00006660000000000000000000000064146700742700014521gustar00rootroot0000000000000052 comment=583e83c6de43530d74f68a04feac3a73ed694ecf greenlet-3.1.0/000077500000000000000000000000001467007427000133275ustar00rootroot00000000000000greenlet-3.1.0/.clang-format000066400000000000000000000012711467007427000157030ustar00rootroot00000000000000# A clang-format style that approximates Python's PEP 7 -*- mode: yaml; -*- # Initially based on # https://gist.github.com/pganssle/0e3a5f828b4d07d79447f6ced8e7e4db BasedOnStyle: Google Language: Cpp AlignAfterOpenBracket: Align AllowShortBlocksOnASingleLine: false AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: All BinPackArguments: false BreakBeforeBraces: Stroustrup BreakBeforeTernaryOperators: true ColumnLimit: 79 DerivePointerAlignment: false IndentWidth: 4 IndentPPDirectives: AfterHash PointerAlignment: Left ReflowComments: true SpaceBeforeParens: ControlStatements SpacesInParentheses: false TabWidth: 4 UseTab: Never greenlet-3.1.0/.github/000077500000000000000000000000001467007427000146675ustar00rootroot00000000000000greenlet-3.1.0/.github/dependabot.yml000066400000000000000000000011041467007427000175130ustar00rootroot00000000000000# Keep GitHub Actions up to date with GitHub's Dependabot... # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem version: 2 updates: - package-ecosystem: github-actions directory: / groups: github-actions: patterns: - "*" # Group all Actions updates into a single larger pull request schedule: interval: monthly greenlet-3.1.0/.github/workflows/000077500000000000000000000000001467007427000167245ustar00rootroot00000000000000greenlet-3.1.0/.github/workflows/tests.yml000066400000000000000000000155121467007427000206150ustar00rootroot00000000000000name: tests on: [push, pull_request, workflow_dispatch] env: PYTHONHASHSEED: 1042466059 ZOPE_INTERFACE_STRICT_IRO: 1 PYTHONUNBUFFERED: 1 PYTHONDONTWRITEBYTECODE: 1 PYTHONDEVMODE: 1 PYTHONFAULTHANDLER: 1 PIP_UPGRADE_STRATEGY: eager # Don't get warnings about Python 2 support being deprecated. We # know. The env var works for pip 20. PIP_NO_PYTHON_VERSION_WARNING: 1 PIP_NO_WARN_SCRIPT_LOCATION: 1 # Uploading built wheels for releases. # TWINE_PASSWORD is encrypted and stored directly in the # repo settings. TWINE_USERNAME: __token__ jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] os: [ubuntu-latest] include: - os: macos-latest python-version: 3.12 - os: macos-latest python-version: 3.13 steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' cache-dependency-path: setup.py allow-prereleases: true - name: Install dependencies run: | python -m pip install -U pip setuptools wheel python -m pip install -U twine - name: Install greenlet (non-Mac) if: ${{ ! startsWith(runner.os, 'Mac') }} run: | python -m pip wheel --wheel-dir ./dist . python -m pip install -U -e ".[test,docs]" env: # Ensure we test with assertions enabled. # As opposed to the manylinux builds, which we distribute and # thus only use O3 (because Ofast enables fast-math, which has # process-wide effects), we test with Ofast here, because we # expect that some people will compile it themselves with that setting. CPPFLAGS: "-Ofast -UNDEBUG" - name: Install greenlet (Mac) if: startsWith(runner.os, 'Mac') run: | python -m pip wheel --wheel-dir ./dist . python -m pip install -U -e ".[test,docs]" ls -l dist # Something in the build system isn't detecting that we're building for both, # so we're getting tagged with just x86_64. Force the universal2 tag. # (I've verified that the .so files are in fact universal, with both architectures.) # The macosx_11_0 tag is conservative: At this writing, # on GHA, Python 3.7/3.8/3.9/3.10 all produce that tag, while # 3.11/3.12 produce the less restrictive macosx_10_9 tag. (Locally on JAM's mac, # the official CPython builds produce 10_9 for everything from 3.9 onward.) wheel tags --remove --platform-tag macosx_11_0_universal2 dist/*whl env: # Unlike the above, we are actually distributing these # wheels, so they need to be built for production use. CPPFLAGS: "-O3" # Build for both architectures ARCHFLAGS: "-arch x86_64 -arch arm64" - name: Check greenlet build run: | ls -l dist twine check dist/* - name: Store greenlet wheel uses: actions/upload-artifact@v4 with: name: greenlet-${{ runner.os }}-${{ matrix.python-version }}.whl path: dist/*whl - name: Test run: | python -VV python -c 'import greenlet._greenlet as G; assert G.GREENLET_USE_STANDARD_THREADING' python -m unittest discover -v greenlet.tests - name: Doctest run: | sphinx-build -b doctest -d docs/_build/doctrees2 docs docs/_build/doctest2 - name: Lint if: matrix.python-version == '3.12' && 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/gh-action-pypi-publish@v1.4.1' because # that's apparently a container action, and those don't run on # the Mac. if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') && startsWith(runner.os, 'Mac') env: TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} run: | twine upload --skip-existing dist/* CodeQL: runs-on: ubuntu-latest permissions: # required for all workflows security-events: write steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.10" cache: 'pip' cache-dependency-path: setup.py - name: Install dependencies run: | python -m pip install -U pip python -m pip install -U setuptools wheel # Set the `CODEQL-PYTHON` environment variable to the Python executable # that includes the dependencies echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: python, cpp # Override the default behavior so that the action doesn't attempt # to auto-install Python dependencies setup-python-dependencies: false - name: Install greenlet run: | python setup.py build # - name: Autobuild # uses: github/codeql-action/autobuild@v1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 manylinux: runs-on: ubuntu-latest # We use a regular Python matrix entry to share as much code as possible. strategy: matrix: python-version: [3.9] image: - manylinux_2_28_x86_64 - manylinux2014_aarch64 - manylinux2014_ppc64le - manylinux2014_s390x - manylinux2014_x86_64 - musllinux_1_1_x86_64 - musllinux_1_1_aarch64 name: ${{ matrix.image }} steps: - name: checkout uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: all - name: Build and test greenlet env: DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }} run: bash ./make-manylinux - name: Store greenlet wheels uses: actions/upload-artifact@v4 with: path: wheelhouse/*whl name: ${{ matrix.image }}_wheels.zip - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@v1.10.1 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') with: user: __token__ password: ${{ secrets.TWINE_PASSWORD }} skip_existing: true packages_dir: wheelhouse/ greenlet-3.1.0/.gitignore000066400000000000000000000002161467007427000153160ustar00rootroot00000000000000*.so *.pyd *.pyc *.pyo build/ dist/ .tox/ wheelhouse/ greenlet.egg-info/ /docs/_build __pycache__/ /.ropeproject/ /MANIFEST benchmarks/*.json greenlet-3.1.0/.pylintrc000066400000000000000000000214241467007427000151770ustar00rootroot00000000000000[MASTER] 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 # 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] # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). # NOTE: comments must go ABOVE the statement. In Python 2, mixing in # comments disables all directives that follow, while in Python 3, putting # comments at the end of the line does the same thing (though Py3 supports # mixing) # invalid-name, ; We get lots of these, especially in scripts. should fix many of them # protected-access, ; We have many cases of this; legit ones need to be examinid and commented, then this removed # no-self-use, ; common in superclasses with extension points # too-few-public-methods, ; Exception and marker classes get tagged with this # exec-used, ; should tag individual instances with this, there are some but not too many # global-statement, ; should tag individual instances # multiple-statements, ; "from gevent import monkey; monkey.patch_all()" # locally-disabled, ; yes, we know we're doing this. don't replace one warning with another # cyclic-import, ; most of these are deferred imports # too-many-arguments, ; these are almost always because that's what the stdlib does # redefined-builtin, ; likewise: these tend to be keyword arguments like len= in the stdlib # undefined-all-variable, ; XXX: This crashes with pylint 1.5.4 on Travis (but not locally on Py2/3 # ; or landscape.io on Py3). The file causing the problem is unclear. UPDATE: identified and disabled # that file. # see https://github.com/PyCQA/pylint/issues/846 # useless-suppression: the only way to avoid repeating it for specific statements everywhere that we # do Py2/Py3 stuff is to put it here. Sadly this means that we might get better but not realize it. # duplicate-code: Yeah, the compatibility ssl modules are much the same # In pylint 1.8.0, inconsistent-return-statements are created for the wrong reasons. # This code raises it, even though there's only one return (the implicit 'return None' is presumably # what triggers it): # def foo(): # if baz: # return 1 # In Pylint 2dev1, needed for Python 3.7, we get spurious 'useless return' errors: # @property # def foo(self): # return None # generates useless-return # Pylint 2.4 adds import-outside-toplevel. But we do that a lot to defer imports because of patching. # 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, 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] # 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 in the report (???) notes= [VARIABLES] dummy-variables-rgx=_.* [TYPECHECK] # 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. # 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 # with qualified names. #ignored-classes=SSLContext, SSLSocket, greenlet, Greenlet, parent, dead # List of module names for which member attributes should not be checked # (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 [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 # Local Variables: # mode: conf # End: greenlet-3.1.0/.readthedocs.yml000066400000000000000000000010151467007427000164120ustar00rootroot00000000000000# .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: builder: html configuration: docs/conf.py # 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: install: - method: pip path: . extra_requirements: - docs greenlet-3.1.0/AUTHORS000066400000000000000000000015211467007427000143760ustar00rootroot00000000000000Original Authors ---------------- * Armin Rigo * Christian Tismer Contributors ------------ * Al Stone * Alexander Schmidt * Alexey Borzenkov * Andreas Schwab * Armin Ronacher * Bin Wang * Bob Ippolito * ChangBo Guo * Christoph Gohlke * Denis Bilenko * Dirk Mueller * Donovan Preston * Fantix King * Floris Bruynooghe * Fredrik Fornwall * Gerd Woetzel * Giel van Schijndel * Gökhan Karabulut * Gustavo Niemeyer * Guy Rozendorn * Hye-Shik Chang * Jared Kuolt * Jason Madden * Josh Snyder * Kyle Ambroff * Laszlo Boszormenyi * Mao Han * Marc Abramowitz * Marc Schlaich * Marcin Bachry * Matt Madison * Matt Turner * Michael Ellerman * Michael Matz * Ralf Schmitt * Robie Basak * Ronny Pfannschmidt * Samual M. Rushing * Tony Bowles * Tony Breeds * Trevor Bowen * Tulio Magno Quites Machado Filho * Ulrich Weigand * Victor Stinner greenlet-3.1.0/CHANGES.rst000066400000000000000000000541411467007427000151360ustar00rootroot00000000000000========= Changes ========= 3.1.0 (2024-09-10) ================== .. note:: This will be the last release to support Python 3.7 and 3.8. - Adds support for Python 3.13. .. note:: greenlet will not work in no-gil (free threaded) builds of CPython. Internally, greenlet heavily depends on the GIL. - Greatly reduce the chances for crashes during interpreter shutdown. See `issue 411 `_. Platform Support ---------------- Support for the following platforms was contributed by the community. Note that they are untested by this project's continuous integration services. - Hitachi's `SuperH CPU `_. - `NetBSD on PowerPC. `_ - RiscV 64 with `-fno-omit-frame-pointer `_. Note that there are `known test failures `_, so this platform may not work reliably at all. 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 `_. Fix by Joshua Oreman in `PR 393 `_. 3.0.2 (2023-12-08) ================== - Packaging: Add a minimal ``pyproject.toml`` to sdists. - Packaging: Various updates to macOS wheels. - Fix a test case on Arm32. Note that this is not a supported platform (there is no CI for it) and support is best effort; there may be other issues lurking. See `issue 385 `_ 3.0.1 (2023-10-25) ================== - Fix a potential crash on Python 3.8 at interpreter shutdown time. This was a regression from earlier 3.0.x releases. Reported by Matt Wozniski in `issue 376 `_. 3.0.0 (2023-10-02) ================== - No changes from 3.0rc3 aside from the version number. 3.0.0rc3 (2023-09-12) ===================== - Fix an intermittent error during process termination on some platforms (GCC/Linux/libstdc++). 3.0.0rc2 (2023-09-09) ===================== - Fix some potential bugs (assertion failures and memory leaks) in previously-untested error handling code. In some cases, this means that the process will execute a controlled ``abort()`` after severe trouble when previously the process might have continued for some time with a corrupt state. It is unlikely those errors occurred in practice. - Fix some assertion errors and potential bugs with re-entrant switches. - Fix a potential crash when certain compilers compile greenlet with high levels of optimization. The symptom would be that switching to a greenlet for the first time immediately crashes. - Fix a potential crash when the callable object passed to the greenlet constructor (or set as the ``greenlet.run`` attribute) has a destructor attached to it that switches. Typically, triggering this issue would require an unlikely subclass of ``greenlet.greenlet``. - Python 3.11+: Fix rare switching errors that could occur when a garbage collection was triggered during the middle of a switch, and Python-level code in ``__del__`` or weakref callbacks switched to a different greenlet and ultimately switched back to the original greenlet. This often manifested as a ``SystemError``: "switch returned NULL without an exception set." For context on the fixes, see `gevent issue #1985 `_. 3.0.0rc1 (2023-09-01) ===================== - Windows wheels are linked statically to the C runtime in an effort to prevent import errors on systems without the correct C runtime installed. It's not clear if this will make the situation better or worse, so please share your experiences in `issue 346 `_. Note that this only applies to the binary wheels found on PyPI. Building greenlet from source defaults to the shared library. Set the environment variable ``GREENLET_STATIC_RUNTIME=1`` at build time to change that. - Build binary wheels for Python 3.12 on macOS. - Fix compiling greenlet on a debug build of CPython 3.12. There is `one known issue `_ that leads to an interpreter crash on debug builds. - Python 3.12: Fix walking the frame stack of suspended greenlets. Previously accessing ``glet.gr_frame.f_back`` would crash due to `changes in CPython's undocumented internal frame handling `_. Platforms --------- - Now, greenlet *may* compile and work on Windows ARM64 using llvm-mingw, but this is untested and unsupported. See `PR `_ by Adrian Vladu. - Now, greenlet *may* compile and work on LoongArch64 Linux systems, but this is untested and unsupported. See `PR 257 `_ by merore. Known Issues ------------ - There may be (very) subtle issues with tracing on Python 3.12, which has redesigned the entire tracing infrastructure. 3.0.0a1 (2023-06-21) ==================== - Build binary wheels for S390x Linux. See `PR 358 `_ from Steven Silvester. - Fix a rare crash on shutdown seen in uWSGI deployments. See `issue 330 `_ and `PR 356 `_ from Andrew Wason. - Make the platform-specific low-level C/assembly snippets stop using the ``register`` storage class. Newer versions of standards remove this storage class, and it has been generally ignored by many compilers for some time. See `PR 347 `_ from Khem Raj. - Add initial support for Python 3.12. See `issue `_ and `PR `_; thanks go to (at least) Michael Droettboom, Andreas Motl, Thomas A Caswell, raphaelauv, Hugo van Kemenade, Mark Shannon, and Petr Viktorin. - Remove support for end-of-life Python versions, including Python 2.7, Python 3.5 and Python 3.6. - Require a compiler that supports ``noinline`` directives. See `issue 271 `_. - Require a compiler that supports C++11. 2.0.2 (2023-01-28) ================== - Fix calling ``greenlet.settrace()`` with the same tracer object that was currently active. See `issue 332 `_. - Various compilation and standards conformance fixes. See #335, #336, #300, #302, #334. 2.0.1 (2022-11-07) ================== - Python 3.11: Fix a memory leak. See `issue 328 `_ and `gevent issue 1924 `_. 2.0.0.post0 (2022-11-03) ======================== - Add ``Programming Language :: Python :: 3.11`` to the PyPI classifier metadata. 2.0.0 (2022-10-31) ================== - Nothing changed yet. 2.0.0rc5 (2022-10-31) ===================== - Linux: Fix another group of rare crashes that could occur when shutting down an interpreter running multiple threads. See `issue 325 `_. 2.0.0rc4 (2022-10-30) ===================== - Linux: Fix a rare crash that could occur when shutting down an interpreter running multiple threads, when some of those threads are in greenlets making calls to functions that release the GIL. 2.0.0rc3 (2022-10-29) ===================== - Python 2: Fix a crash that could occur when raising an old-style instance object. 2.0.0rc2 (2022-10-28) ===================== - Workaround `a CPython 3.8 bug `_ that could cause the interpreter to crash during an early phase of shutdown with the message "Fatal Python error: Python memory allocator called without holding the GI." This only impacted CPython 3.8a3 through CPython 3.9a5; the fix is only applied to CPython 3.8 releases (please don't use an early alpha release of CPython 3.9). 2.0.0rc1 (2022-10-27) ===================== - Deal gracefully with greenlet switches that occur while deferred deallocation of objects is happening using CPython's "trash can" mechanism. Previously, if a large nested container held items that switched greenlets during delayed deallocation, and that second greenlet also invoked the trash can, CPython's internal state could become corrupt. This was visible as an assertion error in debug builds. Now, the relevant internal state is saved and restored during greenlet switches. See also `gevent issue 1909 `_. - Rename the C API function ``PyGreenlet_GET_PARENT`` to ``PyGreenlet_GetParent`` for consistency. The old name remains available as a deprecated alias. 2.0.0a2 (2022-03-24) ==================== - Fix a crash on older versions of the Windows C runtime when an unhandled C++ exception was thrown inside a greenlet by another native extension. This is a bug in that extension, and the interpreter will still abort, but at least it does so deliberately. Thanks to Kirill Smelkov. See `PR 286 `_. - Musllinux wheels for aarch64 are now built, tested, and uploaded to PyPI. Thanks to Alexander Piskun. - This version of greenlet is known to compile and pass tests on CPython 3.11.0a6. Earlier 3.11 releases will not work; later releases may or may not work. See `PR 294 `_. Special thanks to Victor Stinner, Brandt Bucher and the CPython developers. 2.0.0a1 (2022-01-20) ==================== Platforms --------- - Add experimental, untested support for 64-bit Windows on ARM using MSVC. See `PR 271 `_. - Drop support for very old versions of GCC and MSVC. - Compilation now requires a compiler that either supports C++11 or has some other intrinsic way to create thread local variables; for older GCC, clang and SunStudio we use ``__thread``, while for older MSVC we use ``__declspec(thread)``. - Wheels compatible with the musllinux specification are built, tested, and uploaded to PyPI for x86_64. (This was retroactively done for version 1.1.2 as well.) - This version of greenlet is known to compile and pass tests on CPython 3.11.0a4. Earlier or later 3.11 releases may or may not work. See `PR 280 `_. Special thanks to Brandt Bucher and the CPython developers. Fixes ~~~~~ - Fix several leaks that could occur when using greenlets from multiple threads. For example, it is no longer necessary to call ``getcurrent()`` before exiting a thread to allow its main greenlet to be cleaned up. See `issue 252 `_. - Fix the C API ``PyGreenlet_Throw`` to perform the same error checking that the Python API ``greenlet.throw()`` does. Previously, it did no error checking. - Fix C++ exception handling on 32-bit Windows. This might have ramifications if you embed Python in your application and also use SEH on 32-bit windows, or if you embed Python in a C++ application. Please contact the maintainers if you have problems in this area. In general, C++ exception handling is expected to be better on most platforms. This work is ongoing. Changes ~~~~~~~ - The repr of some greenlets has changed. In particular, if the greenlet object was running in a thread that has exited, the repr now indicates that. *NOTE:* The repr of a greenlet is not part of the API and should not be relied upon by production code. It is likely to differ in other implementations such as PyPy. - Main greenlets from threads that have exited are now marked as dead. 1.1.3.post0 (2022-10-10) ======================== - Add musllinux (Alpine) binary wheels. .. important:: This preliminary support for Python 3.11 leaks memory. Please upgrade to greenlet 2 if you're using Python 3.11. 1.1.3 (2022-08-25) ================== - Add support for Python 3.11. Please note that Windows binary wheels are not available at this time. .. important:: This preliminary support for Python 3.11 leaks memory. Please upgrade to greenlet 2 if you're using Python 3.11. 1.1.2 (2021-09-29) ================== - Fix a potential crash due to a reference counting error when Python subclasses of ``greenlet.greenlet`` were deallocated. The crash became more common on Python 3.10; on earlier versions, silent memory corruption could result. See `issue 245 `_. Patch by fygao-wish. - Fix a leak of a list object when the last reference to a greenlet was deleted from some other thread than the one to which it belonged. For this to work correctly, you must call a greenlet API like ``getcurrent()`` before the thread owning the greenlet exits: this is a long-standing limitation that can also lead to the leak of a thread's main greenlet if not called; we hope to lift this limitation. Note that in some cases this may also fix leaks of greenlet objects themselves. See `issue 251 `_. - Python 3.10: Tracing or profiling into a spawned greenlet didn't work as expected. See `issue 256 `_, reported by Joe Rickerby. 1.1.1 (2021-08-06) ================== - Provide Windows binary wheels for Python 3.10 (64-bit only). - Update Python 3.10 wheels to be built against 3.10rc1, where applicable. 1.1.0 (2021-05-06) ================== - Add support for Python 3.10. Pre-built binary wheels for 3.10 are not currently available for all platforms. The greenlet ABI is different on Python 3.10 from all previous versions, but as 3.10 was never supported before, and the ABI has not changed on other Python versions, this is not considered a reason to change greenlet's major version. 1.0.0 (2021-01-13) ================== - Fix %s and %r formatting of a greenlet on Python 2. Previously it would result in a Unicode string instead of a native string. See `issue 218 `_. - Move continuous integration from Travis CI to Github Actions. 1.0a1 (2020-11-20) ================== - Add the ability to set a greenlet's PEP 567 contextvars context directly, by assigning to the greenlet's ``gr_context`` attribute. This restores support for some patterns of using greenlets atop an async environment that became more challenging in 0.4.17. Thanks to Joshua Oreman, Mike bayer, and Fantix King, among others. See `PR 198 `_. - The repr of greenlet objects now includes extra information about its state. This is purely informative and the details are subject to change. See `issue 215 `_. - The ``greenlet`` module is now a package. There are no API changes, so all existing imports, including from C code, should continue to work. - (C API) The undocumented ``GREENLET_VERSION`` macro that defined a string giving the greenlet version is now deprecated and will not be updated. - (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 help installation tools understand supported versions. - Add partial support for AIX ppc64 and IBM i. Thanks to Jesse Gorzinski and Kevin Adler. See `PR 197 `_. Packaging Changes ----------------- - Require setuptools to build from source. - Stop asking setuptools to build both .tar.gz and .zip sdists. PyPI has standardized on .tar.gz for all platforms. - Stop using a custom distutils command to build extensions. distutils is deprecated. - Remove the ability to use the deprecated command ``python setup.py test``. Run greenlet tests with your favorite unittest-compatible test runner, e.g., ``python -m unittest discover greenlet.tests``. See `issue 185 `_. - The directory layout and resulting sdists have changed. See `issue 184 `_. - greenlet is now always built with support for tracing and garbage collection, and, on Python 3.7 and above, support for context variables. The internal and undocumented C preprocessor macros that could be used to alter that at compile time have been removed (no combination other than the defaults was ever tested). This helps define a stable ABI. 0.4.17 (2020-09-22) =================== - Support for PEP 567 ContextVars 0.4.16 ====== - Support for DEC Alpha architecture - Support for Python 3.9 - Support for Python 3.10a0 0.4.15 ====== - Support for RISC-V architecture - Workaround a gcc bug on ppc64 0.4.14 ====== - Support for C-SKY architecture - Fixed support for ppc64 ABI - Fixed support for Python 3.7 0.4.13 ====== - Support for Python 3.7 - Support for MinGW x64 0.4.12 ====== - Stop using trashcan api 0.4.11 ====== - Fixes for aarch64 architecture 0.4.10 ====== - Added missing files to manifest - Added workaround for ppc32 on Linux - Start building binary manylinux1 wheels 0.4.9 ===== - Fixed Windows builds 0.4.8 ===== - Added support for iOS (arm32) - Added support for ppc64le 0.4.7 ===== - Added a missing workaround for ``return 0`` on mips - Restore compatibility with Python 2.5 - Fixed stack switching on sparc 0.4.6 ===== - Expose ``_stack_saved`` property on greenlet objects, it may be used to introspect the amount of memory used by a saved stack, but the API is subject to change in the future - Added a workaround for ``return 0`` compiler optimizations on all architectures - C API typo fixes 0.4.5 ===== - Fixed several bugs in greenlet C API - Fixed a bug in multi-threaded applications, which manifested itself with spurious "cannot switch to a different thread" exceptions - Fixed some crashes on arm and mips architectures 0.4.4 ===== - Fixed PyGreenlet_SetParent signature, thanks to BoonsNaibot - Fixed 64-bit Windows builds depending on wrong runtime dll 0.4.3 ===== - Better slp_switch performance on SPARC - Drop support for Python 2.3 - Fix trashcan assertions on debug builds of Python - Remove deprecated -fno-tree-dominator-opts compiler switch - Enable switch code for SunStudio on 32-bit SunOS - Support for abc abstract methods in greenlet subclasses - Support custom directories for tests - Document switch tracing support 0.4.2 ===== - Add .travis.yml - Fix 'err' may be used uninitialized in this function - Check _MSC_VER for msvc specific code - Fix slp_switch on SPARC for multi-threaded environments - Add support for m68k 0.4.1 ===== * fix segfaults when using gcc 4.8 on amd64/x86 unix * try to disable certain gcc 4.8 optimizations that make greenlet crash * Fix greenlet on aarch64 with gcc 4.8 * workaround segfault on SunOS/sun4v * Add support for Aarch64 * Add support for x32 psABI on x86_64 * Changed memory constraints for assembly macro for PPC Linux platforms. 0.4.0 ===== * Greenlet has an instance dictionary now, which means it can be used for implementing greenlet local storage, etc. However, this might introduce incompatibility if subclasses have ``__dict__`` in their ``__slots__``. Classes like that will fail, because greenlet already has ``__dict__`` out of the box. * Greenlet no longer leaks memory after thread termination, as long as terminated thread has no running greenlets left at the time. * Add support for debian sparc and openbsd5-sparc64 * Add support for ppc64 linux * Don't allow greenlets to be copied with copy.copy/deepcopy * Fix arm32/thumb support * Restore greenlet's parent after kill * Add experimental greenlet tracing 0.3.4 ===== * Use plain distutils for install command, this fixes installation of the greenlet.h header. * Enhanced arm32 support * Fix support for Linux/S390 zSeries * Workaround compiler bug on RHEL 3 / CentOS 3 0.3.3 ===== * Use sphinx to build documentation and publish it on greenlet.rtfd.org * Prevent segfaults on openbsd 4/i386 * Workaround gcc-4.0 not allowing to clobber rbx * Enhance test infrastructure * Fix possible compilation problems when including greenlet.h in C++ mode * Make the greenlet module work on x64 windows * Add a test for greenlet C++ exceptions * Fix compilation on Solaris with SunStudio 0.3.2 ===== * Fix various crashes with recent gcc versions and VC90 * Try to fix stack save/restore on arm32 * Store and restore the threadstate on exceptions like pypy/stackless do * GreenletExit is now based on BaseException on Python >= 2.5 * Switch to using PyCapsule for Python 2.7 and 3.1 * Port for AIX on PowerPC * Fix the sparc/solaris header * Improved build dependencies patch from flub. * Can't pass parent=None to greenlet.greenlet() (fixes #21) * Rudimentary gc support (only non-live greenlets are garbage collected though) 0.3.1 ===== * Fix reference leak when passing keyword arguments to greenlets (mbachry) * Updated documentation. 0.3 === * Python 3 support. * New C API to expose Greenlets to C Extensions. * greenlet.switch() now accept's keyword arguments. * Fix Python crasher caused by switching to new greenlet from another thread. * Fix Python 2.6 crash on Windows when built with VS2009. (arigo) * arm32 support from stackless (Sylvain Baro) * Linux mips support (Thiemo Seufer) * MingGW GCC 4.4 support (Giovanni Bajo) * Fix for a threading bug (issue 40 in py lib) (arigo and ghazel) * Loads more unit tests, some from py lib (3 times as many as Greenlet 0.2) * Add documentation from py lib. * General code, documentation and repository cleanup (Kyle Ambroff, Jared Kuolt) greenlet-3.1.0/LICENSE000066400000000000000000000026321467007427000143370ustar00rootroot00000000000000The following files are derived from Stackless Python and are subject to the same license as Stackless Python: src/greenlet/slp_platformselect.h files in src/greenlet/platform/ directory See LICENSE.PSF and http://www.stackless.com/ for details. Unless otherwise noted, the files in greenlet have been released under the following MIT license: Copyright (c) Armin Rigo, Christian Tismer and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. greenlet-3.1.0/LICENSE.PSF000066400000000000000000000045701467007427000147710ustar00rootroot00000000000000PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. greenlet-3.1.0/MANIFEST.in000066400000000000000000000016251467007427000150710ustar00rootroot00000000000000### # Source code ### recursive-include src *.py recursive-include src *.c recursive-include src *.cpp recursive-include src *.hpp recursive-include src *.h recursive-include src *.cmd recursive-include src *.asm recursive-include src *.obj ### # Benchmarks and results ### exclude benchmarks/*.json recursive-include benchmarks *.py ### # Documentation ### recursive-include docs * prune docs/_build ### # Configuration and project files ### include *.yml include *.txt include *.ini include *.rst include *.cfg include *.py include *.ini include *.toml include .clang-format include .pylintrc recursive-include appveyor *.cmd recursive-include appveyor *.ps1 recursive-include appveyor *.py recursive-include .github *.yml include AUTHORS include LICENSE include LICENSE.PSF include MANIFEST.in include make-manylinux global-exclude *.pyc global-exclude *.pyd global-exclude *.so global-exclude .coverage greenlet-3.1.0/README.rst000066400000000000000000000040221467007427000150140ustar00rootroot00000000000000.. This file is included into docs/history.rst Greenlets are lightweight coroutines for in-process concurrent programming. The "greenlet" package is a spin-off of `Stackless`_, a version of CPython that supports micro-threads called "tasklets". Tasklets run pseudo-concurrently (typically in a single or a few OS-level threads) and are synchronized with data exchanges on "channels". A "greenlet", on the other hand, is a still more primitive notion of micro-thread with no implicit scheduling; coroutines, in other words. This is useful when you want to control exactly when your code runs. You can build custom scheduled micro-threads on top of greenlet; however, it seems that greenlets are useful on their own as a way to make advanced control flow structures. For example, we can recreate generators; the difference with Python's own generators is that our generators can call nested functions and the nested functions can yield values too. (Additionally, you don't need a "yield" keyword. See the example in `test_generator.py `_). Greenlets are provided as a C extension module for the regular unmodified interpreter. .. _`Stackless`: http://www.stackless.com Who is using Greenlet? ====================== There are several libraries that use Greenlet as a more flexible alternative to Python's built in coroutine support: - `Concurrence`_ - `Eventlet`_ - `Gevent`_ .. _Concurrence: http://opensource.hyves.org/concurrence/ .. _Eventlet: http://eventlet.net/ .. _Gevent: http://www.gevent.org/ Getting Greenlet ================ The easiest way to get Greenlet is to install it with pip:: pip install greenlet Source code archives and binary distributions are available on the python package index at https://pypi.org/project/greenlet The source code repository is hosted on github: https://github.com/python-greenlet/greenlet Documentation is available on readthedocs.org: https://greenlet.readthedocs.io greenlet-3.1.0/appveyor.yml000066400000000000000000000160371467007427000157260ustar00rootroot00000000000000clone_depth: 50 max_jobs: 8 shallow_clone: true build: parallel: true verbosity: minimal # The VS 2019 image doesn't have # the MSVC needed for Python 2.7. # Note that as of 2020-11-11, this does not include # a Python 3.9 build. image: Visual Studio 2015 environment: global: APPVEYOR_SAVE_CACHE_ON_ERROR: "true" # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the # /E:ON and /V:ON options are not enabled in the batch script interpreter # See: http://stackoverflow.com/a/13751649/163740 CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd" # Use a fixed hash seed for reproducibility PYTHONHASHSEED: 8675309 PYTHONDEVMODE: 1 PYTHONFAULTHANDLER: 1 PYTHONUNBUFFERED: 1 # Try to avoid pulling in the C runtime, as some users won't have the # right package. GREENLET_STATIC_RUNTIME: "1" # Don't get warnings about Python 2 support being deprecated. We # know. PIP_NO_PYTHON_VERSION_WARNING: 1 PIP_UPGRADE_STRATEGY: eager # Enable this if debugging a resource leak. Otherwise # it slows things down. # PYTHONTRACEMALLOC: 10 # Upload settings for twine. TWINE_USERNAME: "__token__" TWINE_PASSWORD: secure: 9JKBIB2o2S18+REaEqzUrP/1g08eRdX3eEa7D/BBN5ae0XHTlrPqbvRTNJceiUa/o3r4bejFF3o0xA69ueMd+09PGKAAAfpQnwnQPInuLVkOq3mprNk4wW0GyWLFzI3WqZhYnWH7PZtmG4Kr7mNOyd6Qdi773kN3Hn7CNhvk+ik/K3zbsGerb2YYloM/KIQSJbgdEqNcIIItoqrZzg/cqiM/47Pz7ZzcGDvevD8Nx/0lVGqFVJnj8cMevwa9iDPYn7fB59Y1GEEbtBjenrWr1Q== matrix: # http://www.appveyor.com/docs/installed-software#python # Fully supported 64-bit versions, with testing. This should be # all the current (non EOL) versions. - PYTHON: "C:\\Python313-x64" PYTHON_VERSION: "3.13.0" PYTHON_ARCH: "64" PYTHON_EXE: python APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - PYTHON: "C:\\Python312-x64" PYTHON_VERSION: "3.12.0" PYTHON_ARCH: "64" PYTHON_EXE: python APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - PYTHON: "C:\\Python311-x64" PYTHON_VERSION: "3.11.0" PYTHON_ARCH: "64" PYTHON_EXE: python APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - PYTHON: "C:\\Python310-x64" PYTHON_VERSION: "3.10.0" PYTHON_ARCH: "64" PYTHON_EXE: python APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - PYTHON: "C:\\Python39-x64" PYTHON_ARCH: "64" PYTHON_VERSION: "3.9.x" PYTHON_EXE: python APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON: "C:\\Python38-x64" PYTHON_ARCH: "64" PYTHON_VERSION: "3.8.x" PYTHON_EXE: python APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON: "C:\\Python37-x64" PYTHON_ARCH: "64" PYTHON_VERSION: "3.7.x" PYTHON_EXE: python APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 # Tested 32-bit versions. A small, hand-picked selection covering # important variations. No need to include newer versions of # cpython here, 32-bit x86 windows is on the way out. - PYTHON: "C:\\Python39" PYTHON_ARCH: "32" PYTHON_VERSION: "3.9.x" PYTHON_EXE: python APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 # Untested 64-bit versions. We don't expect any variance here from # the other tested 64-bit versions, OR they are very EOL # None right now. # Untested 32-bit versions. As above, we don't expect any variance # from the tested 32-bit versions, OR they are very EOL. - PYTHON: "C:\\Python38" PYTHON_ARCH: "32" PYTHON_VERSION: "3.8.x" PYTHON_EXE: python GWHEEL_ONLY: true - PYTHON: "C:\\Python37" PYTHON_ARCH: "32" PYTHON_VERSION: "3.7.x" PYTHON_EXE: python GWHEEL_ONLY: true cache: - "%TMP%\\py\\" - '%LOCALAPPDATA%\pip\Cache -> appveyor.yml,setup.py' install: # If there is a newer build queued for the same PR, cancel this one. # The AppVeyor 'rollout builds' option is supposed to serve the same # purpose but it is problematic because it tends to cancel builds pushed # directly to master instead of just PR builds (or the converse). # credits: JuliaLang developers. - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` throw "There are newer queued builds for this pull request, failing early." } ## Debugging - ECHO "Filesystem root:" - ps: "ls \"C:/\"" # - ECHO "Installed SDKs:" # - ps: "if(Test-Path(\"C:/Program Files/Microsoft SDKs/Windows\")) {ls \"C:/Program Files/Microsoft SDKs/Windows\";}" # Install Python (from the official .msi of http://python.org) and pip when # not already installed. # PyPy portion based on https://github.com/wbond/asn1crypto/blob/master/appveyor.yml - ps: $env:PYTMP = "${env:TMP}\py"; if (!(Test-Path "$env:PYTMP")) { New-Item -ItemType directory -Path "$env:PYTMP" | Out-Null; } if ("${env:PYTHON_ID}" -eq "pypy") { if (!(Test-Path "${env:PYTMP}\pypy2-v7.3.1-win32.zip")) { (New-Object Net.WebClient).DownloadFile('https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.3.1-win32.zip', "${env:PYTMP}\pypy2-v7.3.1-win32.zip"); } 7z x -y "${env:PYTMP}\pypy2-v7.3.1-win32.zip" -oC:\ | Out-Null; } elseif (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1; } # Prepend newly installed Python to the PATH of this build (this cannot be # done from inside the powershell script as it would require to restart # the parent CMD process). - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PYTHON%\\bin;%PATH%" - "SET PYEXE=%PYTHON%\\%PYTHON_EXE%.exe" - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" # Upgrade to the latest version of pip to avoid it displaying warnings # about it being out of date. Do this here instead of above in # powershell because the annoying 'DEPRECATION:blahblahblah 2.7 blahblahblah' # breaks powershell. - "%CMD_IN_ENV% %PYEXE% -mensurepip -U --user" - "%CMD_IN_ENV% %PYEXE% -mpip install -U --user pip" # Install requirements for running tests and building artifacts - "%CMD_IN_ENV% pip install --upgrade -r dev-requirements.txt" build_script: # Show any compiler warnings. - "%CMD_IN_ENV% python setup.py build_ext" - "%CMD_IN_ENV% python -m pip install -U -e .[test,docs]" - "%CMD_IN_ENV% python -m pip install -U zope.testrunner" test_script: - if not "%GWHEEL_ONLY%"=="true" %PYEXE% -m zope.testrunner --test-path=src -vvv # XXX: Doctest disabled pending sphinx release for 3.10; see tests.yml. # - "%CMD_IN_ENV% python -m sphinx -b doctest -d docs/_build/doctrees docs docs/_build/doctest" after_test: - "%CMD_IN_ENV% python -mpip wheel --wheel-dir dist ." artifacts: - path: dist\* deploy_script: - ps: if ($env:APPVEYOR_REPO_TAG -eq $TRUE) { pip install twine; twine upload --skip-existing dist/* } deploy: on greenlet-3.1.0/appveyor/000077500000000000000000000000001467007427000151745ustar00rootroot00000000000000greenlet-3.1.0/appveyor/install.ps1000066400000000000000000000160331467007427000172720ustar00rootroot00000000000000# Sample script to install Python and pip under Windows # Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ $MINICONDA_URL = "http://repo.continuum.io/miniconda/" $BASE_URL = "https://www.python.org/ftp/python/" $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" $GET_PIP_PATH = "C:\get-pip.py" $PYTHON_PRERELEASE_REGEX = @" (?x) (?\d+) \. (?\d+) \. (?\d+) (?[a-z]{1,2}\d+) "@ function Download ($filename, $url) { $webclient = New-Object System.Net.WebClient $basedir = $pwd.Path + "\" $filepath = $basedir + $filename if (Test-Path $filename) { Write-Host "Reusing" $filepath return $filepath } # Download and retry up to 3 times in case of network transient errors. Write-Host "Downloading" $filename "from" $url $retry_attempts = 2 for ($i = 0; $i -lt $retry_attempts; $i++) { try { $webclient.DownloadFile($url, $filepath) break } Catch [Exception]{ Start-Sleep 1 } } if (Test-Path $filepath) { Write-Host "File saved at" $filepath } else { # Retry once to get the error message if any at the last try $webclient.DownloadFile($url, $filepath) } return $filepath } function ParsePythonVersion ($python_version) { if ($python_version -match $PYTHON_PRERELEASE_REGEX) { return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro, $matches.prerelease) } $version_obj = [version]$python_version return ($version_obj.major, $version_obj.minor, $version_obj.build, "") } function DownloadPython ($python_version, $platform_suffix) { $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version if (($major -le 2 -and $micro -eq 0) ` -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) ` ) { $dir = "$major.$minor" $python_version = "$major.$minor$prerelease" } else { $dir = "$major.$minor.$micro" } if ($prerelease) { if (($major -le 2) ` -or ($major -eq 3 -and $minor -eq 1) ` -or ($major -eq 3 -and $minor -eq 2) ` -or ($major -eq 3 -and $minor -eq 3) ` ) { $dir = "$dir/prev" } } if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) { $ext = "msi" if ($platform_suffix) { $platform_suffix = ".$platform_suffix" } } else { $ext = "exe" if ($platform_suffix) { $platform_suffix = "-$platform_suffix" } } $filename = "python-$python_version$platform_suffix.$ext" $url = "$BASE_URL$dir/$filename" $filepath = Download $filename $url return $filepath } function InstallPython ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "" } else { $platform_suffix = "amd64" } $installer_path = DownloadPython $python_version $platform_suffix $installer_ext = [System.IO.Path]::GetExtension($installer_path) Write-Host "Installing $installer_path to $python_home" $install_log = $python_home + ".log" if ($installer_ext -eq '.msi') { InstallPythonMSI $installer_path $python_home $install_log } else { InstallPythonEXE $installer_path $python_home $install_log } if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallPythonEXE ($exepath, $python_home, $install_log) { $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home" RunCommand $exepath $install_args } function InstallPythonMSI ($msipath, $python_home, $install_log) { $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" $uninstall_args = "/qn /x $msipath" RunCommand "msiexec.exe" $install_args if (-not(Test-Path $python_home)) { Write-Host "Python seems to be installed else-where, reinstalling." RunCommand "msiexec.exe" $uninstall_args RunCommand "msiexec.exe" $install_args } } function RunCommand ($command, $command_args) { Write-Host $command $command_args Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru } function InstallPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $python_path = $python_home + "\python.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $webclient = New-Object System.Net.WebClient $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) Write-Host "Executing:" $python_path $GET_PIP_PATH & $python_path $GET_PIP_PATH } else { Write-Host "pip already installed." } } function DownloadMiniconda ($python_version, $platform_suffix) { if ($python_version -eq "3.4") { $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe" } else { $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe" } $url = $MINICONDA_URL + $filename $filepath = Download $filename $url return $filepath } function InstallMiniconda ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "x86" } else { $platform_suffix = "x86_64" } $filepath = DownloadMiniconda $python_version $platform_suffix Write-Host "Installing" $filepath "to" $python_home $install_log = $python_home + ".log" $args = "/S /D=$python_home" Write-Host $filepath $args Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallMinicondaPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $conda_path = $python_home + "\Scripts\conda.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $args = "install --yes pip" Write-Host $conda_path $args Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru } else { Write-Host "pip already installed." } } function main () { InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON InstallPip $env:PYTHON } main greenlet-3.1.0/appveyor/run_with_env.cmd000066400000000000000000000064461467007427000204020ustar00rootroot00000000000000:: To build extensions for 64 bit Python 3, we need to configure environment :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) :: :: To build extensions for 64 bit Python 2, we need to configure environment :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) :: :: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific :: environment configurations. :: :: Note: this script needs to be run with the /E:ON and /V:ON flags for the :: cmd interpreter, at least for (SDK v7.0) :: :: More details at: :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows :: http://stackoverflow.com/a/13751649/163740 :: :: Author: Olivier Grisel :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ :: :: Notes about batch files for Python people: :: :: Quotes in values are literally part of the values: :: SET FOO="bar" :: FOO is now five characters long: " b a r " :: If you don't want quotes, don't include them on the right-hand side. :: :: The CALL lines at the end of this file look redundant, but if you move them :: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y :: case, I don't know why. @ECHO OFF SET COMMAND_TO_RUN=%* SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf :: Extract the major and minor versions, and allow for the minor version to be :: more than 9. This requires the version number to have two dots in it. SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1% IF "%PYTHON_VERSION:~3,1%" == "." ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1% ) ELSE ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2% ) :: Based on the Python version, determine what SDK version to use, and whether :: to set the SDK for 64-bit. IF %MAJOR_PYTHON_VERSION% == 2 ( SET WINDOWS_SDK_VERSION="v7.0" SET SET_SDK_64=Y ) ELSE ( IF %MAJOR_PYTHON_VERSION% == 3 ( SET WINDOWS_SDK_VERSION="v7.1" IF %MINOR_PYTHON_VERSION% LEQ 4 ( SET SET_SDK_64=Y ) ELSE ( SET SET_SDK_64=N IF EXIST "%WIN_WDK%" ( :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ REN "%WIN_WDK%" 0wdf ) ) ) ELSE ( ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" EXIT 1 ) ) IF %PYTHON_ARCH% == 64 ( IF %SET_SDK_64% == Y ( ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture SET DISTUTILS_USE_SDK=1 SET MSSdk=1 "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ELSE ( ECHO Using default MSVC build environment for 64 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ) ELSE ( ECHO Using default MSVC build environment for 32 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) greenlet-3.1.0/benchmarks/000077500000000000000000000000001467007427000154445ustar00rootroot00000000000000greenlet-3.1.0/benchmarks/chain.py000077500000000000000000000144211467007427000171050ustar00rootroot00000000000000#!/usr/bin/env python """ Create a chain of coroutines and pass a value from one end to the other, where each coroutine will increment the value before passing it 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() next_greenlet.switch(value + 1) CHAIN_GREENLET_COUNT = 100000 def bm_chain(loops): begin = pyperf.perf_counter() for _ in range(loops): 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) assert x == CHAIN_GREENLET_COUNT end = pyperf.perf_counter() return end - begin GETCURRENT_INNER_LOOPS = 10 def bm_getcurrent(loops): getcurrent = greenlet.getcurrent getcurrent() # Factor out the overhead of creating the initial main greenlet begin = pyperf.perf_counter() for _ in range(loops): # Manual unroll getcurrent() getcurrent() getcurrent() getcurrent() getcurrent() getcurrent() getcurrent() getcurrent() getcurrent() getcurrent() end = pyperf.perf_counter() return end - begin SWITCH_INNER_LOOPS = 10000 def bm_switch_shallow(loops): # pylint:disable=attribute-defined-outside-init class G(greenlet.greenlet): other = None def run(self): o = self.other for _ in range(SWITCH_INNER_LOOPS): 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 begin = pyperf.perf_counter() for _ in range(loops): gl() gl() gl() gl() gl() gl() gl() gl() gl() gl() 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, inner_loops=CREATE_INNER_LOOPS ) runner.bench_time_func( '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 ) runner.bench_time_func( '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, ) greenlet-3.1.0/dev-requirements.txt000066400000000000000000000000211467007427000173600ustar00rootroot00000000000000setuptools wheel greenlet-3.1.0/docs/000077500000000000000000000000001467007427000142575ustar00rootroot00000000000000greenlet-3.1.0/docs/Makefile000066400000000000000000000127041467007427000157230ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/greenlet.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/greenlet.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/greenlet" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/greenlet" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." greenlet-3.1.0/docs/_static/000077500000000000000000000000001467007427000157055ustar00rootroot00000000000000greenlet-3.1.0/docs/_static/custom.css000066400000000000000000000064431467007427000177400ustar00rootroot00000000000000/* 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); } greenlet-3.1.0/docs/api.rst000066400000000000000000000060411467007427000155630ustar00rootroot00000000000000====================== Python API Reference ====================== .. currentmodule:: greenlet Exceptions ========== .. autoexception:: GreenletExit .. autoexception:: error Greenlets ========= .. autofunction:: getcurrent .. autoclass:: greenlet Greenlets support boolean tests: ``bool(g)`` is true if ``g`` is active and false if it is dead or not yet started. .. method:: switch(*args, **kwargs) Switches execution to this greenlet. See :ref:`switching`. .. automethod:: throw .. autoattribute:: dead True if this greenlet is dead (i.e., it finished its execution). .. 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. Generally, this should only be set once, before a greenlet begins running. Accessing or modifying this attribute raises :exc:`AttributeError` on Python versions 3.6 and earlier (which don't natively support the `contextvars` module) or if ``greenlet`` was built without contextvars support. For more information, see :doc:`contextvars`. .. versionadded:: 1.0.0 .. autoattribute:: gr_frame The frame that was active in this greenlet when it most recently called ``some_other_greenlet.switch()``, and that will resume execution when ``this_greenlet.switch()`` is next called. The remainder of the greenlet's stack can be accessed by following the frame object's ``f_back`` attributes. ``gr_frame`` is non-None only 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 cycles of parents. A greenlet without a parent is the main greenlet of its thread. Cannot be set to anything except a greenlet. .. autoattribute:: run The callable that this greenlet will run when it starts. After it is started, this attribute no longer exists. Subclasses can define this as a method on the type. Tracing ======= For details on tracing, see :doc:`tracing`. .. autofunction:: gettrace .. autofunction:: settrace :param callback: A callable object with the signature ``callback(event, args)``. greenlet-3.1.0/docs/c_api.rst000066400000000000000000000064611467007427000160730ustar00rootroot00000000000000================= C API Reference ================= Greenlets can be created and manipulated from extension modules written in C or C++, or from applications that embed Python. The ``greenlet.h`` header is provided, and exposes the entire API available to pure Python modules. Note that much of the API is implemented in terms of macros, meaning that it is not necessarily ABI stable. Types ===== .. c:type:: PyGreenlet The C name corresponding to the Python :class:`greenlet.greenlet`. Exceptions ========== .. c:type:: PyExc_GreenletError The C name corresponding to the Python :exc:`greenlet.error` .. c:type:: PyExc_GreenletExit The C name corresponding to the Python :exc:`greenlet.GreenletExit` Functions ========= .. c:function:: void PyGreenlet_Import(void) A macro that imports the greenlet module and initializes the C API. This must be called once for each extension module that uses the greenlet C API, usually in the module's init function. .. c:function:: int PyGreenlet_Check(PyObject* p) Macro that returns true if the argument is a :c:type:`PyGreenlet`. .. c:function:: int PyGreenlet_STARTED(PyGreenlet* g) Macro that returns true if the greenlet *g* has started. .. c:function:: int PyGreenlet_ACTIVE(PyGreenlet* g) Macro that returns true if the greenlet *g* has started and has not died. .. c:function:: PyGreenlet* PyGreenlet_GetParent(PyGreenlet* g) Macro that returns the parent greenlet of *g*. Returns a non-null pointer if there is a parent, or a null pointer on an error or if there is no parent. If this returns a non-null pointer, you must decrement its reference. .. c:function:: int PyGreenlet_SetParent(PyGreenlet* g, PyGreenlet* nparent) Set the parent greenlet of *g*. :return: 0 for success, or -1 on error. When an error is returned, *g* is not a pointer to a greenlet, and an :exc:`AttributeError` has been raised. .. c:function:: PyGreenlet* PyGreenlet_GetCurrent(void) Returns the currently active greenlet object. .. c:function:: PyGreenlet* PyGreenlet_New(PyObject* run, PyObject* parent) Creates a new greenlet object with the callable *run* and parent *parent*. Both parameters are optional and may be ``NULL``. :param run: If ``NULL``, the greenlet will be created, but will fail when switched to. :param parent: If ``NULL``, the parent is automatically set to the current greenlet. .. c:function:: PyObject* PyGreenlet_Switch(PyGreenlet* g, PyObject* args, PyObject* kwargs) Switches to the greenlet *g*. Besides *g*, the remaining parameters are optional and may be ``NULL``. :param args: If ``args`` is NULL, an empty tuple is passed to the target greenlet. If given, must be a :class:`tuple`. :param kwargs: If kwargs is ``NULL``, no keyword arguments are passed to the target greenlet. If given, must be a :class:`dict`. .. c:function:: PyObject* PyGreenlet_Throw(PyGreenlet* g, PyObject* typ, PyObject* val, PyObject* tb) Switches to greenlet *g*, but immediately raise an exception of type *typ* with the value *val*, and optionally, the traceback object *tb*. *tb* can be ``NULL``. The arguments *typ*, *val* and *tb* are interpreted as for :c:func:`PyErr_Restore`. greenlet-3.1.0/docs/caveats.rst000066400000000000000000000021611467007427000164370ustar00rootroot00000000000000========================== Caveats and Known Issues ========================== This document will describe known issues and sharp edges of greenlets. Native Functions Should Be Re-entrant ===================================== Use caution when switching greenlet stacks that include native (C) frames. Much like with threads, if the library function is not re-entrant, and more than one greenlet attempts to enter it, subtle problems can result. Common constructs in C that may not be reentrant include: - static variables in functions; - global variables. This was the source of an issue in gevent that led to corruption of libuv's internal state. The fix was to avoid re-entering the vulnerable function. Use Caution Mixing Greenlets and Signal Handlers ================================================ In CPython, signal handler functions *must* return in order for the rest of the program to proceed. Switching greenlets in a signal handler to, for example, get back to the main greenlet, such that the signal handler function doesn't really return to CPython, is likely to lead to a hang. See :issue:`143` for an example. greenlet-3.1.0/docs/changes.rst000066400000000000000000000000341467007427000164160ustar00rootroot00000000000000.. include:: ../CHANGES.rst greenlet-3.1.0/docs/conf.py000066400000000000000000000226351467007427000155660ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # greenlet documentation build configuration file, created by # sphinx-quickstart on Tue Dec 27 22:58:53 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # 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('.')) sys.path.append(os.path.abspath('../src/')) 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. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = [] # ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'greenlet' copyright = u'2011, Armin Rigo, Christian Tismer' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = glet_version # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. default_role = "obj" # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. 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 # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # 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'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'greenletdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'greenlet.tex', u'greenlet Documentation', u'Armin Rigo, Christian Tismer', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'greenlet', u'greenlet Documentation', [u'Armin Rigo, Christian Tismer'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'greenlet', u'greenlet Documentation', u'Armin Rigo, Christian Tismer', 'greenlet', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' intersphinx_mapping = { 'python': ('https://docs.python.org/', None), 'gevent': ('https://www.gevent.org/', None), } # Sphinx 1.8+ prefers this to `autodoc_default_flags`. It's documented that # either True or None mean the same thing as just setting the flag, but # only None works in 1.8 (True works in 2.0) autodoc_default_options = { # Our only type with members is ``greenlet``, and it # currently contains malformed ReST #'members': None, 'show-inheritance': None, } autodoc_member_order = 'groupwise' autoclass_content = 'both' extlinks = { 'issue': ('https://github.com/python-greenlet/greenlet/issues/%s', 'issue #%s'), 'pr': ('https://github.com/python-greenlet/greenlet/pull/%s', 'pull request #%s') } greenlet-3.1.0/docs/contextvars.rst000066400000000000000000000114141467007427000173720ustar00rootroot00000000000000============================= Context Variables (asyncio) ============================= .. versionadded:: 1.0.0 On Python versions (3.7 and above) that natively support context variables as defined in :pep:`567`, each greenlet runs by default in its own :class:`contextvars.Context`, enabling :class:`~contextvars.ContextVar`\s to be used for "greenlet-local storage". (If you need to support earlier Python versions, you can use attributes on the greenlet object instead.) A new greenlet's context is initially empty, i.e., all :class:`~contextvars.ContextVar`\s have their default values. This matches the behavior of a new thread, but differs from that of a new :class:`asyncio.Task`, which inherits a copy of the context that was active when it was spawned. You can assign to a greenlet's ``gr_context`` attribute to change the context that it will use. For example: .. doctest:: :pyversion: > 3.7 >>> import greenlet >>> import contextvars >>> example = contextvars.ContextVar("example", default=0) >>> def set_it(next_value): ... previous_value = example.get() ... print("Value of example in greenlet :", previous_value) ... print("Setting example in greenlet to:", next_value) ... example.set(next_value) >>> _ = example.set(1) By default, a new greenlet gets an empty context, unrelated to the current context: .. doctest:: :pyversion: > 3.7 >>> gr1 = greenlet.greenlet(set_it) >>> gr1.switch(2) Value of example in greenlet : 0 Setting example in greenlet to: 2 >>> example.get() 1 You can make a greenlet get a copy of the current context when it is created, like asyncio: .. doctest:: :pyversion: > 3.7 >>> gr2 = greenlet.greenlet(set_it) >>> gr2.gr_context = contextvars.copy_context() >>> gr2.switch(2) Value of example in greenlet : 1 Setting example in greenlet to: 2 You can also make a greenlet *share* the current context, like older, non-contextvars-aware versions of greenlet: .. doctest:: :pyversion: > 3.7 >>> gr3 = greenlet.greenlet(set_it) >>> gr3.gr_context = greenlet.getcurrent().gr_context >>> gr3.switch(2) Value of example in greenlet : 1 Setting example in greenlet to: 2 You can alternatively set a new greenlet's context by surrounding its top-level function in a call to :meth:`Context.run() `: .. doctest:: :pyversion: > 3.7 >>> _ = example.set(1) >>> gr4 = greenlet.greenlet(contextvars.copy_context().run) >>> gr4.switch(set_it, 2) Value of example in greenlet : 1 Setting example in greenlet to: 2 >>> example.get() 1 However, contextvars were not designed with greenlets in mind, so using :meth:`Context.run() ` becomes challenging in an environment with arbitrary greenlet-to-greenlet control transfers. The :meth:`~contextvars.Context.run` calls across all greenlets in a thread must effectively form a stack, where the last context entered is the first one to be exited. Also, it's not possible to have two calls to :meth:`~contextvars.Context.run` for the same context active in two different greenlets at the same time. Assigning to ``gr_context`` does not share these restrictions. You can access and change a greenlet's context almost no matter what state the greenlet is in. It can be dead, not yet started, or suspended (on any thread), or running (on the current thread only). Accessing or modifying ``gr_context`` of a greenlet running on a different thread raises :exc:`ValueError`. .. warning:: Changing the ``gr_context`` after a greenlet has begun running is not recommended for reasons outlined below. Once a greenlet has started running, ``gr_context`` tracks its *current* context: the one that would be active if you switched to the greenlet right now. This may not be the same as the value of ``gr_context`` before the greenlet started running. One potential difference occurs if a greenlet running in the default empty context (represented as ``None``) sets any context variables: a new :class:`~contextvars.Context` will be implicitly created to hold them, which will be reflected in ``gr_context``. Another one occurs if a greenlet makes a call to ``Context.run(some_inner, func)``: its ``gr_context`` will be ``some_inner`` until ``func()`` returns. .. warning:: Assigning to ``gr_context`` of an active greenlet that might be inside a call to :meth:`Context.run() ` is not recommended, because :meth:`~contextvars.Context.run` will raise an exception if the current context when it exits doesn't match the context that it set upon entry. The safest thing to do is set ``gr_context`` once, before starting the greenlet; then there's no potential conflict with :meth:`Context.run() ` calls. greenlet-3.1.0/docs/creating_executing_greenlets.rst000066400000000000000000000112161467007427000227310ustar00rootroot00000000000000================================== Creating And Executing Greenlets ================================== .. This document is a mess. It's a cross between how-to and API reference. .. currentmodule:: greenlet To create a new greenlet, simply instantiate a new object of class :class:`greenlet.greenlet`, passing it the initial function to run. .. tip:: If you're using a framework built on greenlets, such as :mod:`gevent`, consult its documentation. Some frameworks have other ways of creating new greenlets (for example, :func:`gevent.spawn`) or prefer a different greenlet class (for example, :class:`gevent.Greenlet`). .. doctest:: >>> import greenlet >>> def run(): ... print("Running in the greenlet function.") >>> glet = greenlet.greenlet(run) The greenlet will have its ``run`` attribute set to the function you passed, and its :ref:`parent ` will be the :func:`current greenlet `. .. doctest:: >>> glet.run is run True >>> glet.parent is greenlet.getcurrent() True Execution of the greenlet begins when :meth:`greenlet.switch` is called on it. .. doctest:: >>> glet.switch() Running in the greenlet function. The ``run`` attribute is deleted at that time. .. doctest:: >>> glet.run Traceback (most recent call last): ... AttributeError: run .. _subclassing_greenlet: Subclassing greenlet ==================== You can also subclass :class:`greenlet.greenlet` and define ``run`` as a method. This is useful to store additional state with the greenlet. .. doctest:: >>> import time >>> class MyGreenlet(greenlet.greenlet): ... created_at = None ... finished_at = None ... def run(self): ... self.created_at = time.time() ... print("Running in the greenlet subclass.") ... self.finished_at = time.time() >>> glet = MyGreenlet() >>> glet.switch() Running in the greenlet subclass. >>> glet.finished_at >= glet.created_at True See :ref:`switching` for more information about switching into greenlets. .. _changing_the_parent: Changing The Parent =================== When a greenlet finishes, :ref:`execution resumes with its parent `. This defaults to the current greenlet when the object was instantiated, but can be changed either at that time or any time later. To set it at creation time, pass the desired parent as the second argument: .. doctest:: >>> def parent(child_result): ... print("In the parent.") >>> parent_glet = greenlet.greenlet(parent) >>> def child(): ... print("In the child.") >>> child_glet = greenlet.greenlet(child, parent_glet) >>> child_glet.switch() In the child. In the parent. To change it later, assign to the ``greenlet.parent`` attribute. .. doctest:: >>> parent_glet = greenlet.greenlet(parent) >>> child_glet = greenlet.greenlet(child) >>> child_glet.parent = parent_glet >>> child_glet.switch() In the child. In the parent. Of course, cycles are not permitted. .. doctest:: >>> parent_glet = greenlet.greenlet(parent) >>> child_glet = greenlet.greenlet(child) >>> child_glet.parent = parent_glet >>> parent_glet.parent = child_glet Traceback (most recent call last): ... ValueError: cyclic parent chain The parent must be a greenlet. .. doctest:: >>> parent_glet.parent = 42 Traceback (most recent call last): ... TypeError: parent must be a greenlet Interrupting Greenlets by Throwing Exceptions ============================================= Besides simply :meth:`switching ` into a greenlet, you can also have it resume execution by throwing an exception into it. This is useful to interrupt a loop in the greenlet, for instance. .. doctest:: >>> main = greenlet.getcurrent() >>> class MyException(Exception): ... pass >>> def run(): ... try: ... main.switch() ... except MyException: ... print("Caught exception in greenlet.") >>> glet = greenlet.greenlet(run) >>> _ = glet.switch() >>> glet.throw(MyException) Caught exception in greenlet. Uncaught exceptions thrown into the greenlet will propagate into the parent greenlet. .. doctest:: >>> glet = greenlet.greenlet(run) >>> _ = glet.switch() >>> glet.throw(ValueError) Traceback (most recent call last): ... ValueError As a special case, if the uncaught exception is :exc:`greenlet.GreenletExit`, it will *not* propagate but instead be returned. This is commonly used to signal an "expected exit". .. doctest:: >>> glet = greenlet.greenlet(run) >>> _ = glet.switch() >>> glet.throw(greenlet.GreenletExit) GreenletExit() greenlet-3.1.0/docs/development.rst000066400000000000000000000013401467007427000173310ustar00rootroot00000000000000===================== Development Process ===================== This document is a collection of notes about how greenlet is developed. Github ====== The primary development location for greenlet is GitHub: https://github.com/python-greenlet/greenlet/ Releases ======== greenlet uses Semantic Versions; this includes changes to the ABI (breaking the ABI is considered a major change). Releases are made using `zest.releaser `_. .. code-block:: shell $ pip install zest.releaser[recommended] $ fullrelease Binary wheels are created and uploaded to PyPI for Windows, macOS, and Linux (x86_64 and aarch64) when a tag is pushed to the repository. The above command does this. greenlet-3.1.0/docs/greenlet.rst000066400000000000000000000133751467007427000166270ustar00rootroot00000000000000.. _what_is_greenlet: =================== greenlet Concepts =================== .. currentmodule:: greenlet .. |--| unicode:: U+2013 .. en dash .. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace :trim: A "greenlet" is a small independent pseudo-thread. Think about it as a small stack of frames; the outermost (bottom) frame is the initial function you called, and the innermost frame is the one in which the greenlet is currently paused. In code, greenlets are represented by objects of class :class:`greenlet`. These objects have a few defined attributes, and also have a ``__dict__``, allowing for arbitrary user-defined attributes. .. warning:: Attribute names beginning with ``gr_`` are reserved for this library. Switching greenlets =================== .. seealso:: :doc:`switching` You work with greenlets by creating a number of such stacks and jumping execution between them. Jumps are never implicit: a greenlet must choose to jump to another greenlet, which will cause the former to suspend and the latter to resume where it was suspended. Jumping between greenlets is called "switching". Similarly to ``generator.send(val)``, switching may pass objects between greenlets. The greenlet Lifecycle ====================== .. seealso:: Details And Examples :doc:`creating_executing_greenlets` Where does execution go when a greenlet dies? :ref:`greenlet_parents` When you create a greenlet, it gets an initially empty stack; when you first switch to it, it starts to run a specified function, which may call other functions, switch out of the greenlet, etc. When eventually the outermost function finishes its execution, the greenlet's stack becomes empty again and the greenlet is "dead". Greenlets can also die of an uncaught exception, or be :doc:`garbage collected ` (which raises an exception). .. rubric:: Example Let's quickly pull together an example demonstrating those concepts before continuing with a few more concepts. .. doctest:: >>> from greenlet import greenlet >>> def test1(): ... print("[gr1] main -> test1") ... gr2.switch() ... print("[gr1] test1 <- test2") ... return 'test1 done' >>> def test2(): ... print("[gr2] test1 -> test2") ... gr1.switch() ... print("This is never printed.") >>> gr1 = greenlet(test1) >>> gr2 = greenlet(test2) >>> gr1.switch() [gr1] main -> test1 [gr2] test1 -> test2 [gr1] test1 <- test2 'test1 done' >>> gr1.dead True >>> gr2.dead False The line ``gr1.switch()`` jumps to ``test1``, which prints that, jumps to ``test2``, and prints that, jumps back into ``test1``, prints that; and then ``test1`` finishes and ``gr1`` dies. At this point, the execution comes back to the original ``gr1.switch()`` call, which returns the value that ``test1`` returned. Note that ``test2`` is never switched back to and so doesn't print its final line; it is also not dead. Having seen that, we can continue with a few more concepts. The Current greenlet ==================== The greenlet that is actively running code is called the "current greenlet." The :class:`greenlet` object representing the current greenlet can be obtained by calling :func:`getcurrent`. (Note that :ref:`this could be a subclass `.) As long as a greenlet is running, no other greenlet can be running. Execution must be explicitly transferred by switching to a different greenlet. The Main greenlet ================= Initially, there is one greenlet that you don't have to create: the main greenlet. This is the only greenlet that can ever have :ref:`a parent of None `. The main greenlet can never be dead. This is true for :doc:`every thread in a process `. .. rubric:: Example .. doctest:: >>> from greenlet import getcurrent >>> def am_i_main(): ... current = getcurrent() ... return current.parent is None >>> am_i_main() True >>> glet = greenlet(am_i_main) >>> glet.switch() False .. _greenlet_parents: Greenlet Parents ================ Every greenlet, except the main greenlet, has a "parent" greenlet. The parent greenlet defaults to being the one in which the greenlet was created (this can be :ref:`changed at any time `). In this way, greenlets are organized in a tree. Top-level code that doesn't run in a user-created greenlet runs in the implicit main greenlet, which is the root of the tree. The parent is where execution continues when a greenlet dies, whether by explicitly returning from its function, "falling off the end" of its function, or by raising an uncaught exception. In the above example, both ``gr1`` and ``gr2`` have the main greenlet as a parent. Whenever one of them dies, the execution comes back to "main". Uncaught Exceptions are Raised In the Parent -------------------------------------------- Uncaught exceptions are propagated into the parent, too. For example, if the above ``test2()`` contained a typo, it would generate a :exc:`NameError` that would kill ``gr2``, and the exception would go back directly into "main". The traceback would show ``test2``, but not ``test1``. Remember, switches are not calls, but transfer of execution between parallel "stack containers", and the "parent" defines which stack logically comes "below" the current one. .. doctest:: >>> def test2(): ... print(this_should_be_a_name_error) >>> gr1 = greenlet(test1) >>> gr2 = greenlet(test2) >>> gr1.switch() Traceback (most recent call last): ... File "", line 1, in gr1.switch() File "", line 2, in test2 print(this_should_be_a_name_error) NameError: name 'this_should_be_a_name_error' is not defined greenlet-3.1.0/docs/greenlet_gc.rst000066400000000000000000000141111467007427000172650ustar00rootroot00000000000000================================== Garbage Collection and greenlets ================================== .. currentmodule:: greenlet If all the references to a greenlet object go away (including the references from the parent attribute of other greenlets), then there is no way to ever switch back to this greenlet. In this case, a :exc:`GreenletExit` exception is generated into the greenlet. This is the only case where a greenlet receives the execution asynchronously (without an explicit call to :meth:`greenlet.switch`). This gives ``try/finally`` blocks a chance to clean up resources held by the greenlet. This feature also enables a programming style in which greenlets are infinite loops waiting for data and processing it. Such loops are automatically interrupted when the last reference to the greenlet goes away. .. doctest:: >>> from greenlet import getcurrent, greenlet, GreenletExit >>> def run(): ... print("Beginning greenlet") ... try: ... while 1: ... print("Switching to parent") ... getcurrent().parent.switch() ... except GreenletExit: ... print("Got GreenletExit; quitting") >>> glet = greenlet(run) >>> _ = glet.switch() Beginning greenlet Switching to parent >>> glet = None Got GreenletExit; quitting The greenlet is expected to either die or be resurrected by having a new reference to it stored somewhere; just catching and ignoring the `GreenletExit` is likely to lead to an infinite loop. Cycles In Frames ================ Greenlets participate in garbage collection in a limited fashion; cycles involving data that is present in a greenlet's frames may not be detected. .. warning:: In particular, storing references to other greenlets cyclically may lead to leaks. .. note:: We use an object with ``__del__`` methods to demonstrate when they are collected. These examples require Python 3 to run; Python 2 won't collect cycles if the ``__del__`` method is defined. Manually Clearing Cycles Works ------------------------------ Here, we define a function that creates a cycle; when we run it and then collect garbage, the cycle is found and cleared, even while the function is running. .. important:: The examples that find and collect the cycle do so because we're manually removing the top-level references to the cycle by deleting the variables in the frame. .. doctest:: :pyversion: >= 3.5 >>> import gc >>> class Cycle(object): ... def __del__(self): ... print("(Running finalizer)") >>> def collect_it(): ... print("Collecting garbage") ... gc.collect() >>> def run(collect=collect_it): ... cycle1 = Cycle() ... cycle2 = Cycle() ... cycle1.cycle = cycle2 ... cycle2.cycle = cycle1 ... print("Deleting cycle vars") ... del cycle1 ... del cycle2 ... collect() ... print("Returning") >>> run() Deleting cycle vars Collecting garbage (Running finalizer) (Running finalizer) Returning If we use the same function in a greenlet, the cycle is also found while the greenlet is active: .. doctest:: :pyversion: >= 3.5 >>> glet = greenlet(run) >>> _ = glet.switch() Deleting cycle vars Collecting garbage (Running finalizer) (Running finalizer) Returning If we tweak the function to return control to a different greenlet (the main greenlet) and then run garbage collection, the cycle is also found: .. doctest:: :pyversion: >= 3.5 >>> glet = greenlet(run) >>> _ = glet.switch(getcurrent().switch) Deleting cycle vars >>> collect_it() Collecting garbage (Running finalizer) (Running finalizer) >>> del glet Cycles In Suspended Frames Are Not Collected ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Where this can fall apart is if a greenlet is left suspended and not switched to. Cycles within the suspended frames will not be detected; note how we don't run finalizers here when the ``outer`` greenlet runs a collection: .. doctest:: :pyversion: >= 3.5 >>> def inner(): ... cycle1 = Cycle() ... cycle2 = Cycle() ... cycle1.cycle = cycle2 ... cycle2.cycle = cycle1 ... getcurrent().parent.switch() >>> def outer(): ... glet = greenlet(inner) ... glet.switch() ... collect_it() >>> outer_glet = greenlet(outer) >>> outer_glet.switch() Collecting garbage It's only when the ``inner`` greenlet becomes garbage itself that its frames and cycles can be freed: .. doctest:: :pyversion: >= 3.5 >>> outer_glet.dead True >>> collect_it() Collecting garbage (Running finalizer) (Running finalizer) A Cycle Of Greenlets Is A Leak ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ What if we introduce a cycle among the greenlets themselves while also leaving a greenlet suspended? Here, the frames of the ``inner`` greenlet refer to the ``outer`` (as the ``inner`` greenlet itself does), and both the frames of the ``outer``, as well as the ``outer`` greenlet itself, refer to the ``inner``: .. doctest:: :pyversion: >= 3.5 >>> def inner(): ... cycle1 = Cycle() ... cycle2 = Cycle() ... cycle1.cycle = cycle2 ... cycle2.cycle = cycle1 ... parent = getcurrent().parent ... parent.switch() >>> def outer(): ... glet = greenlet(inner) ... getcurrent().child_greenlet = glet ... glet.switch() ... collect_it() This time, even letting the outer and inner greenlets die doesn't find the cycle hidden in the inner greenlet's frame: .. doctest:: :pyversion: >= 3.5 >>> outer_glet = greenlet(outer) >>> outer_glet.switch() Collecting garbage >>> outer_glet.dead True >>> collect_it() Collecting garbage Even explicitly deleting the outer greenlet doesn't find and clear the cycle; we have created a legitimate memory leak, not just of the greenlet objects, but also the objects in any suspended frames: .. doctest:: :pyversion: >= 3.5 >>> del outer_glet >>> collect_it() Collecting garbage greenlet-3.1.0/docs/gui_example.rst000066400000000000000000000220631467007427000173130ustar00rootroot00000000000000 .. _gui_example: ================================================================== Motivation: Treating an Asynchronous GUI Like a Synchronous Loop ================================================================== .. currentmodule:: greenlet In this document, we'll demonstrate how greenlet can be used to connect synchronous and asynchronous operations, without introducing any additional threads or race conditions. We'll use the example of transforming a "pull"-based console application into an asynchronous "push"-based GUI application *while still maintaining the simple pull-based structure*. Similar techniques work with XML expat parsers; in general, it can be framework that issues asynchronous callbacks. .. |--| unicode:: U+2013 .. en dash .. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace :trim: A Simple Terminal App ===================== Let's consider a system controlled by a terminal-like console, where the user types commands. Assume that the input comes character by character. In such a system, there will typically be a loop like the following one: .. doctest:: >>> def echo_user_input(user_input): ... print(' <<< ' + user_input.strip()) ... return user_input >>> def process_commands(): ... while True: ... line = '' ... while not line.endswith('\n'): ... line += read_next_char() ... echo_user_input(line) ... if line == 'quit\n': ... print("Are you sure?") ... if echo_user_input(read_next_char()) != 'y': ... continue # ignore the command ... print("(Exiting loop.)") ... break # stop the command loop ... process_command(line) Here, we have an infinite loop. The job of the loop is to read characters that the user types, accumulate that into a command line, and then execute the command. The heart of the loop is around ``read_next_char()``: .. doctest:: >>> def read_next_char(): ... """ ... Called from `process_commands`; ... blocks until a character has been typed and returns it. ... """ This function might be implemented by simply reading from :obj:`sys.stdin`, or by something more complex such as :meth:`curses.window.getch`, but in any case, it doesn't return until a key has been read from the user. Competing Event Loops ===================== Now assume that you want to plug this program into a GUI. Most GUI toolkits are event-based. Internally, they run their own infinite loop much like the one we wrote above, invoking a call-back for each character the user presses (``event_keydown(key)``). .. doctest:: >>> def event_keydown(key): ... "Called by the event system *asynchronously*." In this setting, it is difficult to implement the ``read_next_char()`` function needed by the code above. We have two incompatible functions. First, there's the function the GUI will call asynchronously to notify about an event; it's important to stress that we're not in control of when this function is called |---| in fact, our code isn't in the call stack at all, the GUI's loop is the only thing running. But that doesn't fit with our second function, ``read_next_char()`` which itself is supposed to be blocking and called from the middle of its own loop. How can we fit this asynchronous delivery mechanism together with our synchronous, blocking function that reads the next character the user types? Enter greenlets: Dual Infinite Loops ==================================== You might consider doing that with :class:`threads ` [#f1]_, but that can get complicated rather quickly. greenlets are an alternate solution that don't have the related locking and other problems threads introduce. By introducing a greenlet to run ``process_commands``, and having it communicate with the greenlet running the GUI event loop, we can effectively have a single thread be *in the middle of two infinite loops at once* and switch between them as desired. Pretty cool. It's even cooler when you consider that the GUI's loop is likely to be implemented in C, not Python, so we'll be switching between infinite loops both in native code and in the Python interpreter. First, let's create a greenlet to run the ``process_commands`` function (note that we're not starting it just yet, only defining it). .. doctest:: >>> from greenlet import greenlet >>> g_processor = greenlet(process_commands) Now, we need to arrange for the communication between the GUI's event loop and its callback ``event_keydown`` (running in the implicit main greenlet) and this new greenlet. The changes to ``event_keydown`` are pretty simple: just send the key the GUI gives us into the loop that ``process_commands`` is in using :meth:`greenlet.switch`. .. doctest:: >>> main_greenlet = greenlet.getcurrent() >>> def event_keydown(key): # running in main_greenlet ... # jump into g_processor, sending it the key ... g_processor.switch(key) The other side of the coin is to define ``read_next_char`` to accept this key event. We do this by letting the main greenlet run the GUI loop until the GUI loop jumps back to is from ``event_keydown``: .. doctest:: >>> def read_next_char(): # running in g_processor ... # jump to the main greenlet, where the GUI event ... # loop is running, and wait for the next key ... next_char = main_greenlet.switch('blocking in read_next_char') ... return next_char Having defined both functions, we can start the ``process_commands`` greenlet, which will make it to ``read_next_char()`` and immediately switch back to the main greenlet: .. doctest:: >>> g_processor.switch() 'blocking in read_next_char' Now we can hand control over to the main event loop of the GUI. Of course, in documentation we don't have a GUI, so we'll fake one that feeds keys to ``event_keydown``; for demonstration purposes we'll also fake a ``process_command`` function that just prints the line it got. .. doctest:: >>> def process_command(line): ... print('(Processing command: ' + line.strip() + ')') >>> def gui_mainloop(): ... # The user types "hello" ... for c in 'hello\n': ... event_keydown(c) ... # The user types "quit" ... for c in 'quit\n': ... event_keydown(c) ... # The user responds to the prompt with 'y' ... event_keydown('y') >>> gui_mainloop() <<< hello (Processing command: hello) <<< quit Are you sure? <<< y (Exiting loop.) >>> g_processor.dead True .. sidebar:: Switching Isn't Contagious Notice how a single call to ``gui_mainloop`` successfully switched back and forth between two greenlets without the caller or author of ``gui_mainloop`` needing to be aware of that. Contrast this with :mod:`asyncio`, where the keywords ``async def`` and ``await`` often spread throughout the codebase once introduced. In fact, greenlets can be used to put a halt to that spread and execute ``async def`` code in a synchronous fashion. .. seealso:: For the interactions between :mod:`contextvars` and greenlets. :doc:`contextvars` In this example, the execution flow is: when ``read_next_char()`` is called, it is part of the ``g_processor`` greenlet, so when it switches to its parent greenlet, it resumes execution in the top-level main loop (the GUI). When the GUI calls ``event_keydown()``, it switches to ``g_processor``, which means that the execution jumps back wherever it was suspended in that greenlet |---| in this case, to the ``switch()`` instruction in ``read_next_char()`` |---| and the ``key`` argument in ``event_keydown()`` is passed as the return value of the switch() in ``read_next_char()``. Note that ``read_next_char()`` will be suspended and resumed with its call stack preserved, so that it will itself return to different positions in ``process_commands()`` depending on where it was originally called from. This allows the logic of the program to be kept in a nice control-flow way; we don't have to completely rewrite ``process_commands()`` to turn it into a state machine. Further Reading =============== Continue reading with :doc:`greenlet`. Curious how execution resumed in the main greenlet after ``process_commands`` exited its loop (and never explicitly switched back to the main greenlet)? Read about :ref:`greenlet_parents`. .. rubric:: Footnotes .. [#f1] You might try to run the GUI event loop in one thread, and the ``process_commands`` function in another thread. You could then use a thread-safe :class:`queue.Queue` to exchange keypresses between the two: write to the queue in ``event_keydown``, read from it in ``read_next_char``. One problem with this, though, is that many GUI toolkits are single-threaded and only run in the main thread, so we'd also need a way to communicate any results of ``process_command`` back to the main thread in order to update the GUI. We're now significantly diverging from our simple console-based application. greenlet-3.1.0/docs/history.rst000066400000000000000000000001271467007427000165120ustar00rootroot00000000000000=================== History And About =================== .. include:: ../README.rst greenlet-3.1.0/docs/index.rst000066400000000000000000000124671467007427000161320ustar00rootroot00000000000000============================================== greenlet: Lightweight concurrent programming ============================================== .. TODO: Divide into a few different kinds of documentation (https://documentation.divio.com/explanation/): - Tutorial, - API reference - how-to. - Explanation. Each document should identify what role it fulfills. .. |--| unicode:: U+2013 .. en dash .. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace :trim: .. sidebar:: Contents If this page has piqued your interest in greenlets, continue reading by seeing :ref:`an example transforming an asynchronous GUI into a simple synchronous loop `. To get started building your own code with greenlets, read :doc:`greenlet`, and then :doc:`creating_executing_greenlets`. .. toctree:: :caption: Getting Started :maxdepth: 2 gui_example greenlet creating_executing_greenlets switching .. toctree:: :maxdepth: 1 :caption: Reference Material api c_api changes development history .. toctree:: :maxdepth: 1 :caption: Advanced Usage python_threads contextvars greenlet_gc tracing caveats .. rubric:: What are greenlets? greenlets are lightweight coroutines for in-process sequential concurrent programming. greenlets can be used on their own, but they are frequently used with frameworks such as `gevent`_ to provide higher-level abstractions and asynchronous I/O. greenlets are frequently defined by analogy to :mod:`threads ` or Python's built-in coroutines (generators and ``async def`` functions). The rest of this section will explore those analogies. For a more precise introduction, see :ref:`what_is_greenlet`. See :doc:`history` for how the greenlet library was created, and its relation to other similar concepts. .. rubric:: Are greenlets similar to threads? For many purposes, you can usually think of greenlets as cooperatively scheduled :mod:`threads `. The major differences are that since they're cooperatively scheduled, you are in control of when they execute, and since they are coroutines, many greenlets can exist in a single native thread. .. rubric:: How are greenlets different from threads? Threads (in theory) are preemptive and parallel [#f1]_, meaning that multiple threads can be processing work at the same time, and it's impossible to say in what order different threads will proceed or see the effects of other threads. This necessitates careful programming using :class:`locks `, :class:`queues `, or other approaches to avoid `race conditions`_, `deadlocks`_, or other bugs. In contrast, greenlets are cooperative and sequential. This means that when one greenlet is running, no other greenlet can be running; the programmer is fully in control of when execution switches between greenlets. This can eliminate race conditions and greatly simplify the programming task. Also, threads require resources from the operating system (the thread stack, and bookkeeping in the kernel). Because greenlets are implemented entirely without involving the operating system, they can require fewer resources; it is often practical to have many more greenlets than it is threads. .. _race conditions: https://en.wikipedia.org/wiki/Race_condition .. _deadlocks: https://docs.microsoft.com/en-us/troubleshoot/dotnet/visual-basic/race-conditions-deadlocks#when-deadlocks-occur .. rubric:: How else can greenlets be used? greenlets have many uses: - They can be treated like cooperative threads. You can implement any scheduling policy you desire. - Because greenlets work well with C libraries (greenlets can switch even with C functions in the call stack), they are well suited for integrating with GUIs or other event loops. `gevent`_ is an example of using greenlets to integrate with IO event loops (`libev`_ and `libuv`_) to provide a complete asynchronous environment using familiar programming patterns. - Similar to the above, greenlets can be used to transform apparently asynchronous tasks into a simple synchronous style. See :ref:`gui_example` for an example of writing an asynchronous event-driven GUI app in a simple synchronous style. - In general, greenlets can be used for advanced control flow. For example, you can :doc:`create generators ` |---| without the use of the ``yield`` keyword! .. _gevent: https://www.gevent.org .. _libev: http://software.schmorp.de/pkg/libev.html .. _libuv: http://libuv.org/ .. rubric:: Are greenlets similar to generators? What about asyncio? All three of greenlets, generators, and asyncio use a concept of coroutines. However, greenlets, unlike the other two, require no special keywords or support from the Python language. In addition, greenlets are capable of switching between stacks that feature C libraries, whereas the other two are not. .. rubric:: Footnotes .. [#f1] In CPython, the `global interpreter lock (GIL) `_ generally prevents two threads from executing Python code at the same time. Parallelism is thus limited to code sections that release the GIL, i.e., C code. Indices and tables ================== * :ref:`search` * :ref:`genindex` * :ref:`modindex` greenlet-3.1.0/docs/make.bat000066400000000000000000000117541467007427000156740ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\greenlet.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\greenlet.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end greenlet-3.1.0/docs/python_threads.rst000066400000000000000000000045361467007427000200540ustar00rootroot00000000000000============================== Greenlets and Python Threads ============================== Greenlets can be combined with Python threads; in this case, each thread contains an independent "main" greenlet with a tree of sub-greenlets. It is not possible to mix or switch between greenlets belonging to different threads. .. doctest:: >>> from greenlet import getcurrent >>> from greenlet import greenlet >>> from threading import Thread >>> from threading import Event >>> started = Event() >>> switched = Event() >>> class T(Thread): ... def run(self): ... self.main_glet = getcurrent() ... self.child_glet = greenlet(lambda: None) ... self.child_glet.switch() ... started.set() ... switched.wait() >>> t = T() >>> t.start() >>> _ = started.wait() >>> t.main_glet.switch() Traceback (most recent call last): ... greenlet.error: cannot switch to a different thread >>> switched.set() >>> t.join() Prior to greenlet 2.0, when a thread dies, the thread's main greenlet was not considered to be dead. This has been changed in greenlet 2.0; however, observing this property is still a race condition, and, on some platforms (those that cannot use the C runtime to detect when a thread exits), not guaranteed (because of the potential for uncollectible reference cycles to keep the Python thread state alive). In addition, this is considered an implementation detail and may not be true in all greenlet implementations. .. doctest:: >>> t.main_glet.dead True .. caution:: For these reasons, it's best to not pass references to a greenlet running in one thread to another thread. If you do, take caution to carefully manage the lifetime of the references. If greenlets that are suspended in one thread are referenced from another thread, row memory and Python objects can leak. In greenlet 2.0, the error message for attempting to switch into a dead thread makes that fact clear; again, this is an implementation detail and should not be relied upon. .. doctest:: >>> t.child_glet.switch() Traceback (most recent call last): ... greenlet.error: cannot switch to a different thread (which happens to have exited) >>> t.main_glet.switch() Traceback (most recent call last): ... greenlet.error: cannot switch to a garbage collected greenlet greenlet-3.1.0/docs/switching.rst000066400000000000000000000137201467007427000170130ustar00rootroot00000000000000.. _switching: ========================================================== Switching Between Greenlets: Passing Objects and Control ========================================================== .. This is an "explanation" document. .. currentmodule:: greenlet Switches between greenlets occur when: - The method `greenlet.switch` of a greenlet is called, in which case execution jumps to the greenlet whose ``switch()`` is called; or - When the method `greenlet.throw` is used to raise an exception in the target greenlet, in which case execution jumps to the greenlet whose ``throw`` was called; or - When a greenlet dies, in which case execution jumps to the parent greenlet. During a switch, an object or an exception is "sent" to the target greenlet; this can be used as a convenient way to pass information between greenlets. For example: .. doctest:: >>> from greenlet import greenlet >>> def test1(x, y): ... z = gr2.switch(x + y) ... print(z) >>> def test2(u): ... print(u) ... gr1.switch(42) >>> gr1 = greenlet(test1) >>> gr2 = greenlet(test2) >>> gr1.switch("hello", " world") hello world 42 This prints "hello world" and 42. Note that the arguments of ``test1()`` and ``test2()`` are not provided when the greenlet is created, but only the first time someone switches to it. Here are the precise rules for sending objects around: ``g.switch(*args, **kwargs)`` Switches execution to the greenlet ``g``, sending it the given arguments. As a special case, if ``g`` did not start yet, then it will start to run now; ``args`` and ``kwargs`` are passed to the greenlet's ``run()`` function as its arguments. Dying greenlet If a greenlet's ``run()`` finishes, its return value is the object sent to its parent. If ``run()`` terminates with an exception, the exception is propagated to its parent (unless it is a ``greenlet.GreenletExit`` exception, in which case the exception object is caught and *returned* to the parent). Apart from the cases described above, the target greenlet normally receives the object as the return value of the call to ``switch()`` in which it was previously suspended. Indeed, although a call to ``switch()`` does not return immediately, it will still return at some point in the future, when some other greenlet switches back. When this occurs, then execution resumes just after the ``switch()`` where it was suspended, and the ``switch()`` itself appears to return the object that was just sent. This means that ``x = g.switch(y)`` will send the object ``y`` to ``g``, and will later put the (unrelated) object that some (unrelated) greenlet passes back to us into ``x``. Multiple And Keyword Arguments ============================== You can pass multiple or keyword arguments to ``switch()``. If the greenlet hasn't begun running, those are passed as function arguments to ``run`` as usual in Python. If the greenlet *was* running, multiple arguments will be a :class:`tuple`, and keyword arguments will be a :class:`dict`; any number of positional arguments with keyword arguments will have the entire set in a tuple, with positional arguments in their own nested tuple, and keyword arguments as a `dict` in the the last element of the tuple: .. doctest:: >>> def test1(x, y, **kwargs): ... while 1: ... z = gr2.switch(x + y + ' ' + str(kwargs)) ... if not z: break ... print(z) >>> def test2(u): ... print(u) ... # A single argument -> itself ... gr1.switch(42) ... # Multiple positional args -> a tuple ... gr1.switch("how", "are", "you") ... # Only keyword arguments -> a dict ... gr1.switch(language='en') ... # one positional and keywords -> ((tuple,), dict) ... gr1.switch("howdy", language='en_US') ... # multiple positional and keywords -> ((tuple,), dict) ... gr1.switch("all", "y'all", language='en_US_OK') ... gr1.switch(None) # terminate >>> gr1 = greenlet(test1) >>> gr2 = greenlet(test2) >>> gr1.switch("hello", " world", language='en') hello world {'language': 'en'} 42 ('how', 'are', 'you') {'language': 'en'} (('howdy',), {'language': 'en_US'}) (('all', "y'all"), {'language': 'en_US_OK'}) .. _switch_to_dead: Switching To Dead Greenlets =========================== Note that any attempt to switch to a dead greenlet actually goes to the dead greenlet's parent, or its parent's parent, and so on. (The final parent is the "main" greenlet, which is never dead.) .. doctest:: >>> def inner(): ... print("Entering inner.") ... print("Returning from inner.") ... return 42 >>> def outer(): ... print("Entering outer and spawning inner.") ... inner_glet = greenlet(inner) ... print("Switching to inner.") ... result = inner_glet.switch() ... print("Got from inner value: %s" % (result,)) ... print("Switching to inner again.") ... result = inner_glet.switch() ... print("Got from inner value: %s" % (result,)) ... return inner_glet >>> outer_glet = greenlet(outer) Here, our main greenlet has created another greenlet (``outer``), which in turn creates a greenlet (``inner``). The outer greenlet switches to the inner greenlet, which immediately finishes and dies; the outer greenlet attempts to switch back to the inner greenlet, but since the inner greenlet is dead, it just switches...to itself (since it was the parent). Note how the second switch (to the dead greenlet) returns an empty tuple. .. doctest:: >>> inner_glet = outer_glet.switch() Entering outer and spawning inner. Switching to inner. Entering inner. Returning from inner. Got from inner value: 42 Switching to inner again. Got from inner value: () We can similarly ask the main greenlet to switch to the (dead) inner greenlet and its (dead) parent and wind up still in the main greenlet. >>> inner_glet.switch() () greenlet-3.1.0/docs/tracing.rst000066400000000000000000000056201467007427000164430ustar00rootroot00000000000000======================= Tracing And Profiling ======================= .. currentmodule:: greenlet Standard Python tracing and profiling doesn't work as expected when used with greenlet since stack and frame switching happens on the same Python thread. It is difficult to detect greenlet switching reliably with conventional methods, so to improve support for debugging, tracing and profiling greenlet based code there are new functions in the greenlet module, `gettrace` and `settrace`. Trace Callback Functions ======================== Trace callback functions are installed using `settrace` and must have the signature ``callback(event: str, args: Any)``. .. important:: For compatibility it is very important to unpack ``args`` tuple only when ``event`` is one of those defined here, and not when ``event`` is potentially something else. This way API can be extended to new events similar to :func:`sys.settrace()`. The parameter *event* is a string naming what happened. The following events are defined: ``switch`` In this case, ``args`` is a two-tuple ``(origin, target)``. Called to handle a switch from ``origin`` to ``target``. Note that callback is running in the context of the ``target`` greenlet and any exceptions will be passed as if ``target.throw()`` was used instead of a switch. ``throw`` In this case, ``args`` is a two-tuple ``(origin, target)``. Called to handle a throw from ``origin`` to ``target``. Note that callback is running in the context of ``target`` greenlet and any exceptions will replace the original, as if ``target.throw()`` was used with the replacing exception. For example: .. doctest:: >>> import greenlet >>> def callback(event, args): ... if event in ('switch', 'throw'): ... origin, target = args ... print("Transfer from %s to %s with %s" ... % (origin, target, event)) >>> class Origin(greenlet.greenlet): ... def run(self): ... print("In origin") ... target.switch() ... print("Returned to origin") ... target.throw() ... def __str__(self): ... return "" >>> class Target(greenlet.greenlet): ... def run(self): ... origin.switch() ... def __str__(self): ... return "" >>> old_trace = greenlet.settrace(callback) >>> origin = Origin() >>> target = Target() >>> _ = origin.switch() Transfer from to with switch In origin Transfer from to with switch Transfer from to with switch Returned to origin Transfer from to with throw Transfer from to with switch Of course, when we're done, it's important to restore the previous state: .. doctest:: >>> _ = greenlet.settrace(old_trace) greenlet-3.1.0/make-manylinux000077500000000000000000000044231467007427000162170ustar00rootroot00000000000000#!/bin/bash # Initially based on a snippet from the greenlet project. # This needs to be run from the root of the project. # To update: docker pull quay.io/pypa/manylinux2010_x86_64 set -e export PYTHONUNBUFFERED=1 export PYTHONDONTWRITEBYTECODE=1 # Use a fixed hash seed for reproducibility export PYTHONHASHSEED=8675309 export CI=1 export TRAVIS=true # Don't get warnings about Python 2 support being deprecated. We # know. The env var works for pip 20. export PIP_NO_PYTHON_VERSION_WARNING=1 export PIP_NO_WARN_SCRIPT_LOCATION=1 if [ -d /greenlet -a -d /opt/python ]; then # Running inside docker export GREENLET_MANYLINUX=1 # Build for speed (we're going to test this anyway) and without assertions. # Note: -Ofast includes -ffast-math which affects process-wide floating-point flags (e.g. can affect numpy). # It may also violate standards compliance in a few ways. Rather than opt-out with -fno-fast-math, # we use O3, which has neither of those problems. export CFLAGS="-O3 -DNDEBUG" # Build in an isolated directory mkdir /tmp/build cd /tmp/build git config --global --add safe.directory /greenlet/.git git clone /greenlet greenlet cd greenlet mkdir -p /greenlet/wheelhouse OPATH="$PATH" which auditwheel echo "Installed Python versions" ls -l /opt/python for variant in `ls -d /opt/python/cp{313,37,38,39,310,311,312}*`; do if [ "$variant" = "/opt/python/cp313-313t" ]; then echo "Skipping no-gil build. The GIL is required." continue fi export PATH="$variant/bin:$OPATH" echo "Building $variant $(python --version)" python -mpip install -U pip python -mpip install -U setuptools wheel python -mpip wheel --wheel-dir ./dist . python -mpip install -U .[test] python -m unittest discover -v greenlet.tests PATH="$OPATH" auditwheel repair dist/greenlet*.whl cp wheelhouse/greenlet*.whl /greenlet/wheelhouse rm -rf build rm -f dist/greenlet*.whl done exit 0 fi # Mount the current directory as /greenlet # Can't use -i on Travis with arm64, "input device not a tty" docker run --rm -v "$(pwd):/greenlet" ${DOCKER_IMAGE:-quay.io/pypa/manylinux2014_x86_64} /greenlet/$(basename $0) ls -l wheelhouse greenlet-3.1.0/pyproject.toml000066400000000000000000000001421467007427000162400ustar00rootroot00000000000000[build-system] build-backend = "setuptools.build_meta" requires = [ "setuptools >= 40.8.0" ] greenlet-3.1.0/setup.cfg000066400000000000000000000001261467007427000151470ustar00rootroot00000000000000[zest.releaser] python-file-with-version = src/greenlet/__init__.py create-wheel = no greenlet-3.1.0/setup.py000077500000000000000000000234531467007427000150530ustar00rootroot00000000000000#! /usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function import sys import os import glob import platform # distutils is deprecated and vendored into setuptools now. from setuptools import setup from setuptools import Extension from setuptools import find_packages # Extra compiler arguments passed to *all* extensions. global_compile_args = [] # Extra compiler arguments passed to C++ extensions cpp_compile_args = [] # Extra linker arguments passed to C++ extensions cpp_link_args = [] # Extra compiler arguments passed to the main extension main_compile_args = [] is_win = sys.platform.startswith("win") # workaround segfaults on openbsd and RHEL 3 / CentOS 3 . see # https://bitbucket.org/ambroff/greenlet/issue/11/segfault-on-openbsd-i386 # https://github.com/python-greenlet/greenlet/issues/4 # https://github.com/python-greenlet/greenlet/issues/94 # pylint:disable=too-many-boolean-expressions is_linux = sys.platform.startswith('linux') # could be linux or linux2 plat_platform = platform.platform() plat_machine = platform.machine() plat_compiler = platform.python_compiler() try: # (sysname, nodename, release, version, machine) unam_machine = os.uname()[-1] except AttributeError: unam_machine = '' if ( (sys.platform == "openbsd4" and unam_machine == "i386") or ("-with-redhat-3." in plat_platform and plat_machine == 'i686') or (sys.platform == "sunos5" and unam_machine == "sun4v") # SysV-based Solaris or ("SunOS" in plat_platform and plat_machine == "sun4v") # Old BSD-based SunOS or (is_linux and plat_machine == "ppc") # https://github.com/python-greenlet/greenlet/pull/300: When compiling for RISC-V the command # ``riscv64-linux-gnu-gcc -pthread -fno-strict-aliasing -Wdate-time \ # -D_FORTIFY_SOURCE=2 -g -ffile-prefix-map=/build/python2.7-7GU7VT/python2.7-2.7.18=. \ # -fstack-protector-strong -Wformat -Werror=format-security -fPIC \ # -I/usr/include/python2.7 # -c src/greenlet/greenlet.cpp -o build/temp.linux-riscv64-2.7/src/greenlet/greenlet.o`` # # fails with: # # src/greenlet/platform/switch_riscv_unix.h:30:1: error: s0 cannot be used in 'asm' here # # Adding the -Os flag fixes the problem. or (is_linux and plat_machine == "riscv64") ): global_compile_args.append("-Os") if sys.platform == 'darwin' or 'clang' in plat_compiler: # The clang compiler doesn't use --std=c++11 by default cpp_compile_args.append("--std=gnu++11") elif is_win and "MSC" in plat_compiler: # Older versions of MSVC (Python 2.7) don't handle C++ exceptions # correctly by default. While newer versions do handle exceptions # by default, they don't do it fully correctly ("By default....the # compiler generates code that only partially supports C++ # exceptions."). So we need an argument on all versions. #"/EH" == exception handling. # "s" == standard C++, # "c" == extern C functions don't throw # OR # "a" == standard C++, and Windows SEH; anything may throw, compiler optimizations # around try blocks are less aggressive. Because this catches SEH, # which Windows uses internally, the MS docs say this can be a security issue. # DO NOT USE. # /EHsc is suggested, and /EHa isn't supposed to be linked to other things not built # with it. Leaving off the "c" should just result in slower, safer code. # Other options: # "r" == Always generate standard confirming checks for noexcept blocks, terminating # if violated. IMPORTANT: We rely on this. # See https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-170 handler = "/EHsr" cpp_compile_args.append(handler) # To disable most optimizations: #cpp_compile_args.append('/Od') # To enable assertions: #cpp_compile_args.append('/UNDEBUG') # To enable more compile-time warnings (/Wall produces a mountain of output). #cpp_compile_args.append('/W4') # To link with the debug C runtime...except we can't because we need # the Python debug lib too, and they're not around by default # cpp_compile_args.append('/MDd') # Support fiber-safe thread-local storage: "the compiler mustn't # cache the address of the TLS array, or optimize it as a common # subexpression across a function call." This would probably solve # some of the issues we had with MSVC caching the thread local # variables on the stack, leading to having to split some # functions up. Revisit those. cpp_compile_args.append("/GT") def readfile(filename): with open(filename, 'r') as f: # pylint:disable=unspecified-encoding return f.read() GREENLET_SRC_DIR = 'src/greenlet/' GREENLET_HEADER_DIR = GREENLET_SRC_DIR GREENLET_HEADER = GREENLET_HEADER_DIR + 'greenlet.h' GREENLET_TEST_DIR = 'src/greenlet/tests/' # The location of the platform specific assembly files # for switching. GREENLET_PLATFORM_DIR = GREENLET_SRC_DIR + 'platform/' def _find_platform_headers(): return glob.glob(GREENLET_PLATFORM_DIR + "switch_*.h") def _find_impl_headers(): return glob.glob(GREENLET_SRC_DIR + "*.hpp") + glob.glob(GREENLET_SRC_DIR + "*.cpp") if hasattr(sys, "pypy_version_info"): ext_modules = [] headers = [] else: headers = [GREENLET_HEADER] if is_win and '64 bit' in sys.version: # this works when building with msvc, not with 64 bit gcc # switch__masm.obj can be created with setup_switch__masm.cmd obj_fn = 'switch_arm64_masm.obj' if plat_machine == 'ARM64' else 'switch_x64_masm.obj' extra_objects = [os.path.join(GREENLET_PLATFORM_DIR, obj_fn)] else: extra_objects = [] if is_win and os.environ.get('GREENLET_STATIC_RUNTIME') in ('1', 'yes'): main_compile_args.append('/MT') elif unam_machine in ('ppc64el', 'ppc64le'): main_compile_args.append('-fno-tree-dominator-opts') ext_modules = [ Extension( name='greenlet._greenlet', sources=[ GREENLET_SRC_DIR + 'greenlet.cpp', ], language='c++', extra_objects=extra_objects, extra_compile_args=global_compile_args + main_compile_args + cpp_compile_args, extra_link_args=cpp_link_args, depends=[ GREENLET_HEADER, GREENLET_SRC_DIR + 'slp_platformselect.h', ] + _find_platform_headers() + _find_impl_headers(), define_macros=[ ] + ([ ('WIN32', '1'), ] if is_win else [ ]) ), # Test extensions. # # We used to try hard to not include these in built # distributions, because we only distributed ``greenlet.so``. # That's really not important, now we have a clean layout with # the test directory nested inside a greenlet directory. See # https://github.com/python-greenlet/greenlet/issues/184 and # 189 Extension( name='greenlet.tests._test_extension', sources=[GREENLET_TEST_DIR + '_test_extension.c'], include_dirs=[GREENLET_HEADER_DIR], extra_compile_args=global_compile_args, ), Extension( name='greenlet.tests._test_extension_cpp', sources=[GREENLET_TEST_DIR + '_test_extension_cpp.cpp'], language="c++", include_dirs=[GREENLET_HEADER_DIR], extra_compile_args=global_compile_args + cpp_compile_args, extra_link_args=cpp_link_args, ), ] def get_greenlet_version(): with open('src/greenlet/__init__.py') as f: # pylint:disable=unspecified-encoding looking_for = '__version__ = \'' for line in f: if line.startswith(looking_for): version = line[len(looking_for):-2] return version raise ValueError("Unable to find version") setup( name="greenlet", version=get_greenlet_version(), description='Lightweight in-process concurrent programming', long_description=readfile("README.rst"), long_description_content_type="text/x-rst", url="https://greenlet.readthedocs.io/", keywords="greenlet coroutine concurrency threads cooperative", author="Alexey Borzenkov", author_email="snaury@gmail.com", maintainer='Jason Madden', maintainer_email='jason@seecoresoftware.com', project_urls={ 'Bug Tracker': 'https://github.com/python-greenlet/greenlet/issues', 'Source Code': 'https://github.com/python-greenlet/greenlet/', 'Documentation': 'https://greenlet.readthedocs.io/', }, license="MIT License", platforms=['any'], package_dir={'': 'src'}, packages=find_packages('src'), include_package_data=True, headers=headers, ext_modules=ext_modules, classifiers=[ "Development Status :: 5 - Production/Stable", 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Programming Language :: C', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules' ], extras_require={ 'docs': [ 'Sphinx', 'furo', ], 'test': [ 'objgraph', 'psutil', ], }, python_requires=">=3.7", zip_safe=False, ) greenlet-3.1.0/src/000077500000000000000000000000001467007427000141165ustar00rootroot00000000000000greenlet-3.1.0/src/greenlet/000077500000000000000000000000001467007427000157235ustar00rootroot00000000000000greenlet-3.1.0/src/greenlet/TBrokenGreenlet.cpp000066400000000000000000000020051467007427000214560ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ /** * Implementation of greenlet::UserGreenlet. * * Format with: * clang-format -i --style=file src/greenlet/greenlet.c * * * Fix missing braces with: * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" */ #include "greenlet_greenlet.hpp" namespace greenlet { void* BrokenGreenlet::operator new(size_t UNUSED(count)) { return allocator.allocate(1); } void BrokenGreenlet::operator delete(void* ptr) { return allocator.deallocate(static_cast(ptr), 1); } greenlet::PythonAllocator greenlet::BrokenGreenlet::allocator; bool BrokenGreenlet::force_slp_switch_error() const noexcept { return this->_force_slp_switch_error; } UserGreenlet::switchstack_result_t BrokenGreenlet::g_switchstack(void) { if (this->_force_switch_error) { return switchstack_result_t(-1); } return UserGreenlet::g_switchstack(); } }; //namespace greenlet greenlet-3.1.0/src/greenlet/TExceptionState.cpp000066400000000000000000000025271467007427000215200ustar00rootroot00000000000000#ifndef GREENLET_EXCEPTION_STATE_CPP #define GREENLET_EXCEPTION_STATE_CPP #include #include "greenlet_greenlet.hpp" namespace greenlet { ExceptionState::ExceptionState() { this->clear(); } void ExceptionState::operator<<(const PyThreadState *const tstate) noexcept { this->exc_info = tstate->exc_info; this->exc_state = tstate->exc_state; } void ExceptionState::operator>>(PyThreadState *const tstate) noexcept { tstate->exc_state = this->exc_state; tstate->exc_info = this->exc_info ? this->exc_info : &tstate->exc_state; this->clear(); } void ExceptionState::clear() noexcept { this->exc_info = nullptr; this->exc_state.exc_value = nullptr; #if !GREENLET_PY311 this->exc_state.exc_type = nullptr; this->exc_state.exc_traceback = nullptr; #endif this->exc_state.previous_item = nullptr; } int ExceptionState::tp_traverse(visitproc visit, void* arg) noexcept { Py_VISIT(this->exc_state.exc_value); #if !GREENLET_PY311 Py_VISIT(this->exc_state.exc_type); Py_VISIT(this->exc_state.exc_traceback); #endif return 0; } void ExceptionState::tp_clear() noexcept { Py_CLEAR(this->exc_state.exc_value); #if !GREENLET_PY311 Py_CLEAR(this->exc_state.exc_type); Py_CLEAR(this->exc_state.exc_traceback); #endif } }; // namespace greenlet #endif // GREENLET_EXCEPTION_STATE_CPP greenlet-3.1.0/src/greenlet/TGreenlet.cpp000066400000000000000000000620451467007427000203270ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ /** * Implementation of greenlet::Greenlet. * * Format with: * clang-format -i --style=file src/greenlet/greenlet.c * * * Fix missing braces with: * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" */ #include "greenlet_internal.hpp" #include "greenlet_greenlet.hpp" #include "greenlet_thread_state.hpp" #include "TGreenletGlobals.cpp" #include "TThreadStateDestroy.cpp" namespace greenlet { Greenlet::Greenlet(PyGreenlet* p) { p ->pimpl = this; } Greenlet::~Greenlet() { // XXX: Can't do this. tp_clear is a virtual function, and by the // time we're here, we've sliced off our child classes. //this->tp_clear(); } Greenlet::Greenlet(PyGreenlet* p, const StackState& initial_stack) : stack_state(initial_stack) { // can't use a delegating constructor because of // MSVC for Python 2.7 p->pimpl = this; } bool Greenlet::force_slp_switch_error() const noexcept { return false; } void Greenlet::release_args() { this->switch_args.CLEAR(); } /** * CAUTION: This will allocate memory and may trigger garbage * collection and arbitrary Python code. */ OwnedObject Greenlet::throw_GreenletExit_during_dealloc(const ThreadState& UNUSED(current_thread_state)) { // If we're killed because we lost all references in the // middle of a switch, that's ok. Don't reset the args/kwargs, // we still want to pass them to the parent. PyErr_SetString(mod_globs->PyExc_GreenletExit, "Killing the greenlet because all references have vanished."); // To get here it had to have run before return this->g_switch(); } inline void Greenlet::slp_restore_state() noexcept { #ifdef SLP_BEFORE_RESTORE_STATE SLP_BEFORE_RESTORE_STATE(); #endif this->stack_state.copy_heap_to_stack( this->thread_state()->borrow_current()->stack_state); } inline int Greenlet::slp_save_state(char *const stackref) noexcept { // XXX: This used to happen in the middle, before saving, but // after finding the next owner. Does that matter? This is // only defined for Sparc/GCC where it flushes register // windows to the stack (I think) #ifdef SLP_BEFORE_SAVE_STATE SLP_BEFORE_SAVE_STATE(); #endif return this->stack_state.copy_stack_to_heap(stackref, this->thread_state()->borrow_current()->stack_state); } /** * CAUTION: This will allocate memory and may trigger garbage * collection and arbitrary Python code. */ OwnedObject Greenlet::on_switchstack_or_initialstub_failure( Greenlet* target, const Greenlet::switchstack_result_t& err, const bool target_was_me, const bool was_initial_stub) { // If we get here, either g_initialstub() // failed, or g_switchstack() failed. Either one of those // cases SHOULD leave us in the original greenlet with a valid stack. if (!PyErr_Occurred()) { PyErr_SetString( PyExc_SystemError, was_initial_stub ? "Failed to switch stacks into a greenlet for the first time." : "Failed to switch stacks into a running greenlet."); } this->release_args(); if (target && !target_was_me) { target->murder_in_place(); } assert(!err.the_new_current_greenlet); assert(!err.origin_greenlet); return OwnedObject(); } OwnedGreenlet Greenlet::g_switchstack_success() noexcept { PyThreadState* tstate = PyThreadState_GET(); // restore the saved state this->python_state >> tstate; this->exception_state >> tstate; // The thread state hasn't been changed yet. ThreadState* thread_state = this->thread_state(); OwnedGreenlet result(thread_state->get_current()); thread_state->set_current(this->self()); //assert(thread_state->borrow_current().borrow() == this->_self); return result; } Greenlet::switchstack_result_t Greenlet::g_switchstack(void) { // if any of these assertions fail, it's likely because we // switched away and tried to switch back to us. Early stages of // switching are not reentrant because we re-use ``this->args()``. // Switching away would happen if we trigger a garbage collection // (by just using some Python APIs that happen to allocate Python // objects) and some garbage had weakref callbacks or __del__ that // switches (people don't write code like that by hand, but with // gevent it's possible without realizing it) assert(this->args() || PyErr_Occurred()); { /* save state */ if (this->thread_state()->is_current(this->self())) { // Hmm, nothing to do. // TODO: Does this bypass trace events that are // important? return switchstack_result_t(0, this, this->thread_state()->borrow_current()); } BorrowedGreenlet current = this->thread_state()->borrow_current(); PyThreadState* tstate = PyThreadState_GET(); current->python_state << tstate; 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 // return twice, once with 1 in the new greenlet, once with 0 // in the origin. int err; if (this->force_slp_switch_error()) { err = -1; } else { err = slp_switch(); } if (err < 0) { /* error */ // Tested by // test_greenlet.TestBrokenGreenlets.test_failed_to_slp_switch_into_running // // It's not clear if it's worth trying to clean up and // continue here. Failing to switch stacks is a big deal which // may not be recoverable (who knows what state the stack is in). // Also, we've stolen references in preparation for calling // ``g_switchstack_success()`` and we don't have a clean // mechanism for backing that all out. Py_FatalError("greenlet: Failed low-level slp_switch(). The stack is probably corrupt."); } // No stack-based variables are valid anymore. // But the global is volatile so we can reload it without the // compiler caching it from earlier. Greenlet* greenlet_that_switched_in = switching_thread_state; // aka this switching_thread_state = nullptr; // except that no stack variables are valid, we would: // assert(this == greenlet_that_switched_in); // switchstack success is where we restore the exception state, // etc. It returns the origin greenlet because its convenient. OwnedGreenlet origin = greenlet_that_switched_in->g_switchstack_success(); assert(greenlet_that_switched_in->args() || PyErr_Occurred()); return switchstack_result_t(err, greenlet_that_switched_in, origin); } inline void Greenlet::check_switch_allowed() const { // TODO: Make this take a parameter of the current greenlet, // or current main greenlet, to make the check for // cross-thread switching cheaper. Surely somewhere up the // call stack we've already accessed the thread local variable. // We expect to always have a main greenlet now; accessing the thread state // created it. However, if we get here and cleanup has already // begun because we're a greenlet that was running in a // (now dead) thread, these invariants will not hold true. In // fact, accessing `this->thread_state` may not even be possible. // If the thread this greenlet was running in is dead, // we'll still have a reference to a main greenlet, but the // thread state pointer we have is bogus. // TODO: Give the objects an API to determine if they belong // to a dead thread. const BorrowedMainGreenlet main_greenlet = this->find_main_greenlet_in_lineage(); if (!main_greenlet) { throw PyErrOccurred(mod_globs->PyExc_GreenletError, "cannot switch to a garbage collected greenlet"); } if (!main_greenlet->thread_state()) { throw PyErrOccurred(mod_globs->PyExc_GreenletError, "cannot switch to a different thread (which happens to have exited)"); } // The main greenlet we found was from the .parent lineage. // That may or may not have any relationship to the main // greenlet of the running thread. We can't actually access // our this->thread_state members to try to check that, // because it could be in the process of getting destroyed, // but setting the main_greenlet->thread_state member to NULL // may not be visible yet. So we need to check against the // current thread state (once the cheaper checks are out of // the way) const BorrowedMainGreenlet current_main_greenlet = GET_THREAD_STATE().state().borrow_main_greenlet(); if ( // lineage main greenlet is not this thread's greenlet current_main_greenlet != main_greenlet || ( // atteched to some thread this->main_greenlet() // XXX: Same condition as above. Was this supposed to be // this->main_greenlet()? && current_main_greenlet != main_greenlet) // switching into a known dead thread (XXX: which, if we get here, // is bad, because we just accessed the thread state, which is // gone!) || (!current_main_greenlet->thread_state())) { // CAUTION: This may trigger memory allocations, gc, and // arbitrary Python code. throw PyErrOccurred(mod_globs->PyExc_GreenletError, "cannot switch to a different thread"); } } const OwnedObject Greenlet::context() const { using greenlet::PythonStateContext; OwnedObject result; if (this->is_currently_running_in_some_thread()) { /* Currently running greenlet: context is stored in the thread state, not the greenlet object. */ if (GET_THREAD_STATE().state().is_current(this->self())) { result = PythonStateContext::context(PyThreadState_GET()); } else { throw ValueError( "cannot get context of a " "greenlet that is running in a different thread"); } } else { /* Greenlet is not running: just return context. */ result = this->python_state.context(); } if (!result) { result = OwnedObject::None(); } return result; } void Greenlet::context(BorrowedObject given) { using greenlet::PythonStateContext; if (!given) { throw AttributeError("can't delete context attribute"); } if (given.is_None()) { /* "Empty context" is stored as NULL, not None. */ given = nullptr; } //checks type, incrs refcnt greenlet::refs::OwnedContext context(given); PyThreadState* tstate = PyThreadState_GET(); if (this->is_currently_running_in_some_thread()) { if (!GET_THREAD_STATE().state().is_current(this->self())) { throw ValueError("cannot set context of a greenlet" " that is running in a different thread"); } /* Currently running greenlet: context is stored in the thread state, not the greenlet object. */ OwnedObject octx = OwnedObject::consuming(PythonStateContext::context(tstate)); PythonStateContext::context(tstate, context.relinquish_ownership()); } else { /* Greenlet is not running: just set context. Note that the greenlet may be dead.*/ this->python_state.context() = context; } } /** * CAUTION: May invoke arbitrary Python code. * * Figure out what the result of ``greenlet.switch(arg, kwargs)`` * should be and transfers ownership of it to the left-hand-side. * * If switch() was just passed an arg tuple, then we'll just return that. * If only keyword arguments were passed, then we'll pass the keyword * argument dict. Otherwise, we'll create a tuple of (args, kwargs) and * return both. * * CAUTION: This may allocate a new tuple object, which may * cause the Python garbage collector to run, which in turn may * run arbitrary Python code that switches. */ OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept { // Because this may invoke arbitrary Python code, which could // result in switching back to us, we need to get the // arguments locally on the stack. assert(rhs); OwnedObject args = rhs.args(); OwnedObject kwargs = rhs.kwargs(); rhs.CLEAR(); // We shouldn't be called twice for the same switch. assert(args || kwargs); assert(!rhs); if (!kwargs) { lhs = args; } else if (!PyDict_Size(kwargs.borrow())) { lhs = args; } else if (!PySequence_Length(args.borrow())) { lhs = kwargs; } else { // PyTuple_Pack allocates memory, may GC, may run arbitrary // Python code. lhs = OwnedObject::consuming(PyTuple_Pack(2, args.borrow(), kwargs.borrow())); } return lhs; } static OwnedObject g_handle_exit(const OwnedObject& greenlet_result) { if (!greenlet_result && mod_globs->PyExc_GreenletExit.PyExceptionMatches()) { /* catch and ignore GreenletExit */ PyErrFetchParam val; PyErr_Fetch(PyErrFetchParam(), val, PyErrFetchParam()); if (!val) { return OwnedObject::None(); } return OwnedObject(val); } if (greenlet_result) { // package the result into a 1-tuple // PyTuple_Pack increments the reference of its arguments, // so we always need to decref the greenlet result; // the owner will do that. return OwnedObject::consuming(PyTuple_Pack(1, greenlet_result.borrow())); } return OwnedObject(); } /** * May run arbitrary Python code. */ OwnedObject Greenlet::g_switch_finish(const switchstack_result_t& err) { assert(err.the_new_current_greenlet == this); ThreadState& state = *this->thread_state(); // Because calling the trace function could do arbitrary things, // including switching away from this greenlet and then maybe // switching back, we need to capture the arguments now so that // they don't change. OwnedObject result; if (this->args()) { result <<= this->args(); } else { assert(PyErr_Occurred()); } assert(!this->args()); try { // Our only caller handles the bad error case assert(err.status >= 0); assert(state.borrow_current() == this->self()); if (OwnedObject tracefunc = state.get_tracefunc()) { assert(result || PyErr_Occurred()); g_calltrace(tracefunc, result ? mod_globs->event_switch : mod_globs->event_throw, err.origin_greenlet, this->self()); } // The above could have invoked arbitrary Python code, but // it couldn't switch back to this object and *also* // throw an exception, so the args won't have changed. if (PyErr_Occurred()) { // We get here if we fell of the end of the run() function // raising an exception. The switch itself was // successful, but the function raised. // valgrind reports that memory allocated here can still // be reached after a test run. throw PyErrOccurred::from_current(); } return result; } catch (const PyErrOccurred&) { /* Turn switch errors into switch throws */ /* Turn trace errors into switch throws */ this->release_args(); throw; } } void Greenlet::g_calltrace(const OwnedObject& tracefunc, const greenlet::refs::ImmortalEventName& event, const BorrowedGreenlet& origin, const BorrowedGreenlet& target) { PyErrPieces saved_exc; try { TracingGuard tracing_guard; // TODO: We have saved the active exception (if any) that's // about to be raised. In the 'throw' case, we could provide // the exception to the tracefunction, which seems very helpful. tracing_guard.CallTraceFunction(tracefunc, event, origin, target); } catch (const PyErrOccurred&) { // In case of exceptions trace function is removed, // and any existing exception is replaced with the tracing // exception. GET_THREAD_STATE().state().set_tracefunc(Py_None); throw; } saved_exc.PyErrRestore(); assert( (event == mod_globs->event_throw && PyErr_Occurred()) || (event == mod_globs->event_switch && !PyErr_Occurred()) ); } void Greenlet::murder_in_place() { if (this->active()) { assert(!this->is_currently_running_in_some_thread()); this->deactivate_and_free(); } } inline void Greenlet::deactivate_and_free() { if (!this->active()) { return; } // Throw away any saved stack. this->stack_state = StackState(); assert(!this->stack_state.active()); // Throw away any Python references. // We're holding a borrowed reference to the last // frame we executed. Since we borrowed it, the // normal traversal, clear, and dealloc functions // ignore it, meaning it leaks. (The thread state // object can't find it to clear it when that's // deallocated either, because by definition if we // got an object on this list, it wasn't // running and the thread state doesn't have // this frame.) // So here, we *do* clear it. this->python_state.tp_clear(true); } bool Greenlet::belongs_to_thread(const ThreadState* thread_state) const { if (!this->thread_state() // not running anywhere, or thread // exited || !thread_state) { // same, or there is no thread state. return false; } return true; } void Greenlet::deallocing_greenlet_in_thread(const ThreadState* current_thread_state) { /* Cannot raise an exception to kill the greenlet if it is not running in the same thread! */ if (this->belongs_to_thread(current_thread_state)) { assert(current_thread_state); // To get here it had to have run before /* Send the greenlet a GreenletExit exception. */ // We don't care about the return value, only whether an // exception happened. this->throw_GreenletExit_during_dealloc(*current_thread_state); return; } // Not the same thread! Temporarily save the greenlet // into its thread's deleteme list, *if* it exists. // If that thread has already exited, and processed its pending // cleanup, we'll never be able to clean everything up: we won't // be able to raise an exception. // That's mostly OK! Since we can't add it to a list, our refcount // won't increase, and we'll go ahead with the DECREFs later. ThreadState *const thread_state = this->thread_state(); if (thread_state) { thread_state->delete_when_thread_running(this->self()); } else { // The thread is dead, we can't raise an exception. // We need to make it look non-active, though, so that dealloc // finishes killing it. this->deactivate_and_free(); } return; } int Greenlet::tp_traverse(visitproc visit, void* arg) { int result; if ((result = this->exception_state.tp_traverse(visit, arg)) != 0) { return result; } //XXX: This is ugly. But so is handling everything having to do //with the top frame. bool visit_top_frame = this->was_running_in_dead_thread(); // When true, the thread is dead. Our implicit weak reference to the // frame is now all that's left; we consider ourselves to // strongly own it now. if ((result = this->python_state.tp_traverse(visit, arg, visit_top_frame)) != 0) { return result; } return 0; } int Greenlet::tp_clear() { bool own_top_frame = this->was_running_in_dead_thread(); this->exception_state.tp_clear(); this->python_state.tp_clear(own_top_frame); return 0; } bool Greenlet::is_currently_running_in_some_thread() const { 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 greenlet-3.1.0/src/greenlet/TGreenletGlobals.cpp000066400000000000000000000063041467007427000216270ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ /** * Implementation of GreenletGlobals. * * Format with: * clang-format -i --style=file src/greenlet/greenlet.c * * * Fix missing braces with: * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" */ #ifndef T_GREENLET_GLOBALS #define T_GREENLET_GLOBALS #include "greenlet_refs.hpp" #include "greenlet_exceptions.hpp" #include "greenlet_thread_support.hpp" #include "greenlet_thread_state.hpp" namespace greenlet { // This encapsulates what were previously module global "constants" // established at init time. // This is a step towards Python3 style module state that allows // reloading. // // In an earlier iteration of this code, we used placement new to be // able to allocate this object statically still, so that references // to its members don't incur an extra pointer indirection. // But under some scenarios, that could result in crashes at // shutdown because apparently the destructor was getting run twice? class GreenletGlobals { public: const greenlet::refs::ImmortalEventName event_switch; const greenlet::refs::ImmortalEventName event_throw; const greenlet::refs::ImmortalException PyExc_GreenletError; const greenlet::refs::ImmortalException PyExc_GreenletExit; const greenlet::refs::ImmortalObject empty_tuple; const greenlet::refs::ImmortalObject empty_dict; const greenlet::refs::ImmortalString str_run; Mutex* const thread_states_to_destroy_lock; greenlet::cleanup_queue_t thread_states_to_destroy; GreenletGlobals() : event_switch("switch"), event_throw("throw"), PyExc_GreenletError("greenlet.error"), PyExc_GreenletExit("greenlet.GreenletExit", PyExc_BaseException), empty_tuple(Require(PyTuple_New(0))), empty_dict(Require(PyDict_New())), str_run("run"), thread_states_to_destroy_lock(new Mutex()) {} ~GreenletGlobals() { // This object is (currently) effectively immortal, and not // just because of those placement new tricks; if we try to // deallocate the static object we allocated, and overwrote, // we would be doing so at C++ teardown time, which is after // the final Python GIL is released, and we can't use the API // then. // (The members will still be destructed, but they also don't // do any deallocation.) } void queue_to_destroy(ThreadState* ts) const { // we're currently accessed through a static const object, // implicitly marking our members as const, so code can't just // call push_back (or pop_back) without casting away the // const. // // Do that for callers. greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy); q.push_back(ts); } ThreadState* take_next_to_destroy() const { greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy); ThreadState* result = q.back(); q.pop_back(); return result; } }; }; // namespace greenlet static const greenlet::GreenletGlobals* mod_globs; #endif // T_GREENLET_GLOBALS greenlet-3.1.0/src/greenlet/TMainGreenlet.cpp000066400000000000000000000064451467007427000211360ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ /** * Implementation of greenlet::MainGreenlet. * * Format with: * clang-format -i --style=file src/greenlet/greenlet.c * * * Fix missing braces with: * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" */ #include "greenlet_greenlet.hpp" #include "greenlet_thread_state.hpp" // Protected by the GIL. Incremented when we create a main greenlet, // in a new thread, decremented when it is destroyed. static Py_ssize_t G_TOTAL_MAIN_GREENLETS; namespace greenlet { greenlet::PythonAllocator MainGreenlet::allocator; void* MainGreenlet::operator new(size_t UNUSED(count)) { return allocator.allocate(1); } void MainGreenlet::operator delete(void* ptr) { return allocator.deallocate(static_cast(ptr), 1); } MainGreenlet::MainGreenlet(PyGreenlet* p, ThreadState* state) : Greenlet(p, StackState::make_main()), _self(p), _thread_state(state) { G_TOTAL_MAIN_GREENLETS++; } MainGreenlet::~MainGreenlet() { G_TOTAL_MAIN_GREENLETS--; this->tp_clear(); } ThreadState* MainGreenlet::thread_state() const noexcept { return this->_thread_state; } void MainGreenlet::thread_state(ThreadState* t) noexcept { assert(!t); this->_thread_state = t; } BorrowedGreenlet MainGreenlet::self() const noexcept { return BorrowedGreenlet(this->_self.borrow()); } const BorrowedMainGreenlet MainGreenlet::main_greenlet() const { return this->_self; } BorrowedMainGreenlet MainGreenlet::find_main_greenlet_in_lineage() const { return BorrowedMainGreenlet(this->_self); } bool MainGreenlet::was_running_in_dead_thread() const noexcept { return !this->_thread_state; } OwnedObject MainGreenlet::g_switch() { try { this->check_switch_allowed(); } catch (const PyErrOccurred&) { this->release_args(); throw; } switchstack_result_t err = this->g_switchstack(); if (err.status < 0) { // XXX: This code path is untested, but it is shared // with the UserGreenlet path that is tested. return this->on_switchstack_or_initialstub_failure( this, err, true, // target was me false // was initial stub ); } return err.the_new_current_greenlet->g_switch_finish(err); } int MainGreenlet::tp_traverse(visitproc visit, void* arg) { if (this->_thread_state) { // we've already traversed main, (self), don't do it again. int result = this->_thread_state->tp_traverse(visit, arg, false); if (result) { return result; } } return Greenlet::tp_traverse(visit, arg); } const OwnedObject& MainGreenlet::run() const { throw AttributeError("Main greenlets do not have a run attribute."); } void MainGreenlet::run(const BorrowedObject UNUSED(nrun)) { throw AttributeError("Main greenlets do not have a run attribute."); } void MainGreenlet::parent(const BorrowedObject raw_new_parent) { if (!raw_new_parent) { throw AttributeError("can't delete attribute"); } throw AttributeError("cannot set the parent of a main greenlet"); } const OwnedGreenlet MainGreenlet::parent() const { return OwnedGreenlet(); // null becomes None } }; // namespace greenlet greenlet-3.1.0/src/greenlet/TPythonState.cpp000066400000000000000000000360071467007427000210430ustar00rootroot00000000000000#ifndef GREENLET_PYTHON_STATE_CPP #define GREENLET_PYTHON_STATE_CPP #include #include "greenlet_greenlet.hpp" namespace greenlet { PythonState::PythonState() : _top_frame() #if GREENLET_USE_CFRAME ,cframe(nullptr) ,use_tracing(0) #endif #if GREENLET_PY312 ,py_recursion_depth(0) ,c_recursion_depth(0) #else ,recursion_depth(0) #endif #if GREENLET_PY313 ,delete_later(nullptr) #else ,trash_delete_nesting(0) #endif #if GREENLET_PY311 ,current_frame(nullptr) ,datastack_chunk(nullptr) ,datastack_top(nullptr) ,datastack_limit(nullptr) #endif { #if GREENLET_USE_CFRAME /* The PyThreadState->cframe pointer usually points to memory on the stack, alloceted in a call into PyEval_EvalFrameDefault. Initially, before any evaluation begins, it points to the initial PyThreadState object's ``root_cframe`` object, which is statically allocated for the lifetime of the thread. A greenlet can last for longer than a call to PyEval_EvalFrameDefault, so we can't set its ``cframe`` pointer to be the current ``PyThreadState->cframe``; nor could we use one from the greenlet parent for the same reason. Yet a further no: we can't allocate one scoped to the greenlet and then destroy it when the greenlet is deallocated, because inside the interpreter the _PyCFrame objects form a linked list, and that too can result in accessing memory beyond its dynamic lifetime (if the greenlet doesn't actually finish before it dies, its entry could still be in the list). Using the ``root_cframe`` is problematic, though, because its members are never modified by the interpreter and are set to 0, meaning that its ``use_tracing`` flag is never updated. We don't want to modify that value in the ``root_cframe`` ourself: it *shouldn't* matter much because we should probably never get back to the point where that's the only cframe on the stack; even if it did matter, the major consequence of an incorrect value for ``use_tracing`` is that if its true the interpreter does some extra work --- however, it's just good code hygiene. Our solution: before a greenlet runs, after its initial creation, it uses the ``root_cframe`` just to have something to put there. However, once the greenlet is actually switched to for the first time, ``g_initialstub`` (which doesn't actually "return" while the greenlet is running) stores a new _PyCFrame on its local stack, and copies the appropriate values from the currently running _PyCFrame; this is then made the _PyCFrame for the newly-minted greenlet. ``g_initialstub`` then proceeds to call ``glet.run()``, which results in ``PyEval_...`` adding the _PyCFrame to the list. Switches continue as normal. Finally, when the greenlet finishes, the call to ``glet.run()`` returns and the _PyCFrame is taken out of the linked list and the stack value is now unused and free to expire. XXX: I think we can do better. If we're deallocing in the same thread, can't we traverse the list and unlink our frame? Can we just keep a reference to the thread state in case we dealloc in another thread? (Is that even possible if we're still running and haven't returned from g_initialstub?) */ this->cframe = &PyThreadState_GET()->root_cframe; #endif } inline void PythonState::may_switch_away() noexcept { #if GREENLET_PY311 // PyThreadState_GetFrame is probably going to have to allocate a // new frame object. That may trigger garbage collection. Because // we call this during the early phases of a switch (it doesn't // matter to which greenlet, as this has a global effect), if a GC // triggers a switch away, two things can happen, both bad: // - We might not get switched back to, halting forward progress. // this is pathological, but possible. // - We might get switched back to with a different set of // arguments or a throw instead of a switch. That would corrupt // our state (specifically, PyErr_Occurred() and this->args() // would no longer agree). // // Thus, when we call this API, we need to have GC disabled. // This method serves as a bottleneck we call when maybe beginning // a switch. In this way, it is always safe -- no risk of GC -- to // use ``_GetFrame()`` whenever we need to, just as it was in // <=3.10 (because subsequent calls will be cached and not // allocate memory). GCDisabledGuard no_gc; Py_XDECREF(PyThreadState_GetFrame(PyThreadState_GET())); #endif } void PythonState::operator<<(const PyThreadState *const tstate) noexcept { this->_context.steal(tstate->context); #if GREENLET_USE_CFRAME /* IMPORTANT: ``cframe`` is a pointer into the STACK. Thus, because the call to ``slp_switch()`` changes the contents of the stack, you cannot read from ``ts_current->cframe`` after that call and necessarily get the same values you get from reading it here. Anything you need to restore from now to then must be saved in a global/threadlocal variable (because we can't use stack variables here either). For things that need to persist across the switch, use `will_switch_from`. */ this->cframe = tstate->cframe; #if !GREENLET_PY312 this->use_tracing = tstate->cframe->use_tracing; #endif #endif // GREENLET_USE_CFRAME #if GREENLET_PY311 #if GREENLET_PY312 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining; #else // not 312 this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; #endif // GREENLET_PY312 #if GREENLET_PY313 this->current_frame = tstate->current_frame; #elif GREENLET_USE_CFRAME this->current_frame = tstate->cframe->current_frame; #endif this->datastack_chunk = tstate->datastack_chunk; this->datastack_top = tstate->datastack_top; this->datastack_limit = tstate->datastack_limit; PyFrameObject *frame = PyThreadState_GetFrame((PyThreadState *)tstate); Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new // reference. this->_top_frame.steal(frame); #if GREENLET_PY313 this->delete_later = Py_XNewRef(tstate->delete_later); #elif GREENLET_PY312 this->trash_delete_nesting = tstate->trash.delete_nesting; #else // not 312 this->trash_delete_nesting = tstate->trash_delete_nesting; #endif // GREENLET_PY312 #else // Not 311 this->recursion_depth = tstate->recursion_depth; this->_top_frame.steal(tstate->frame); this->trash_delete_nesting = tstate->trash_delete_nesting; #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 { tstate->context = this->_context.relinquish_ownership(); /* Incrementing this value invalidates the contextvars cache, which would otherwise remain valid across switches */ tstate->context_ver++; #if GREENLET_USE_CFRAME tstate->cframe = this->cframe; /* If we were tracing, we need to keep tracing. There should never be the possibility of hitting the root_cframe here. See note above about why we can't just copy this from ``origin->cframe->use_tracing``. */ #if !GREENLET_PY312 tstate->cframe->use_tracing = this->use_tracing; #endif #endif // GREENLET_USE_CFRAME #if GREENLET_PY311 #if GREENLET_PY312 tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth; tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth; this->unexpose_frames(); #else // \/ 3.11 tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth; #endif // GREENLET_PY312 #if GREENLET_PY313 tstate->current_frame = this->current_frame; #elif GREENLET_USE_CFRAME tstate->cframe->current_frame = this->current_frame; #endif tstate->datastack_chunk = this->datastack_chunk; tstate->datastack_top = this->datastack_top; tstate->datastack_limit = this->datastack_limit; this->_top_frame.relinquish_ownership(); #if GREENLET_PY313 Py_XDECREF(tstate->delete_later); tstate->delete_later = this->delete_later; Py_CLEAR(this->delete_later); #elif GREENLET_PY312 tstate->trash.delete_nesting = this->trash_delete_nesting; #else // not 3.12 tstate->trash_delete_nesting = this->trash_delete_nesting; #endif // GREENLET_PY312 #else // not 3.11 tstate->frame = this->_top_frame.relinquish_ownership(); tstate->recursion_depth = this->recursion_depth; tstate->trash_delete_nesting = this->trash_delete_nesting; #endif // GREENLET_PY311 } inline void PythonState::will_switch_from(PyThreadState *const origin_tstate) noexcept { #if GREENLET_USE_CFRAME && !GREENLET_PY312 // The weird thing is, we don't actually save this for an // effect on the current greenlet, it's saved for an // effect on the target greenlet. That is, we want // continuity of this setting across the greenlet switch. this->use_tracing = origin_tstate->cframe->use_tracing; #endif } void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept { this->_top_frame = nullptr; #if GREENLET_PY312 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; // XXX: TODO: Comment from a reviewer: // Should this be ``Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining``? // But to me it looks more like that might not be the right // initialization either? this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; #elif GREENLET_PY311 this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; #else this->recursion_depth = tstate->recursion_depth; #endif } // TODO: Better state management about when we own the top frame. int PythonState::tp_traverse(visitproc visit, void* arg, bool own_top_frame) noexcept { Py_VISIT(this->_context.borrow()); if (own_top_frame) { Py_VISIT(this->_top_frame.borrow()); } return 0; } void PythonState::tp_clear(bool own_top_frame) noexcept { PythonStateContext::tp_clear(); // If we get here owning a frame, // we got dealloc'd without being finished. We may or may not be // in the same thread. if (own_top_frame) { this->_top_frame.CLEAR(); } } #if GREENLET_USE_CFRAME void PythonState::set_new_cframe(_PyCFrame& frame) noexcept { frame = *PyThreadState_GET()->cframe; /* Make the target greenlet refer to the stack value. */ this->cframe = &frame; /* And restore the link to the previous frame so this one gets unliked appropriately. */ this->cframe->previous = &PyThreadState_GET()->root_cframe; } #endif const PythonState::OwnedFrame& PythonState::top_frame() const noexcept { return this->_top_frame; } void PythonState::did_finish(PyThreadState* tstate) noexcept { #if GREENLET_PY311 // See https://github.com/gevent/gevent/issues/1924 and // https://github.com/python-greenlet/greenlet/issues/328. In // short, Python 3.11 allocates memory for frames as a sort of // linked list that's kept as part of PyThreadState in the // ``datastack_chunk`` member and friends. These are saved and // restored as part of switching greenlets. // // When we initially switch to a greenlet, we set those to NULL. // That causes the frame management code to treat this like a // brand new thread and start a fresh list of chunks, beginning // with a new "root" chunk. As we make calls in this greenlet, // those chunks get added, and as calls return, they get popped. // But the frame code (pystate.c) is careful to make sure that the // root chunk never gets popped. // // Thus, when a greenlet exits for the last time, there will be at // least a single root chunk that we must be responsible for // deallocating. // // The complex part is that these chunks are allocated and freed // using ``_PyObject_VirtualAlloc``/``Free``. Those aren't public // functions, and they aren't exported for linking. It so happens // that we know they are just thin wrappers around the Arena // allocator, so we can use that directly to deallocate in a // compatible way. // // CAUTION: Check this implementation detail on every major version. // // It might be nice to be able to do this in our destructor, but // can we be sure that no one else is using that memory? Plus, as // described below, our pointers may not even be valid anymore. As // a special case, there is one time that we know we can do this, // and that's from the destructor of the associated UserGreenlet // (NOT main greenlet) PyObjectArenaAllocator alloc; _PyStackChunk* chunk = nullptr; if (tstate) { // We really did finish, we can never be switched to again. chunk = tstate->datastack_chunk; // Unfortunately, we can't do much sanity checking. Our // this->datastack_chunk pointer is out of date (evaluation may // have popped down through it already) so we can't verify that // we deallocate it. I don't think we can even check datastack_top // for the same reason. PyObject_GetArenaAllocator(&alloc); tstate->datastack_chunk = nullptr; tstate->datastack_limit = nullptr; tstate->datastack_top = nullptr; } else if (this->datastack_chunk) { // The UserGreenlet (NOT the main greenlet!) is being deallocated. If we're // still holding a stack chunk, it's garbage because we know // we can never switch back to let cPython clean it up. // Because the last time we got switched away from, and we // haven't run since then, we know our chain is valid and can // be dealloced. chunk = this->datastack_chunk; PyObject_GetArenaAllocator(&alloc); } if (alloc.free && chunk) { // In case the arena mechanism has been torn down already. while (chunk) { _PyStackChunk *prev = chunk->previous; chunk->previous = nullptr; alloc.free(alloc.ctx, chunk, chunk->size); chunk = prev; } } this->datastack_chunk = nullptr; this->datastack_limit = nullptr; this->datastack_top = nullptr; #endif } }; // namespace greenlet #endif // GREENLET_PYTHON_STATE_CPP greenlet-3.1.0/src/greenlet/TStackState.cpp000066400000000000000000000163351467007427000206310ustar00rootroot00000000000000#ifndef GREENLET_STACK_STATE_CPP #define GREENLET_STACK_STATE_CPP #include "greenlet_greenlet.hpp" namespace greenlet { #ifdef GREENLET_USE_STDIO #include using std::cerr; using std::endl; std::ostream& operator<<(std::ostream& os, const StackState& s) { os << "StackState(stack_start=" << (void*)s._stack_start << ", stack_stop=" << (void*)s.stack_stop << ", stack_copy=" << (void*)s.stack_copy << ", stack_saved=" << s._stack_saved << ", stack_prev=" << s.stack_prev << ", addr=" << &s << ")"; return os; } #endif StackState::StackState(void* mark, StackState& current) : _stack_start(nullptr), stack_stop((char*)mark), stack_copy(nullptr), _stack_saved(0), /* Skip a dying greenlet */ stack_prev(current._stack_start ? ¤t : current.stack_prev) { } StackState::StackState() : _stack_start(nullptr), stack_stop(nullptr), stack_copy(nullptr), _stack_saved(0), stack_prev(nullptr) { } StackState::StackState(const StackState& other) // can't use a delegating constructor because of // MSVC for Python 2.7 : _stack_start(nullptr), stack_stop(nullptr), stack_copy(nullptr), _stack_saved(0), stack_prev(nullptr) { this->operator=(other); } StackState& StackState::operator=(const StackState& other) { if (&other == this) { return *this; } if (other._stack_saved) { throw std::runtime_error("Refusing to steal memory."); } //If we have memory allocated, dispose of it this->free_stack_copy(); this->_stack_start = other._stack_start; this->stack_stop = other.stack_stop; this->stack_copy = other.stack_copy; this->_stack_saved = other._stack_saved; this->stack_prev = other.stack_prev; return *this; } inline void StackState::free_stack_copy() noexcept { PyMem_Free(this->stack_copy); this->stack_copy = nullptr; this->_stack_saved = 0; } inline void StackState::copy_heap_to_stack(const StackState& current) noexcept { /* Restore the heap copy back into the C stack */ if (this->_stack_saved != 0) { memcpy(this->_stack_start, this->stack_copy, this->_stack_saved); this->free_stack_copy(); } StackState* owner = const_cast(¤t); if (!owner->_stack_start) { owner = owner->stack_prev; /* greenlet is dying, skip it */ } while (owner && owner->stack_stop <= this->stack_stop) { // cerr << "\tOwner: " << owner << endl; owner = owner->stack_prev; /* find greenlet with more stack */ } this->stack_prev = owner; // cerr << "\tFinished with: " << *this << endl; } inline int StackState::copy_stack_to_heap_up_to(const char* const stop) noexcept { /* Save more of g's stack into the heap -- at least up to 'stop' g->stack_stop |________| | | | __ stop . . . . . | | ==> . . |________| _______ | | | | | | | | g->stack_start | | |_______| g->stack_copy */ intptr_t sz1 = this->_stack_saved; intptr_t sz2 = stop - this->_stack_start; assert(this->_stack_start); if (sz2 > sz1) { char* c = (char*)PyMem_Realloc(this->stack_copy, sz2); if (!c) { PyErr_NoMemory(); return -1; } memcpy(c + sz1, this->_stack_start + sz1, sz2 - sz1); this->stack_copy = c; this->_stack_saved = sz2; } return 0; } inline int StackState::copy_stack_to_heap(char* const stackref, const StackState& current) noexcept { /* must free all the C stack up to target_stop */ const char* const target_stop = this->stack_stop; StackState* owner = const_cast(¤t); assert(owner->_stack_saved == 0); // everything is present on the stack if (!owner->_stack_start) { owner = owner->stack_prev; /* not saved if dying */ } else { owner->_stack_start = stackref; } while (owner->stack_stop < target_stop) { /* ts_current is entierely within the area to free */ if (owner->copy_stack_to_heap_up_to(owner->stack_stop)) { return -1; /* XXX */ } owner = owner->stack_prev; } if (owner != this) { if (owner->copy_stack_to_heap_up_to(target_stop)) { return -1; /* XXX */ } } return 0; } inline bool StackState::started() const noexcept { return this->stack_stop != nullptr; } inline bool StackState::main() const noexcept { return this->stack_stop == (char*)-1; } inline bool StackState::active() const noexcept { return this->_stack_start != nullptr; } inline void StackState::set_active() noexcept { assert(this->_stack_start == nullptr); this->_stack_start = (char*)1; } inline void StackState::set_inactive() noexcept { this->_stack_start = nullptr; // XXX: What if we still have memory out there? // That case is actually triggered by // test_issue251_issue252_explicit_reference_not_collectable (greenlet.tests.test_leaks.TestLeaks) // and // test_issue251_issue252_need_to_collect_in_background // (greenlet.tests.test_leaks.TestLeaks) // // Those objects never get deallocated, so the destructor never // runs. // It *seems* safe to clean up the memory here? if (this->_stack_saved) { this->free_stack_copy(); } } inline intptr_t StackState::stack_saved() const noexcept { return this->_stack_saved; } inline char* StackState::stack_start() const noexcept { return this->_stack_start; } inline StackState StackState::make_main() noexcept { StackState s; s._stack_start = (char*)1; s.stack_stop = (char*)-1; return s; } StackState::~StackState() { if (this->_stack_saved != 0) { this->free_stack_copy(); } } void StackState::copy_from_stack(void* vdest, const void* vsrc, size_t n) const { char* dest = static_cast(vdest); const char* src = static_cast(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(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 #endif // GREENLET_STACK_STATE_CPP greenlet-3.1.0/src/greenlet/TThreadStateDestroy.cpp000066400000000000000000000174661467007427000223530ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ /** * Implementation of the ThreadState destructors. * * Format with: * clang-format -i --style=file src/greenlet/greenlet.c * * * Fix missing braces with: * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" */ #ifndef T_THREADSTATE_DESTROY #define T_THREADSTATE_DESTROY #include "greenlet_greenlet.hpp" #include "greenlet_thread_state.hpp" #include "greenlet_thread_support.hpp" #include "greenlet_cpython_add_pending.hpp" #include "TGreenletGlobals.cpp" namespace greenlet { struct ThreadState_DestroyWithGIL { ThreadState_DestroyWithGIL(ThreadState* state) { if (state && state->has_main_greenlet()) { DestroyWithGIL(state); } } static int DestroyWithGIL(ThreadState* state) { // Holding the GIL. // Passed a non-shared pointer to the actual thread state. // state -> main greenlet assert(state->has_main_greenlet()); PyGreenlet* main(state->borrow_main_greenlet()); // When we need to do cross-thread operations, we check this. // A NULL value means the thread died some time ago. // We do this here, rather than in a Python dealloc function // for the greenlet, in case there's still a reference out // there. static_cast(main->pimpl)->thread_state(nullptr); delete state; // Deleting this runs the destructor, DECREFs the main greenlet. return 0; } }; struct ThreadState_DestroyNoGIL { // ensure this is actually defined. static_assert(GREENLET_BROKEN_PY_ADD_PENDING == 1 || GREENLET_BROKEN_PY_ADD_PENDING == 0, "GREENLET_BROKEN_PY_ADD_PENDING not defined correctly."); #if GREENLET_BROKEN_PY_ADD_PENDING static int _push_pending_call(struct _pending_calls *pending, int (*func)(void *), void *arg) { int i = pending->last; int j = (i + 1) % NPENDINGCALLS; if (j == pending->first) { return -1; /* Queue full */ } pending->calls[i].func = func; pending->calls[i].arg = arg; pending->last = j; return 0; } static int AddPendingCall(int (*func)(void *), void *arg) { _PyRuntimeState *runtime = &_PyRuntime; if (!runtime) { // obviously impossible return 0; } struct _pending_calls *pending = &runtime->ceval.pending; if (!pending->lock) { return 0; } int result = 0; PyThread_acquire_lock(pending->lock, WAIT_LOCK); if (!pending->finishing) { result = _push_pending_call(pending, func, arg); } PyThread_release_lock(pending->lock); SIGNAL_PENDING_CALLS(&runtime->ceval); return result; } #else // Python < 3.8 or >= 3.9 static int AddPendingCall(int (*func)(void*), void* arg) { // If the interpreter is in the middle of finalizing, we can't add a // pending call. Trying to do so will end up in a SIGSEGV, as // Py_AddPendingCall will not be able to get the interpreter and will // try to dereference a NULL pointer. It's possible this can still // segfault if we happen to get context switched, and maybe we should // just always implement our own AddPendingCall, but I'd like to see if // this works first #if GREENLET_PY313 if (Py_IsFinalizing()) { #else if (_Py_IsFinalizing()) { #endif fprintf(stderr, "greenlet: WARNING: Interpreter is finalizing. Ignoring " "call to Py_AddPendingCall; \n"); return 0; } return Py_AddPendingCall(func, arg); } #endif ThreadState_DestroyNoGIL(ThreadState* state) { // We are *NOT* holding the GIL. Our thread is in the middle // of its death throes and the Python thread state is already // gone so we can't use most Python APIs. One that is safe is // ``Py_AddPendingCall``, unless the interpreter itself has // been torn down. There is a limited number of calls that can // be queued: 32 (NPENDINGCALLS) in CPython 3.10, so we // coalesce these calls using our own queue. if (state && state->has_main_greenlet()) { // mark the thread as dead ASAP. // this is racy! If we try to throw or switch to a // greenlet from this thread from some other thread before // we clear the state pointer, it won't realize the state // is dead which can crash the process. PyGreenlet* p = state->borrow_main_greenlet(); assert(p->pimpl->thread_state() == state || p->pimpl->thread_state() == nullptr); static_cast(p->pimpl)->thread_state(nullptr); } // NOTE: Because we're not holding the GIL here, some other // Python thread could run and call ``os.fork()``, which would // be bad if that happened while we are holding the cleanup // lock (it wouldn't function in the child process). // Make a best effort to try to keep the duration we hold the // lock short. // TODO: On platforms that support it, use ``pthread_atfork`` to // drop this lock. LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); if (state && state->has_main_greenlet()) { // Because we don't have the GIL, this is a race condition. if (!PyInterpreterState_Head()) { // We have to leak the thread state, if the // interpreter has shut down when we're getting // deallocated, we can't run the cleanup code that // deleting it would imply. return; } mod_globs->queue_to_destroy(state); if (mod_globs->thread_states_to_destroy.size() == 1) { // We added the first item to the queue. We need to schedule // the cleanup. int result = ThreadState_DestroyNoGIL::AddPendingCall( ThreadState_DestroyNoGIL::DestroyQueueWithGIL, NULL); if (result < 0) { // Hmm, what can we do here? fprintf(stderr, "greenlet: WARNING: failed in call to Py_AddPendingCall; " "expect a memory leak.\n"); } } } } static int DestroyQueueWithGIL(void* UNUSED(arg)) { // We're holding the GIL here, so no Python code should be able to // run to call ``os.fork()``. while (1) { ThreadState* to_destroy; { LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); if (mod_globs->thread_states_to_destroy.empty()) { break; } to_destroy = mod_globs->take_next_to_destroy(); } // Drop the lock while we do the actual deletion. ThreadState_DestroyWithGIL::DestroyWithGIL(to_destroy); } return 0; } }; }; // namespace greenlet // The intent when GET_THREAD_STATE() is needed multiple times in a // function is to take a reference to its return value in a local // variable, to avoid the thread-local indirection. On some platforms // (macOS), accessing a thread-local involves a function call (plus an // initial function call in each function that uses a thread local); // in contrast, static volatile variables are at some pre-computed // offset. typedef greenlet::ThreadStateCreator ThreadStateCreator; static thread_local ThreadStateCreator g_thread_state_global; #define GET_THREAD_STATE() g_thread_state_global #endif //T_THREADSTATE_DESTROY greenlet-3.1.0/src/greenlet/TUserGreenlet.cpp000066400000000000000000000561021467007427000211630ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ /** * Implementation of greenlet::UserGreenlet. * * Format with: * clang-format -i --style=file src/greenlet/greenlet.c * * * Fix missing braces with: * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" */ #include "greenlet_internal.hpp" #include "greenlet_greenlet.hpp" #include "greenlet_thread_state.hpp" #include "TThreadStateDestroy.cpp" namespace greenlet { using greenlet::refs::BorrowedMainGreenlet; greenlet::PythonAllocator UserGreenlet::allocator; void* UserGreenlet::operator new(size_t UNUSED(count)) { return allocator.allocate(1); } void UserGreenlet::operator delete(void* ptr) { return allocator.deallocate(static_cast(ptr), 1); } UserGreenlet::UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent) : Greenlet(p), _parent(the_parent) { this->_self = p; } UserGreenlet::~UserGreenlet() { // Python 3.11: If we don't clear out the raw frame datastack // when deleting an unfinished greenlet, // TestLeaks.test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main fails. this->python_state.did_finish(nullptr); this->tp_clear(); } BorrowedGreenlet UserGreenlet::self() const noexcept { return this->_self; } const BorrowedMainGreenlet UserGreenlet::main_greenlet() const { return this->_main_greenlet; } BorrowedMainGreenlet UserGreenlet::find_main_greenlet_in_lineage() const { if (this->started()) { assert(this->_main_greenlet); return BorrowedMainGreenlet(this->_main_greenlet); } if (!this->_parent) { /* garbage collected greenlet in chain */ // XXX: WHAT? return BorrowedMainGreenlet(nullptr); } return this->_parent->find_main_greenlet_in_lineage(); } /** * CAUTION: This will allocate memory and may trigger garbage * collection and arbitrary Python code. */ OwnedObject UserGreenlet::throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state) { /* The dying greenlet cannot be a parent of ts_current because the 'parent' field chain would hold a reference */ UserGreenlet::ParentIsCurrentGuard with_current_parent(this, current_thread_state); // We don't care about the return value, only whether an // exception happened. Whether or not an exception happens, // we need to restore the parent in case the greenlet gets // resurrected. return Greenlet::throw_GreenletExit_during_dealloc(current_thread_state); } ThreadState* UserGreenlet::thread_state() const noexcept { // TODO: maybe make this throw, if the thread state isn't there? // if (!this->main_greenlet) { // throw std::runtime_error("No thread state"); // TODO: Better exception // } if (!this->_main_greenlet) { return nullptr; } return this->_main_greenlet->thread_state(); } bool UserGreenlet::was_running_in_dead_thread() const noexcept { return this->_main_greenlet && !this->thread_state(); } OwnedObject UserGreenlet::g_switch() { assert(this->args() || PyErr_Occurred()); try { this->check_switch_allowed(); } catch (const PyErrOccurred&) { this->release_args(); throw; } // Switching greenlets used to attempt to clean out ones that need // deleted *if* we detected a thread switch. Should it still do // that? // An issue is that if we delete a greenlet from another thread, // it gets queued to this thread, and ``kill_greenlet()`` switches // back into the greenlet /* find the real target by ignoring dead greenlets, and if necessary starting a greenlet. */ switchstack_result_t err; Greenlet* target = this; // TODO: probably cleaner to handle the case where we do // switch to ourself separately from the other cases. // This can probably even further be simplified if we keep // track of the switching_state we're going for and just call // into g_switch() if it's not ourself. The main problem with that // is that we would be using more stack space. bool target_was_me = true; bool was_initial_stub = false; while (target) { if (target->active()) { if (!target_was_me) { target->args() <<= this->args(); assert(!this->args()); } err = target->g_switchstack(); break; } if (!target->started()) { // We never encounter a main greenlet that's not started. assert(!target->main()); UserGreenlet* real_target = static_cast(target); assert(real_target); void* dummymarker; was_initial_stub = true; if (!target_was_me) { target->args() <<= this->args(); assert(!this->args()); } try { // This can only throw back to us while we're // still in this greenlet. Once the new greenlet // is bootstrapped, it has its own exception state. err = real_target->g_initialstub(&dummymarker); } catch (const PyErrOccurred&) { this->release_args(); throw; } catch (const GreenletStartedWhileInPython&) { // The greenlet was started sometime before this // greenlet actually switched to it, i.e., // "concurrent" calls to switch() or throw(). // We need to retry the switch. // Note that the current greenlet has been reset // to this one (or we wouldn't be running!) continue; } break; } target = target->parent(); target_was_me = false; } // The ``this`` pointer and all other stack or register based // variables are invalid now, at least where things succeed // above. // But this one, probably not so much? It's not clear if it's // safe to throw an exception at this point. if (err.status < 0) { // If we get here, either g_initialstub() // failed, or g_switchstack() failed. Either one of those // cases SHOULD leave us in the original greenlet with a valid // stack. return this->on_switchstack_or_initialstub_failure(target, err, target_was_me, was_initial_stub); } // err.the_new_current_greenlet would be the same as ``target``, // if target wasn't probably corrupt. return err.the_new_current_greenlet->g_switch_finish(err); } Greenlet::switchstack_result_t UserGreenlet::g_initialstub(void* mark) { OwnedObject run; // We need to grab a reference to the current switch arguments // in case we're entered concurrently during the call to // GetAttr() and have to try again. // We'll restore them when we return in that case. // Scope them tightly to avoid ref leaks. { SwitchingArgs args(this->args()); /* save exception in case getattr clears it */ PyErrPieces saved; /* self.run is the object to call in the new greenlet. This could run arbitrary python code and switch greenlets! */ run = this->_self.PyRequireAttr(mod_globs->str_run); /* restore saved exception */ saved.PyErrRestore(); /* recheck that it's safe to switch in case greenlet reparented anywhere above */ this->check_switch_allowed(); /* by the time we got here another start could happen elsewhere, * that means it should now be a regular switch. * This can happen if the Python code is a subclass that implements * __getattribute__ or __getattr__, or makes ``run`` a descriptor; * all of those can run arbitrary code that switches back into * this greenlet. */ if (this->stack_state.started()) { // the successful switch cleared these out, we need to // restore our version. They will be copied on up to the // next target. assert(!this->args()); this->args() <<= args; throw GreenletStartedWhileInPython(); } } // Sweet, if we got here, we have the go-ahead and will switch // greenlets. // Nothing we do from here on out should allow for a thread or // greenlet switch: No arbitrary calls to Python, including // decref'ing #if GREENLET_USE_CFRAME /* OK, we need it, we're about to switch greenlets, save the state. */ /* See green_new(). This is a stack-allocated variable used while *self* is in PyObject_Call(). We want to defer copying the state info until we're sure we need it and are in a stable place to do so. */ _PyCFrame trace_info; this->python_state.set_new_cframe(trace_info); #endif /* start the greenlet */ ThreadState& thread_state = GET_THREAD_STATE().state(); this->stack_state = StackState(mark, thread_state.borrow_current()->stack_state); this->python_state.set_initial_state(PyThreadState_GET()); this->exception_state.clear(); this->_main_greenlet = thread_state.get_main_greenlet(); /* perform the initial switch */ switchstack_result_t err = this->g_switchstack(); /* returns twice! The 1st time with ``err == 1``: we are in the new greenlet. This one owns a greenlet that used to be current. The 2nd time with ``err <= 0``: back in the caller's greenlet; this happens if the child finishes or switches explicitly to us. Either way, the ``err`` variable is created twice at the same memory location, but possibly having different ``origin`` values. Note that it's not constructed for the second time until the switch actually happens. */ if (err.status == 1) { // In the new greenlet. // This never returns! Calling inner_bootstrap steals // the contents of our run object within this stack frame, so // it is not valid to do anything with it. try { this->inner_bootstrap(err.origin_greenlet.relinquish_ownership(), run.relinquish_ownership()); } // Getting a C++ exception here isn't good. It's probably a // bug in the underlying greenlet, meaning it's probably a // C++ extension. We're going to abort anyway, but try to // display some nice information *if* possible. Some obscure // platforms don't properly support this (old 32-bit Arm, see see // https://github.com/python-greenlet/greenlet/issues/385); that's not // great, but should usually be OK because, as mentioned above, we're // terminating anyway. // // The catching is tested by // ``test_cpp.CPPTests.test_unhandled_exception_in_greenlet_aborts``. // // PyErrOccurred can theoretically be thrown by // inner_bootstrap() -> g_switch_finish(), but that should // never make it back to here. It is a std::exception and // would be caught if it is. catch (const std::exception& e) { std::string base = "greenlet: Unhandled C++ exception: "; base += e.what(); Py_FatalError(base.c_str()); } catch (...) { // Some compilers/runtimes use exceptions internally. // It appears that GCC on Linux with libstdc++ throws an // exception internally at process shutdown time to unwind // stacks and clean up resources. Depending on exactly // where we are when the process exits, that could result // in an unknown exception getting here. If we // Py_FatalError() or abort() here, we interfere with // orderly process shutdown. Throwing the exception on up // is the right thing to do. // // gevent's ``examples/dns_mass_resolve.py`` demonstrates this. #ifndef NDEBUG fprintf(stderr, "greenlet: inner_bootstrap threw unknown exception; " "is the process terminating?\n"); #endif throw; } Py_FatalError("greenlet: inner_bootstrap returned with no exception.\n"); } // In contrast, notice that we're keeping the origin greenlet // around as an owned reference; we need it to call the trace // function for the switch back into the parent. It was only // captured at the time the switch actually happened, though, // so we haven't been keeping an extra reference around this // whole time. /* back in the parent */ if (err.status < 0) { /* start failed badly, restore greenlet state */ this->stack_state = StackState(); this->_main_greenlet.CLEAR(); // CAUTION: This may run arbitrary Python code. run.CLEAR(); // inner_bootstrap didn't run, we own the reference. } // In the success case, the spawned code (inner_bootstrap) will // take care of decrefing this, so we relinquish ownership so as // to not double-decref. run.relinquish_ownership(); return err; } void UserGreenlet::inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run) { // The arguments here would be another great place for move. // As it is, we take them as a reference so that when we clear // them we clear what's on the stack above us. Do that NOW, and // without using a C++ RAII object, // so there's no way that exiting the parent frame can clear it, // or we clear it unexpectedly. This arises in the context of the // interpreter shutting down. See https://github.com/python-greenlet/greenlet/issues/325 //PyObject* run = _run.relinquish_ownership(); /* in the new greenlet */ assert(this->thread_state()->borrow_current() == this->_self); // C++ exceptions cannot propagate to the parent greenlet from // here. (TODO: Do we need a catch(...) clause, perhaps on the // function itself? ALl we could do is terminate the program.) // NOTE: On 32-bit Windows, the call chain is extremely // important here in ways that are subtle, having to do with // the depth of the SEH list. The call to restore it MUST NOT // add a new SEH handler to the list, or we'll restore it to // the wrong thing. this->thread_state()->restore_exception_state(); /* stack variables from above are no good and also will not unwind! */ // EXCEPT: That can't be true, we access run, among others, here. this->stack_state.set_active(); /* running */ // We're about to possibly run Python code again, which // could switch back/away to/from us, so we need to grab the // arguments locally. SwitchingArgs args; args <<= this->args(); assert(!this->args()); // XXX: We could clear this much earlier, right? // Or would that introduce the possibility of running Python // code when we don't want to? // CAUTION: This may run arbitrary Python code. this->_run_callable.CLEAR(); // The first switch we need to manually call the trace // function here instead of in g_switch_finish, because we // never return there. if (OwnedObject tracefunc = this->thread_state()->get_tracefunc()) { OwnedGreenlet trace_origin; trace_origin = origin_greenlet; try { g_calltrace(tracefunc, args ? mod_globs->event_switch : mod_globs->event_throw, trace_origin, this->_self); } catch (const PyErrOccurred&) { /* Turn trace errors into switch throws */ args.CLEAR(); } } // We no longer need the origin, it was only here for // tracing. // We may never actually exit this stack frame so we need // to explicitly clear it. // This could run Python code and switch. Py_CLEAR(origin_greenlet); OwnedObject result; if (!args) { /* pending exception */ result = NULL; } else { /* call g.run(*args, **kwargs) */ // This could result in further switches try { //result = run.PyCall(args.args(), args.kwargs()); // CAUTION: Just invoking this, before the function even // runs, may cause memory allocations, which may trigger // GC, which may run arbitrary Python code. result = OwnedObject::consuming(PyObject_Call(run, args.args().borrow(), args.kwargs().borrow())); } catch (...) { // Unhandled C++ exception! // If we declare ourselves as noexcept, if we don't catch // this here, most platforms will just abort() the // process. But on 64-bit Windows with older versions of // the C runtime, this can actually corrupt memory and // just return. We see this when compiling with the // Windows 7.0 SDK targeting Windows Server 2008, but not // when using the Appveyor Visual Studio 2019 image. So // this currently only affects Python 2.7 on Windows 64. // That is, the tests pass and the runtime aborts // everywhere else. // // However, if we catch it and try to continue with a // Python error, then all Windows 64 bit platforms corrupt // memory. So all we can do is manually abort, hopefully // with a good error message. (Note that the above was // tested WITHOUT the `/EHr` switch being used at compile // time, so MSVC may have "optimized" out important // checking. Using that switch, we may be in a better // place in terms of memory corruption.) But sometimes it // can't be caught here at all, which is confusing but not // terribly surprising; so again, the G_NOEXCEPT_WIN32 // plus "/EHr". // // Hopefully the basic C stdlib is still functional enough // for us to at least print an error. // // It gets more complicated than that, though, on some // platforms, specifically at least Linux/gcc/libstdc++. They use // an exception to unwind the stack when a background // thread exits. (See comments about noexcept.) So this // may not actually represent anything untoward. On those // platforms we allow throws of this to propagate, or // attempt to anyway. # if defined(WIN32) || defined(_WIN32) Py_FatalError( "greenlet: Unhandled C++ exception from a greenlet run function. " "Because memory is likely corrupted, terminating process."); std::abort(); #else throw; #endif } } // These lines may run arbitrary code args.CLEAR(); Py_CLEAR(run); if (!result && mod_globs->PyExc_GreenletExit.PyExceptionMatches() && (this->args())) { // This can happen, for example, if our only reference // goes away after we switch back to the parent. // See test_dealloc_switch_args_not_lost PyErrPieces clear_error; result <<= this->args(); result = single_result(result); } this->release_args(); this->python_state.did_finish(PyThreadState_GET()); result = g_handle_exit(result); assert(this->thread_state()->borrow_current() == this->_self); /* jump back to parent */ this->stack_state.set_inactive(); /* dead */ // TODO: Can we decref some things here? Release our main greenlet // and maybe parent? for (Greenlet* parent = this->_parent; parent; parent = parent->parent()) { // We need to somewhere consume a reference to // the result; in most cases we'll never have control // back in this stack frame again. Calling // green_switch actually adds another reference! // This would probably be clearer with a specific API // to hand results to the parent. parent->args() <<= result; assert(!result); // The parent greenlet now owns the result; in the // typical case we'll never get back here to assign to // result and thus release the reference. try { result = parent->g_switch(); } catch (const PyErrOccurred&) { // Ignore, keep passing the error on up. } /* Return here means switch to parent failed, * in which case we throw *current* exception * to the next parent in chain. */ assert(!result); } /* We ran out of parents, cannot continue */ PyErr_WriteUnraisable(this->self().borrow_o()); Py_FatalError("greenlet: ran out of parent greenlets while propagating exception; " "cannot continue"); std::abort(); } void UserGreenlet::run(const BorrowedObject nrun) { if (this->started()) { throw AttributeError( "run cannot be set " "after the start of the greenlet"); } this->_run_callable = nrun; } const OwnedGreenlet UserGreenlet::parent() const { return this->_parent; } void UserGreenlet::parent(const BorrowedObject raw_new_parent) { if (!raw_new_parent) { throw AttributeError("can't delete attribute"); } BorrowedMainGreenlet main_greenlet_of_new_parent; BorrowedGreenlet new_parent(raw_new_parent.borrow()); // could // throw // TypeError! for (BorrowedGreenlet p = new_parent; p; p = p->parent()) { if (p == this->_self) { throw ValueError("cyclic parent chain"); } main_greenlet_of_new_parent = p->main_greenlet(); } if (!main_greenlet_of_new_parent) { throw ValueError("parent must not be garbage collected"); } if (this->started() && this->_main_greenlet != main_greenlet_of_new_parent) { throw ValueError("parent cannot be on a different thread"); } this->_parent = new_parent; } void UserGreenlet::murder_in_place() { this->_main_greenlet.CLEAR(); Greenlet::murder_in_place(); } bool UserGreenlet::belongs_to_thread(const ThreadState* thread_state) const { return Greenlet::belongs_to_thread(thread_state) && this->_main_greenlet == thread_state->borrow_main_greenlet(); } int UserGreenlet::tp_traverse(visitproc visit, void* arg) { Py_VISIT(this->_parent.borrow_o()); Py_VISIT(this->_main_greenlet.borrow_o()); Py_VISIT(this->_run_callable.borrow_o()); return Greenlet::tp_traverse(visit, arg); } int UserGreenlet::tp_clear() { Greenlet::tp_clear(); this->_parent.CLEAR(); this->_main_greenlet.CLEAR(); this->_run_callable.CLEAR(); return 0; } UserGreenlet::ParentIsCurrentGuard::ParentIsCurrentGuard(UserGreenlet* p, const ThreadState& thread_state) : oldparent(p->_parent), greenlet(p) { p->_parent = thread_state.get_current(); } UserGreenlet::ParentIsCurrentGuard::~ParentIsCurrentGuard() { this->greenlet->_parent = oldparent; oldparent.CLEAR(); } }; //namespace greenlet greenlet-3.1.0/src/greenlet/__init__.py000066400000000000000000000032731467007427000200410ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ The root of the greenlet package. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function __all__ = [ '__version__', '_C_API', 'GreenletExit', 'error', 'getcurrent', 'greenlet', 'gettrace', 'settrace', ] # pylint:disable=no-name-in-module ### # Metadata ### __version__ = '3.1.0' from ._greenlet import _C_API # pylint:disable=no-name-in-module ### # Exceptions ### from ._greenlet import GreenletExit from ._greenlet import error ### # greenlets ### from ._greenlet import getcurrent from ._greenlet import greenlet ### # tracing ### try: from ._greenlet import gettrace from ._greenlet import settrace except ImportError: # Tracing wasn't supported. # XXX: The option to disable it was removed in 1.0, # so this branch should be dead code. pass ### # Constants # These constants aren't documented and aren't recommended. # In 1.0, USE_GC and USE_TRACING are always true, and USE_CONTEXT_VARS # is the same as ``sys.version_info[:2] >= 3.7`` ### from ._greenlet import GREENLET_USE_CONTEXT_VARS # pylint:disable=unused-import from ._greenlet import GREENLET_USE_GC # pylint:disable=unused-import from ._greenlet import GREENLET_USE_TRACING # pylint:disable=unused-import # Controlling the use of the gc module. Provisional API for this greenlet # implementation in 2.0. from ._greenlet import CLOCKS_PER_SEC # pylint:disable=unused-import from ._greenlet import enable_optional_cleanup # pylint:disable=unused-import from ._greenlet import get_clocks_used_doing_optional_cleanup # pylint:disable=unused-import # Other APIS in the _greenlet module are for test support. greenlet-3.1.0/src/greenlet/greenlet.cpp000066400000000000000000001373211467007427000202430ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ /* Format with: * clang-format -i --style=file src/greenlet/greenlet.c * * * Fix missing braces with: * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" */ #include #include #include #include #define PY_SSIZE_T_CLEAN #include #include "structmember.h" // PyMemberDef #include "greenlet_internal.hpp" // Code after this point can assume access to things declared in stdint.h, // including the fixed-width types. This goes for the platform-specific switch functions // as well. #include "greenlet_refs.hpp" #include "greenlet_slp_switch.hpp" #include "greenlet_thread_state.hpp" #include "greenlet_thread_support.hpp" #include "greenlet_greenlet.hpp" #include "TGreenletGlobals.cpp" #include "TThreadStateDestroy.cpp" #include "TGreenlet.cpp" #include "TMainGreenlet.cpp" #include "TUserGreenlet.cpp" #include "TBrokenGreenlet.cpp" #include "TExceptionState.cpp" #include "TPythonState.cpp" #include "TStackState.cpp" using greenlet::LockGuard; using greenlet::LockInitError; using greenlet::PyErrOccurred; using greenlet::Require; using greenlet::g_handle_exit; using greenlet::single_result; using greenlet::Greenlet; using greenlet::UserGreenlet; using greenlet::MainGreenlet; using greenlet::BrokenGreenlet; using greenlet::ThreadState; using greenlet::PythonState; // ******* Implementation of things from included files template greenlet::refs::_BorrowedGreenlet& greenlet::refs::_BorrowedGreenlet::operator=(const greenlet::refs::BorrowedObject& other) { this->_set_raw_pointer(static_cast(other)); return *this; } template inline greenlet::refs::_BorrowedGreenlet::operator Greenlet*() const noexcept { if (!this->p) { return nullptr; } return reinterpret_cast(this->p)->pimpl; } template greenlet::refs::_BorrowedGreenlet::_BorrowedGreenlet(const BorrowedObject& p) : BorrowedReference(nullptr) { this->_set_raw_pointer(p.borrow()); } template inline greenlet::refs::_OwnedGreenlet::operator Greenlet*() const noexcept { if (!this->p) { return nullptr; } return reinterpret_cast(this->p)->pimpl; } #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wmissing-field-initializers" # pragma clang diagnostic ignored "-Wwritable-strings" #elif defined(__GNUC__) # pragma GCC diagnostic push // warning: ISO C++ forbids converting a string constant to â€char*’ // (The python APIs aren't const correct and accept writable char*) # pragma GCC diagnostic ignored "-Wwrite-strings" #endif /*********************************************************** A PyGreenlet is a range of C stack addresses that must be saved and restored in such a way that the full range of the stack contains valid data when we switch to it. Stack layout for a greenlet: | ^^^ | | older data | | | stack_stop . |_______________| . | | . | greenlet data | . | in stack | . * |_______________| . . _____________ stack_copy + stack_saved . | | | | . | data | |greenlet data| . | unrelated | | saved | . | to | | in heap | stack_start . | this | . . |_____________| stack_copy | greenlet | | | | newer data | | vvv | Note that a greenlet's stack data is typically partly at its correct place in the stack, and partly saved away in the heap, but always in the above configuration: two blocks, the more recent one in the heap and the older one still in the stack (either block may be empty). Greenlets are chained: each points to the previous greenlet, which is the one that owns the data currently in the C stack above my stack_stop. The currently running greenlet is the first element of this chain. The main (initial) greenlet is the last one. Greenlets whose stack is entirely in the heap can be skipped from the chain. The chain is not related to execution order, but only to the order in which bits of C stack happen to belong to greenlets at a particular point in time. The main greenlet doesn't have a stack_stop: it is responsible for the complete rest of the C stack, and we don't know where it begins. We use (char*) -1, the largest possible address. States: stack_stop == NULL && stack_start == NULL: did not start yet stack_stop != NULL && stack_start == NULL: already finished stack_stop != NULL && stack_start != NULL: active The running greenlet's stack_start is undefined but not NULL. ***********************************************************/ static PyGreenlet* green_create_main(ThreadState* state) { PyGreenlet* gmain; /* create the main greenlet for this thread */ gmain = (PyGreenlet*)PyType_GenericAlloc(&PyGreenlet_Type, 0); if (gmain == NULL) { Py_FatalError("green_create_main failed to alloc"); return NULL; } new MainGreenlet(gmain, state); assert(Py_REFCNT(gmain) == 1); return gmain; } /***********************************************************/ /* Some functions must not be inlined: * slp_restore_state, when inlined into slp_switch might cause it to restore stack over its own local variables * slp_save_state, when inlined would add its own local variables to the saved stack, wasting space * slp_switch, cannot be inlined for obvious reasons * g_initialstub, when inlined would receive a pointer into its own stack frame, leading to incomplete stack save/restore g_initialstub is a member function and declared virtual so that the compiler always calls it through a vtable. slp_save_state and slp_restore_state are also member functions. They are called from trampoline functions that themselves are declared as not eligible for inlining. */ extern "C" { static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref) { return switching_thread_state->slp_save_state(stackref); } static void GREENLET_NOINLINE(slp_restore_state_trampoline)() { switching_thread_state->slp_restore_state(); } } /***********************************************************/ static PyGreenlet* green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds)) { PyGreenlet* o = (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict); if (o) { new UserGreenlet(o, GET_THREAD_STATE().state().borrow_current()); assert(Py_REFCNT(o) == 1); } return o; } static PyGreenlet* green_unswitchable_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds)) { PyGreenlet* o = (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict); if (o) { new BrokenGreenlet(o, GET_THREAD_STATE().state().borrow_current()); assert(Py_REFCNT(o) == 1); } return o; } static int green_setrun(BorrowedGreenlet self, BorrowedObject nrun, void* c); static int green_setparent(BorrowedGreenlet self, BorrowedObject nparent, void* c); static int green_init(BorrowedGreenlet self, BorrowedObject args, BorrowedObject kwargs) { PyArgParseParam run; PyArgParseParam nparent; static char* kwlist[] = { "run", "parent", NULL }; // recall: The O specifier does NOT increase the reference count. if (!PyArg_ParseTupleAndKeywords( args, kwargs, "|OO:green", kwlist, &run, &nparent)) { return -1; } if (run) { if (green_setrun(self, run, NULL)) { return -1; } } if (nparent && !nparent.is_None()) { return green_setparent(self, nparent, NULL); } return 0; } static int green_traverse(PyGreenlet* self, visitproc visit, void* arg) { // We must only visit referenced objects, i.e. only objects // Py_INCREF'ed by this greenlet (directly or indirectly): // // - stack_prev is not visited: holds previous stack pointer, but it's not // referenced // - frames are not visited as we don't strongly reference them; // alive greenlets are not garbage collected // anyway. This can be a problem, however, if this greenlet is // never allowed to finish, and is referenced from the frame: we // have an uncollectible cycle in that case. Note that the // frame object itself is also frequently not even tracked by the GC // starting with Python 3.7 (frames are allocated by the // interpreter untracked, and only become tracked when their // evaluation is finished if they have a refcount > 1). All of // this is to say that we should probably strongly reference // the frame object. Doing so, while always allowing GC on a // greenlet, solves several leaks for us. Py_VISIT(self->dict); if (!self->pimpl) { // Hmm. I have seen this at interpreter shutdown time, // I think. That's very odd because this doesn't go away until // we're ``green_dealloc()``, at which point we shouldn't be // traversed anymore. return 0; } return self->pimpl->tp_traverse(visit, arg); } static int green_is_gc(BorrowedGreenlet self) { int result = 0; /* Main greenlet can be garbage collected since it can only become unreachable if the underlying thread exited. Active greenlets --- including those that are suspended --- cannot be garbage collected, however. */ if (self->main() || !self->active()) { result = 1; } // The main greenlet pointer will eventually go away after the thread dies. if (self->was_running_in_dead_thread()) { // Our thread is dead! We can never run again. Might as well // GC us. Note that if a tuple containing only us and other // immutable objects had been scanned before this, when we // would have returned 0, the tuple will take itself out of GC // tracking and never be investigated again. So that could // result in both us and the tuple leaking due to an // unreachable/uncollectible reference. The same goes for // dictionaries. // // It's not a great idea to be changing our GC state on the // fly. result = 1; } return result; } static int green_clear(PyGreenlet* self) { /* Greenlet is only cleared if it is about to be collected. Since active greenlets are not garbage collectable, we can be sure that, even if they are deallocated during clear, nothing they reference is in unreachable or finalizers, so even if it switches we are relatively safe. */ // XXX: Are we responsible for clearing weakrefs here? Py_CLEAR(self->dict); return self->pimpl->tp_clear(); } /** * Returns 0 on failure (the object was resurrected) or 1 on success. **/ static int _green_dealloc_kill_started_non_main_greenlet(BorrowedGreenlet self) { /* Hacks hacks hacks copied from instance_dealloc() */ /* Temporarily resurrect the greenlet. */ assert(self.REFCNT() == 0); Py_SET_REFCNT(self.borrow(), 1); /* Save the current exception, if any. */ PyErrPieces saved_err; try { // BY THE TIME WE GET HERE, the state may actually be going // away // if we're shutting down the interpreter and freeing thread // entries, // this could result in freeing greenlets that were leaked. So // we can't try to read the state. self->deallocing_greenlet_in_thread( self->thread_state() ? static_cast(GET_THREAD_STATE()) : nullptr); } catch (const PyErrOccurred&) { PyErr_WriteUnraisable(self.borrow_o()); /* XXX what else should we do? */ } /* Check for no resurrection must be done while we keep * our internal reference, otherwise PyFile_WriteObject * causes recursion if using Py_INCREF/Py_DECREF */ if (self.REFCNT() == 1 && self->active()) { /* Not resurrected, but still not dead! XXX what else should we do? we complain. */ PyObject* f = PySys_GetObject("stderr"); Py_INCREF(self.borrow_o()); /* leak! */ if (f != NULL) { PyFile_WriteString("GreenletExit did not kill ", f); PyFile_WriteObject(self.borrow_o(), f, 0); PyFile_WriteString("\n", f); } } /* Restore the saved exception. */ saved_err.PyErrRestore(); /* Undo the temporary resurrection; can't use DECREF here, * it would cause a recursive call. */ assert(self.REFCNT() > 0); Py_ssize_t refcnt = self.REFCNT() - 1; Py_SET_REFCNT(self.borrow_o(), refcnt); if (refcnt != 0) { /* Resurrected! */ _Py_NewReference(self.borrow_o()); Py_SET_REFCNT(self.borrow_o(), refcnt); /* Better to use tp_finalizer slot (PEP 442) * and call ``PyObject_CallFinalizerFromDealloc``, * but that's only supported in Python 3.4+; see * Modules/_io/iobase.c for an example. * * The following approach is copied from iobase.c in CPython 2.7. * (along with much of this function in general). Here's their * comment: * * When called from a heap type's dealloc, the type will be * decref'ed on return (see e.g. subtype_dealloc in typeobject.c). */ if (PyType_HasFeature(self.TYPE(), Py_TPFLAGS_HEAPTYPE)) { Py_INCREF(self.TYPE()); } PyObject_GC_Track((PyObject*)self); _Py_DEC_REFTOTAL; #ifdef COUNT_ALLOCS --Py_TYPE(self)->tp_frees; --Py_TYPE(self)->tp_allocs; #endif /* COUNT_ALLOCS */ return 0; } return 1; } static void green_dealloc(PyGreenlet* self) { PyObject_GC_UnTrack(self); BorrowedGreenlet me(self); if (me->active() && me->started() && !me->main()) { if (!_green_dealloc_kill_started_non_main_greenlet(me)) { return; } } if (self->weakreflist != NULL) { PyObject_ClearWeakRefs((PyObject*)self); } Py_CLEAR(self->dict); if (self->pimpl) { // In case deleting this, which frees some memory, // somehow winds up calling back into us. That's usually a //bug in our code. Greenlet* p = self->pimpl; self->pimpl = nullptr; delete p; } // and finally we're done. self is now invalid. Py_TYPE(self)->tp_free((PyObject*)self); } static OwnedObject throw_greenlet(BorrowedGreenlet self, PyErrPieces& err_pieces) { PyObject* result = nullptr; err_pieces.PyErrRestore(); assert(PyErr_Occurred()); if (self->started() && !self->active()) { /* dead greenlet: turn GreenletExit into a regular return */ result = g_handle_exit(OwnedObject()).relinquish_ownership(); } self->args() <<= result; return single_result(self->g_switch()); } PyDoc_STRVAR( green_switch_doc, "switch(*args, **kwargs)\n" "\n" "Switch execution to this greenlet.\n" "\n" "If this greenlet has never been run, then this greenlet\n" "will be switched to using the body of ``self.run(*args, **kwargs)``.\n" "\n" "If the greenlet is active (has been run, but was switch()'ed\n" "out before leaving its run function), then this greenlet will\n" "be resumed and the return value to its switch call will be\n" "None if no arguments are given, the given argument if one\n" "argument is given, or the args tuple and keyword args dict if\n" "multiple arguments are given.\n" "\n" "If the greenlet is dead, or is the current greenlet then this\n" "function will simply return the arguments using the same rules as\n" "above.\n"); static PyObject* green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs) { using greenlet::SwitchingArgs; SwitchingArgs switch_args(OwnedObject::owning(args), OwnedObject::owning(kwargs)); self->pimpl->may_switch_away(); self->pimpl->args() <<= switch_args; // If we're switching out of a greenlet, and that switch is the // last thing the greenlet does, the greenlet ought to be able to // go ahead and die at that point. Currently, someone else must // manually switch back to the greenlet so that we "fall off the // end" and can perform cleanup. You'd think we'd be able to // figure out that this is happening using the frame's ``f_lasti`` // member, which is supposed to be an index into // ``frame->f_code->co_code``, the bytecode string. However, in // recent interpreters, ``f_lasti`` tends not to be updated thanks // to things like the PREDICT() macros in ceval.c. So it doesn't // really work to do that in many cases. For example, the Python // code: // def run(): // greenlet.getcurrent().parent.switch() // produces bytecode of len 16, with the actual call to switch() // being at index 10 (in Python 3.10). However, the reported // ``f_lasti`` we actually see is...5! (Which happens to be the // second byte of the CALL_METHOD op for ``getcurrent()``). try { //OwnedObject result = single_result(self->pimpl->g_switch()); OwnedObject result(single_result(self->pimpl->g_switch())); #ifndef NDEBUG // Note that the current greenlet isn't necessarily self. If self // finished, we went to one of its parents. assert(!self->pimpl->args()); const BorrowedGreenlet& current = GET_THREAD_STATE().state().borrow_current(); // It's possible it's never been switched to. assert(!current->args()); #endif PyObject* p = result.relinquish_ownership(); if (!p && !PyErr_Occurred()) { // This shouldn't be happening anymore, so the asserts // are there for debug builds. Non-debug builds // crash "gracefully" in this case, although there is an // argument to be made for killing the process in all // cases --- for this to be the case, our switches // probably nested in an incorrect way, so the state is // suspicious. Nothing should be corrupt though, just // confused at the Python level. Letting this propagate is // probably good enough. assert(p || PyErr_Occurred()); throw PyErrOccurred( mod_globs->PyExc_GreenletError, "Greenlet.switch() returned NULL without an exception set." ); } return p; } catch(const PyErrOccurred&) { return nullptr; } } PyDoc_STRVAR( green_throw_doc, "Switches execution to this greenlet, but immediately raises the\n" "given exception in this greenlet. If no argument is provided, the " "exception\n" "defaults to `greenlet.GreenletExit`. The normal exception\n" "propagation rules apply, as described for `switch`. Note that calling " "this\n" "method is almost equivalent to the following::\n" "\n" " def raiser():\n" " raise typ, val, tb\n" " g_raiser = greenlet(raiser, parent=g)\n" " g_raiser.switch()\n" "\n" "except that this trick does not work for the\n" "`greenlet.GreenletExit` exception, which would not propagate\n" "from ``g_raiser`` to ``g``.\n"); static PyObject* green_throw(PyGreenlet* self, PyObject* args) { PyArgParseParam typ(mod_globs->PyExc_GreenletExit); PyArgParseParam val; PyArgParseParam tb; if (!PyArg_ParseTuple(args, "|OOO:throw", &typ, &val, &tb)) { return nullptr; } assert(typ.borrow() || val.borrow()); self->pimpl->may_switch_away(); try { // Both normalizing the error and the actual throw_greenlet // could throw PyErrOccurred. PyErrPieces err_pieces(typ.borrow(), val.borrow(), tb.borrow()); return throw_greenlet(self, err_pieces).relinquish_ownership(); } catch (const PyErrOccurred&) { return nullptr; } } static int green_bool(PyGreenlet* self) { return self->pimpl->active(); } /** * CAUTION: Allocates memory, may run GC and arbitrary Python code. */ static PyObject* green_getdict(PyGreenlet* self, void* UNUSED(context)) { if (self->dict == NULL) { self->dict = PyDict_New(); if (self->dict == NULL) { return NULL; } } Py_INCREF(self->dict); return self->dict; } static int green_setdict(PyGreenlet* self, PyObject* val, void* UNUSED(context)) { PyObject* tmp; if (val == NULL) { PyErr_SetString(PyExc_TypeError, "__dict__ may not be deleted"); return -1; } if (!PyDict_Check(val)) { PyErr_SetString(PyExc_TypeError, "__dict__ must be a dictionary"); return -1; } tmp = self->dict; Py_INCREF(val); self->dict = val; Py_XDECREF(tmp); return 0; } static bool _green_not_dead(BorrowedGreenlet self) { // XXX: Where else should we do this? // Probably on entry to most Python-facing functions? if (self->was_running_in_dead_thread()) { self->deactivate_and_free(); return false; } return self->active() || !self->started(); } static PyObject* green_getdead(BorrowedGreenlet self, void* UNUSED(context)) { if (_green_not_dead(self)) { Py_RETURN_FALSE; } else { Py_RETURN_TRUE; } } static PyObject* green_get_stack_saved(PyGreenlet* self, void* UNUSED(context)) { return PyLong_FromSsize_t(self->pimpl->stack_saved()); } static PyObject* green_getrun(BorrowedGreenlet self, void* UNUSED(context)) { try { OwnedObject result(self->run()); return result.relinquish_ownership(); } catch(const PyErrOccurred&) { return nullptr; } } static int green_setrun(BorrowedGreenlet self, BorrowedObject nrun, void* UNUSED(context)) { try { self->run(nrun); return 0; } catch(const PyErrOccurred&) { return -1; } } static PyObject* green_getparent(BorrowedGreenlet self, void* UNUSED(context)) { return self->parent().acquire_or_None(); } static int green_setparent(BorrowedGreenlet self, BorrowedObject nparent, void* UNUSED(context)) { try { self->parent(nparent); } catch(const PyErrOccurred&) { return -1; } return 0; } static PyObject* green_getcontext(const PyGreenlet* self, void* UNUSED(context)) { const Greenlet *const g = self->pimpl; try { OwnedObject result(g->context()); return result.relinquish_ownership(); } catch(const PyErrOccurred&) { return nullptr; } } static int green_setcontext(BorrowedGreenlet self, PyObject* nctx, void* UNUSED(context)) { try { self->context(nctx); return 0; } catch(const PyErrOccurred&) { return -1; } } static PyObject* green_getframe(BorrowedGreenlet self, void* UNUSED(context)) { const PythonState::OwnedFrame& top_frame = self->top_frame(); return top_frame.acquire_or_None(); } static PyObject* green_getstate(PyGreenlet* self) { PyErr_Format(PyExc_TypeError, "cannot serialize '%s' object", Py_TYPE(self)->tp_name); return nullptr; } static PyObject* green_repr(BorrowedGreenlet self) { /* Return a string like The handling of greenlets across threads is not super good. We mostly use the internal definitions of these terms, but they generally should make sense to users as well. */ PyObject* result; int never_started = !self->started() && !self->active(); const char* const tp_name = Py_TYPE(self)->tp_name; if (_green_not_dead(self)) { /* XXX: The otid= is almost useless because you can't correlate it to any thread identifier exposed to Python. We could use PyThreadState_GET()->thread_id, but we'd need to save that in the greenlet, or save the whole PyThreadState object itself. As it stands, its only useful for identifying greenlets from the same thread. */ const char* state_in_thread; if (self->was_running_in_dead_thread()) { // The thread it was running in is dead! // This can happen, especially at interpreter shut down. // It complicates debugging output because it may be // impossible to access the current thread state at that // time. Thus, don't access the current thread state. state_in_thread = " (thread exited)"; } else { state_in_thread = GET_THREAD_STATE().state().is_current(self) ? " current" : (self->started() ? " suspended" : ""); } result = PyUnicode_FromFormat( "<%s object at %p (otid=%p)%s%s%s%s>", tp_name, self.borrow_o(), self->thread_state(), state_in_thread, self->active() ? " active" : "", never_started ? " pending" : " started", self->main() ? " main" : "" ); } else { result = PyUnicode_FromFormat( "<%s object at %p (otid=%p) %sdead>", tp_name, self.borrow_o(), self->thread_state(), self->was_running_in_dead_thread() ? "(thread exited) " : "" ); } return result; } /***************************************************************************** * C interface * * These are exported using the CObject API */ extern "C" { static PyGreenlet* PyGreenlet_GetCurrent(void) { return GET_THREAD_STATE().state().get_current().relinquish_ownership(); } static int PyGreenlet_SetParent(PyGreenlet* g, PyGreenlet* nparent) { return green_setparent((PyGreenlet*)g, (PyObject*)nparent, NULL); } static PyGreenlet* PyGreenlet_New(PyObject* run, PyGreenlet* parent) { using greenlet::refs::NewDictReference; // In the past, we didn't use green_new and green_init, but that // was a maintenance issue because we duplicated code. This way is // much safer, but slightly slower. If that's a problem, we could // refactor green_init to separate argument parsing from initialization. OwnedGreenlet g = OwnedGreenlet::consuming(green_new(&PyGreenlet_Type, nullptr, nullptr)); if (!g) { return NULL; } try { NewDictReference kwargs; if (run) { kwargs.SetItem(mod_globs->str_run, run); } if (parent) { kwargs.SetItem("parent", (PyObject*)parent); } Require(green_init(g, mod_globs->empty_tuple, kwargs)); } catch (const PyErrOccurred&) { return nullptr; } return g.relinquish_ownership(); } static PyObject* PyGreenlet_Switch(PyGreenlet* self, PyObject* args, PyObject* kwargs) { if (!PyGreenlet_Check(self)) { PyErr_BadArgument(); return NULL; } if (args == NULL) { args = mod_globs->empty_tuple; } if (kwargs == NULL || !PyDict_Check(kwargs)) { kwargs = NULL; } return green_switch(self, args, kwargs); } static PyObject* PyGreenlet_Throw(PyGreenlet* self, PyObject* typ, PyObject* val, PyObject* tb) { if (!PyGreenlet_Check(self)) { PyErr_BadArgument(); return nullptr; } try { PyErrPieces err_pieces(typ, val, tb); return throw_greenlet(self, err_pieces).relinquish_ownership(); } catch (const PyErrOccurred&) { return nullptr; } } static int Extern_PyGreenlet_MAIN(PyGreenlet* self) { if (!PyGreenlet_Check(self)) { PyErr_BadArgument(); return -1; } return self->pimpl->main(); } static int Extern_PyGreenlet_ACTIVE(PyGreenlet* self) { if (!PyGreenlet_Check(self)) { PyErr_BadArgument(); return -1; } return self->pimpl->active(); } static int Extern_PyGreenlet_STARTED(PyGreenlet* self) { if (!PyGreenlet_Check(self)) { PyErr_BadArgument(); return -1; } return self->pimpl->started(); } static PyGreenlet* Extern_PyGreenlet_GET_PARENT(PyGreenlet* self) { if (!PyGreenlet_Check(self)) { PyErr_BadArgument(); return NULL; } // This can return NULL even if there is no exception return self->pimpl->parent().acquire(); } } // extern C. /** End C API ****************************************************************/ static PyMethodDef green_methods[] = { {"switch", reinterpret_cast(green_switch), METH_VARARGS | METH_KEYWORDS, green_switch_doc}, {"throw", (PyCFunction)green_throw, METH_VARARGS, green_throw_doc}, {"__getstate__", (PyCFunction)green_getstate, METH_NOARGS, NULL}, {NULL, NULL} /* sentinel */ }; static PyGetSetDef green_getsets[] = { /* name, getter, setter, doc, context pointer */ {"__dict__", (getter)green_getdict, (setter)green_setdict, /*XXX*/ NULL}, {"run", (getter)green_getrun, (setter)green_setrun, /*XXX*/ NULL}, {"parent", (getter)green_getparent, (setter)green_setparent, /*XXX*/ NULL}, {"gr_frame", (getter)green_getframe, NULL, /*XXX*/ NULL}, {"gr_context", (getter)green_getcontext, (setter)green_setcontext, /*XXX*/ NULL}, {"dead", (getter)green_getdead, NULL, /*XXX*/ NULL}, {"_stack_saved", (getter)green_get_stack_saved, NULL, /*XXX*/ NULL}, {NULL} }; static PyMemberDef green_members[] = { {NULL} }; static PyNumberMethods green_as_number = { NULL, /* nb_add */ NULL, /* nb_subtract */ NULL, /* nb_multiply */ NULL, /* nb_remainder */ NULL, /* nb_divmod */ NULL, /* nb_power */ NULL, /* nb_negative */ NULL, /* nb_positive */ NULL, /* nb_absolute */ (inquiry)green_bool, /* nb_bool */ }; PyTypeObject PyGreenlet_Type = { PyVarObject_HEAD_INIT(NULL, 0) "greenlet.greenlet", /* tp_name */ sizeof(PyGreenlet), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ (destructor)green_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)green_repr, /* tp_repr */ &green_as_number, /* tp_as _number*/ 0, /* tp_as _sequence*/ 0, /* tp_as _mapping*/ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer*/ G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "greenlet(run=None, parent=None) -> greenlet\n\n" "Creates a new greenlet object (without running it).\n\n" " - *run* -- The callable to invoke.\n" " - *parent* -- The parent greenlet. The default is the current " "greenlet.", /* tp_doc */ (traverseproc)green_traverse, /* tp_traverse */ (inquiry)green_clear, /* tp_clear */ 0, /* tp_richcompare */ offsetof(PyGreenlet, weakreflist), /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ green_methods, /* tp_methods */ green_members, /* tp_members */ green_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ offsetof(PyGreenlet, dict), /* tp_dictoffset */ (initproc)green_init, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ (newfunc)green_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ (inquiry)green_is_gc, /* tp_is_gc */ }; static PyObject* green_unswitchable_getforce(PyGreenlet* self, void* UNUSED(context)) { BrokenGreenlet* broken = dynamic_cast(self->pimpl); return PyBool_FromLong(broken->_force_switch_error); } static int green_unswitchable_setforce(PyGreenlet* self, BorrowedObject nforce, void* UNUSED(context)) { if (!nforce) { PyErr_SetString( PyExc_AttributeError, "Cannot delete force_switch_error" ); return -1; } BrokenGreenlet* broken = dynamic_cast(self->pimpl); int is_true = PyObject_IsTrue(nforce); if (is_true == -1) { return -1; } broken->_force_switch_error = is_true; return 0; } static PyObject* green_unswitchable_getforceslp(PyGreenlet* self, void* UNUSED(context)) { BrokenGreenlet* broken = dynamic_cast(self->pimpl); return PyBool_FromLong(broken->_force_slp_switch_error); } static int green_unswitchable_setforceslp(PyGreenlet* self, BorrowedObject nforce, void* UNUSED(context)) { if (!nforce) { PyErr_SetString( PyExc_AttributeError, "Cannot delete force_slp_switch_error" ); return -1; } BrokenGreenlet* broken = dynamic_cast(self->pimpl); int is_true = PyObject_IsTrue(nforce); if (is_true == -1) { return -1; } broken->_force_slp_switch_error = is_true; return 0; } static PyGetSetDef green_unswitchable_getsets[] = { /* name, getter, setter, doc, context pointer */ {"force_switch_error", (getter)green_unswitchable_getforce, (setter)green_unswitchable_setforce, /*XXX*/ NULL}, {"force_slp_switch_error", (getter)green_unswitchable_getforceslp, (setter)green_unswitchable_setforceslp, /*XXX*/ NULL}, {NULL} }; PyTypeObject PyGreenletUnswitchable_Type = { PyVarObject_HEAD_INIT(NULL, 0) "greenlet._greenlet.UnswitchableGreenlet", 0, /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ (destructor)green_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as _number*/ 0, /* tp_as _sequence*/ 0, /* tp_as _mapping*/ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer*/ G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Undocumented internal class", /* tp_doc */ (traverseproc)green_traverse, /* tp_traverse */ (inquiry)green_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ green_unswitchable_getsets, /* tp_getset */ &PyGreenlet_Type, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)green_init, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ (newfunc)green_unswitchable_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ (inquiry)green_is_gc, /* tp_is_gc */ }; PyDoc_STRVAR(mod_getcurrent_doc, "getcurrent() -> greenlet\n" "\n" "Returns the current greenlet (i.e. the one which called this " "function).\n"); static PyObject* mod_getcurrent(PyObject* UNUSED(module)) { return GET_THREAD_STATE().state().get_current().relinquish_ownership_o(); } PyDoc_STRVAR(mod_settrace_doc, "settrace(callback) -> object\n" "\n" "Sets a new tracing function and returns the previous one.\n"); static PyObject* mod_settrace(PyObject* UNUSED(module), PyObject* args) { PyArgParseParam tracefunc; if (!PyArg_ParseTuple(args, "O", &tracefunc)) { return NULL; } ThreadState& state = GET_THREAD_STATE(); OwnedObject previous = state.get_tracefunc(); if (!previous) { previous = Py_None; } state.set_tracefunc(tracefunc); return previous.relinquish_ownership(); } PyDoc_STRVAR(mod_gettrace_doc, "gettrace() -> object\n" "\n" "Returns the currently set tracing function, or None.\n"); static PyObject* mod_gettrace(PyObject* UNUSED(module)) { OwnedObject tracefunc = GET_THREAD_STATE().state().get_tracefunc(); if (!tracefunc) { tracefunc = Py_None; } return tracefunc.relinquish_ownership(); } PyDoc_STRVAR(mod_set_thread_local_doc, "set_thread_local(key, value) -> None\n" "\n" "Set a value in the current thread-local dictionary. Debugging only.\n"); static PyObject* mod_set_thread_local(PyObject* UNUSED(module), PyObject* args) { PyArgParseParam key; PyArgParseParam value; PyObject* result = NULL; if (PyArg_UnpackTuple(args, "set_thread_local", 2, 2, &key, &value)) { if(PyDict_SetItem( PyThreadState_GetDict(), // borrow key, value) == 0 ) { // success Py_INCREF(Py_None); result = Py_None; } } return result; } PyDoc_STRVAR(mod_get_pending_cleanup_count_doc, "get_pending_cleanup_count() -> Integer\n" "\n" "Get the number of greenlet cleanup operations pending. Testing only.\n"); static PyObject* mod_get_pending_cleanup_count(PyObject* UNUSED(module)) { LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); return PyLong_FromSize_t(mod_globs->thread_states_to_destroy.size()); } PyDoc_STRVAR(mod_get_total_main_greenlets_doc, "get_total_main_greenlets() -> Integer\n" "\n" "Quickly return the number of main greenlets that exist. Testing only.\n"); static PyObject* mod_get_total_main_greenlets(PyObject* UNUSED(module)) { return PyLong_FromSize_t(G_TOTAL_MAIN_GREENLETS); } PyDoc_STRVAR(mod_get_clocks_used_doing_optional_cleanup_doc, "get_clocks_used_doing_optional_cleanup() -> Integer\n" "\n" "Get the number of clock ticks the program has used doing optional " "greenlet cleanup.\n" "Beginning in greenlet 2.0, greenlet tries to find and dispose of greenlets\n" "that leaked after a thread exited. This requires invoking Python's garbage collector,\n" "which may have a performance cost proportional to the number of live objects.\n" "This function returns the amount of processor time\n" "greenlet has used to do this. In programs that run with very large amounts of live\n" "objects, this metric can be used to decide whether the cost of doing this cleanup\n" "is worth the memory leak being corrected. If not, you can disable the cleanup\n" "using ``enable_optional_cleanup(False)``.\n" "The units are arbitrary and can only be compared to themselves (similarly to ``time.clock()``);\n" "for example, to see how it scales with your heap. You can attempt to convert them into seconds\n" "by dividing by the value of CLOCKS_PER_SEC." "If cleanup has been disabled, returns None." "\n" "This is an implementation specific, provisional API. It may be changed or removed\n" "in the future.\n" ".. versionadded:: 2.0" ); static PyObject* mod_get_clocks_used_doing_optional_cleanup(PyObject* UNUSED(module)) { std::clock_t& clocks = ThreadState::clocks_used_doing_gc(); if (clocks == std::clock_t(-1)) { Py_RETURN_NONE; } // This might not actually work on some implementations; clock_t // is an opaque type. return PyLong_FromSsize_t(clocks); } PyDoc_STRVAR(mod_enable_optional_cleanup_doc, "mod_enable_optional_cleanup(bool) -> None\n" "\n" "Enable or disable optional cleanup operations.\n" "See ``get_clocks_used_doing_optional_cleanup()`` for details.\n" ); static PyObject* mod_enable_optional_cleanup(PyObject* UNUSED(module), PyObject* flag) { int is_true = PyObject_IsTrue(flag); if (is_true == -1) { return nullptr; } std::clock_t& clocks = ThreadState::clocks_used_doing_gc(); if (is_true) { // If we already have a value, we don't want to lose it. if (clocks == std::clock_t(-1)) { clocks = 0; } } else { clocks = std::clock_t(-1); } Py_RETURN_NONE; } #if !GREENLET_PY313 PyDoc_STRVAR(mod_get_tstate_trash_delete_nesting_doc, "get_tstate_trash_delete_nesting() -> Integer\n" "\n" "Return the 'trash can' nesting level. Testing only.\n"); static PyObject* mod_get_tstate_trash_delete_nesting(PyObject* UNUSED(module)) { PyThreadState* tstate = PyThreadState_GET(); #if GREENLET_PY312 return PyLong_FromLong(tstate->trash.delete_nesting); #else return PyLong_FromLong(tstate->trash_delete_nesting); #endif } #endif static PyMethodDef GreenMethods[] = { {"getcurrent", (PyCFunction)mod_getcurrent, METH_NOARGS, mod_getcurrent_doc}, {"settrace", (PyCFunction)mod_settrace, METH_VARARGS, mod_settrace_doc}, {"gettrace", (PyCFunction)mod_gettrace, METH_NOARGS, mod_gettrace_doc}, {"set_thread_local", (PyCFunction)mod_set_thread_local, METH_VARARGS, mod_set_thread_local_doc}, {"get_pending_cleanup_count", (PyCFunction)mod_get_pending_cleanup_count, METH_NOARGS, mod_get_pending_cleanup_count_doc}, {"get_total_main_greenlets", (PyCFunction)mod_get_total_main_greenlets, METH_NOARGS, mod_get_total_main_greenlets_doc}, {"get_clocks_used_doing_optional_cleanup", (PyCFunction)mod_get_clocks_used_doing_optional_cleanup, METH_NOARGS, mod_get_clocks_used_doing_optional_cleanup_doc}, {"enable_optional_cleanup", (PyCFunction)mod_enable_optional_cleanup, METH_O, mod_enable_optional_cleanup_doc}, #if !GREENLET_PY313 {"get_tstate_trash_delete_nesting", (PyCFunction)mod_get_tstate_trash_delete_nesting, METH_NOARGS, mod_get_tstate_trash_delete_nesting_doc}, #endif {NULL, NULL} /* Sentinel */ }; static const char* const copy_on_greentype[] = { "getcurrent", "error", "GreenletExit", "settrace", "gettrace", NULL }; static struct PyModuleDef greenlet_module_def = { PyModuleDef_HEAD_INIT, "greenlet._greenlet", NULL, -1, GreenMethods, }; static PyObject* greenlet_internal_mod_init() noexcept { static void* _PyGreenlet_API[PyGreenlet_API_pointers]; try { CreatedModule m(greenlet_module_def); Require(PyType_Ready(&PyGreenlet_Type)); Require(PyType_Ready(&PyGreenletUnswitchable_Type)); mod_globs = new greenlet::GreenletGlobals; ThreadState::init(); m.PyAddObject("greenlet", PyGreenlet_Type); m.PyAddObject("UnswitchableGreenlet", PyGreenletUnswitchable_Type); m.PyAddObject("error", mod_globs->PyExc_GreenletError); m.PyAddObject("GreenletExit", mod_globs->PyExc_GreenletExit); m.PyAddObject("GREENLET_USE_GC", 1); m.PyAddObject("GREENLET_USE_TRACING", 1); m.PyAddObject("GREENLET_USE_CONTEXT_VARS", 1L); m.PyAddObject("GREENLET_USE_STANDARD_THREADING", 1L); OwnedObject clocks_per_sec = OwnedObject::consuming(PyLong_FromSsize_t(CLOCKS_PER_SEC)); m.PyAddObject("CLOCKS_PER_SEC", clocks_per_sec); /* also publish module-level data as attributes of the greentype. */ // XXX: This is weird, and enables a strange pattern of // confusing the class greenlet with the module greenlet; with // the exception of (possibly) ``getcurrent()``, this // shouldn't be encouraged so don't add new items here. for (const char* const* p = copy_on_greentype; *p; p++) { OwnedObject o = m.PyRequireAttr(*p); PyDict_SetItemString(PyGreenlet_Type.tp_dict, *p, o.borrow()); } /* * Expose C API */ /* types */ _PyGreenlet_API[PyGreenlet_Type_NUM] = (void*)&PyGreenlet_Type; /* exceptions */ _PyGreenlet_API[PyExc_GreenletError_NUM] = (void*)mod_globs->PyExc_GreenletError; _PyGreenlet_API[PyExc_GreenletExit_NUM] = (void*)mod_globs->PyExc_GreenletExit; /* methods */ _PyGreenlet_API[PyGreenlet_New_NUM] = (void*)PyGreenlet_New; _PyGreenlet_API[PyGreenlet_GetCurrent_NUM] = (void*)PyGreenlet_GetCurrent; _PyGreenlet_API[PyGreenlet_Throw_NUM] = (void*)PyGreenlet_Throw; _PyGreenlet_API[PyGreenlet_Switch_NUM] = (void*)PyGreenlet_Switch; _PyGreenlet_API[PyGreenlet_SetParent_NUM] = (void*)PyGreenlet_SetParent; /* Previously macros, but now need to be functions externally. */ _PyGreenlet_API[PyGreenlet_MAIN_NUM] = (void*)Extern_PyGreenlet_MAIN; _PyGreenlet_API[PyGreenlet_STARTED_NUM] = (void*)Extern_PyGreenlet_STARTED; _PyGreenlet_API[PyGreenlet_ACTIVE_NUM] = (void*)Extern_PyGreenlet_ACTIVE; _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM] = (void*)Extern_PyGreenlet_GET_PARENT; /* XXX: Note that our module name is ``greenlet._greenlet``, but for backwards compatibility with existing C code, we need the _C_API to be directly in greenlet. */ const NewReference c_api_object(Require( PyCapsule_New( (void*)_PyGreenlet_API, "greenlet._C_API", NULL))); m.PyAddObject("_C_API", c_api_object); assert(c_api_object.REFCNT() == 2); // cerr << "Sizes:" // << "\n\tGreenlet : " << sizeof(Greenlet) // << "\n\tUserGreenlet : " << sizeof(UserGreenlet) // << "\n\tMainGreenlet : " << sizeof(MainGreenlet) // << "\n\tExceptionState : " << sizeof(greenlet::ExceptionState) // << "\n\tPythonState : " << sizeof(greenlet::PythonState) // << "\n\tStackState : " << sizeof(greenlet::StackState) // << "\n\tSwitchingArgs : " << sizeof(greenlet::SwitchingArgs) // << "\n\tOwnedObject : " << sizeof(greenlet::refs::OwnedObject) // << "\n\tBorrowedObject : " << sizeof(greenlet::refs::BorrowedObject) // << "\n\tPyGreenlet : " << sizeof(PyGreenlet) // << endl; return m.borrow(); // But really it's the main reference. } catch (const LockInitError& e) { PyErr_SetString(PyExc_MemoryError, e.what()); return NULL; } catch (const PyErrOccurred&) { return NULL; } } extern "C" { PyMODINIT_FUNC PyInit__greenlet(void) { return greenlet_internal_mod_init(); } }; // extern C #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif greenlet-3.1.0/src/greenlet/greenlet.h000066400000000000000000000112231467007427000177000ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ /* Greenlet object interface */ #ifndef Py_GREENLETOBJECT_H #define Py_GREENLETOBJECT_H #include #ifdef __cplusplus extern "C" { #endif /* This is deprecated and undocumented. It does not change. */ #define GREENLET_VERSION "1.0.0" #ifndef GREENLET_MODULE #define implementation_ptr_t void* #endif typedef struct _greenlet { PyObject_HEAD PyObject* weakreflist; PyObject* dict; implementation_ptr_t pimpl; } PyGreenlet; #define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) /* C API functions */ /* Total number of symbols that are exported */ #define PyGreenlet_API_pointers 12 #define PyGreenlet_Type_NUM 0 #define PyExc_GreenletError_NUM 1 #define PyExc_GreenletExit_NUM 2 #define PyGreenlet_New_NUM 3 #define PyGreenlet_GetCurrent_NUM 4 #define PyGreenlet_Throw_NUM 5 #define PyGreenlet_Switch_NUM 6 #define PyGreenlet_SetParent_NUM 7 #define PyGreenlet_MAIN_NUM 8 #define PyGreenlet_STARTED_NUM 9 #define PyGreenlet_ACTIVE_NUM 10 #define PyGreenlet_GET_PARENT_NUM 11 #ifndef GREENLET_MODULE /* This section is used by modules that uses the greenlet C API */ static void** _PyGreenlet_API = NULL; # define PyGreenlet_Type \ (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) # define PyExc_GreenletError \ ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) # define PyExc_GreenletExit \ ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) /* * PyGreenlet_New(PyObject *args) * * greenlet.greenlet(run, parent=None) */ # define PyGreenlet_New \ (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ _PyGreenlet_API[PyGreenlet_New_NUM]) /* * PyGreenlet_GetCurrent(void) * * greenlet.getcurrent() */ # define PyGreenlet_GetCurrent \ (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) /* * PyGreenlet_Throw( * PyGreenlet *greenlet, * PyObject *typ, * PyObject *val, * PyObject *tb) * * g.throw(...) */ # define PyGreenlet_Throw \ (*(PyObject * (*)(PyGreenlet * self, \ PyObject * typ, \ PyObject * val, \ PyObject * tb)) \ _PyGreenlet_API[PyGreenlet_Throw_NUM]) /* * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) * * g.switch(*args, **kwargs) */ # define PyGreenlet_Switch \ (*(PyObject * \ (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ _PyGreenlet_API[PyGreenlet_Switch_NUM]) /* * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) * * g.parent = new_parent */ # define PyGreenlet_SetParent \ (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ _PyGreenlet_API[PyGreenlet_SetParent_NUM]) /* * PyGreenlet_GetParent(PyObject* greenlet) * * return greenlet.parent; * * This could return NULL even if there is no exception active. * If it does not return NULL, you are responsible for decrementing the * reference count. */ # define PyGreenlet_GetParent \ (*(PyGreenlet* (*)(PyGreenlet*)) \ _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) /* * deprecated, undocumented alias. */ # define PyGreenlet_GET_PARENT PyGreenlet_GetParent # define PyGreenlet_MAIN \ (*(int (*)(PyGreenlet*)) \ _PyGreenlet_API[PyGreenlet_MAIN_NUM]) # define PyGreenlet_STARTED \ (*(int (*)(PyGreenlet*)) \ _PyGreenlet_API[PyGreenlet_STARTED_NUM]) # define PyGreenlet_ACTIVE \ (*(int (*)(PyGreenlet*)) \ _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) /* Macro that imports greenlet and initializes C API */ /* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we keep the older definition to be sure older code that might have a copy of the header still works. */ # define PyGreenlet_Import() \ { \ _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ } #endif /* GREENLET_MODULE */ #ifdef __cplusplus } #endif #endif /* !Py_GREENLETOBJECT_H */ greenlet-3.1.0/src/greenlet/greenlet_allocator.hpp000066400000000000000000000030561467007427000223050ustar00rootroot00000000000000#ifndef GREENLET_ALLOCATOR_HPP #define GREENLET_ALLOCATOR_HPP #define PY_SSIZE_T_CLEAN #include #include #include "greenlet_compiler_compat.hpp" namespace greenlet { // This allocator is stateless; all instances are identical. // It can *ONLY* be used when we're sure we're holding the GIL // (Python's allocators require the GIL). template struct PythonAllocator : public std::allocator { PythonAllocator(const PythonAllocator& UNUSED(other)) : std::allocator() { } PythonAllocator(const std::allocator other) : std::allocator(other) {} template PythonAllocator(const std::allocator& other) : std::allocator(other) { } PythonAllocator() : std::allocator() {} T* allocate(size_t number_objects, const void* UNUSED(hint)=0) { void* p; if (number_objects == 1) p = PyObject_Malloc(sizeof(T)); else p = PyMem_Malloc(sizeof(T) * number_objects); return static_cast(p); } void deallocate(T* t, size_t n) { void* p = t; if (n == 1) { PyObject_Free(p); } else PyMem_Free(p); } // This member is deprecated in C++17 and removed in C++20 template< class U > struct rebind { typedef PythonAllocator other; }; }; } #endif greenlet-3.1.0/src/greenlet/greenlet_compiler_compat.hpp000066400000000000000000000100541467007427000234760ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ #ifndef GREENLET_COMPILER_COMPAT_HPP #define GREENLET_COMPILER_COMPAT_HPP /** * Definitions to aid with compatibility with different compilers. * * .. caution:: Use extreme care with noexcept. * Some compilers and runtimes, specifically gcc/libgcc/libstdc++ on * Linux, implement stack unwinding by throwing an uncatchable * exception, one that specifically does not appear to be an active * exception to the rest of the runtime. If this happens while we're in a noexcept function, * we have violated our dynamic exception contract, and so the runtime * will call std::terminate(), which kills the process with the * unhelpful message "terminate called without an active exception". * * This has happened in this scenario: A background thread is running * a greenlet that has made a native call and released the GIL. * Meanwhile, the main thread finishes and starts shutting down the * interpreter. When the background thread is scheduled again and * attempts to obtain the GIL, it notices that the interpreter is * exiting and calls ``pthread_exit()``. This in turn starts to unwind * the stack by throwing that exception. But we had the ``PyCall`` * functions annotated as noexcept, so the runtime terminated us. * * #2 0x00007fab26fec2b7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6 * #3 0x00007fab26febb3c in __gxx_personality_v0 () from /lib/x86_64-linux-gnu/libstdc++.so.6 * #4 0x00007fab26f34de6 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1 * #6 0x00007fab276a34c6 in __GI___pthread_unwind at ./nptl/unwind.c:130 * #7 0x00007fab2769bd3a in __do_cancel () at ../sysdeps/nptl/pthreadP.h:280 * #8 __GI___pthread_exit (value=value@entry=0x0) at ./nptl/pthread_exit.c:36 * #9 0x000000000052e567 in PyThread_exit_thread () at ../Python/thread_pthread.h:370 * #10 0x00000000004d60b5 in take_gil at ../Python/ceval_gil.h:224 * #11 0x00000000004d65f9 in PyEval_RestoreThread at ../Python/ceval.c:467 * #12 0x000000000060cce3 in setipaddr at ../Modules/socketmodule.c:1203 * #13 0x00000000006101cd in socket_gethostbyname */ #include # if defined(__clang__) # define G_FP_TMPL_STATIC static # else // GCC has no problem allowing static function pointers, but emits // tons of warnings about "whose type uses the anonymous namespace [-Wsubobject-linkage]" # define G_FP_TMPL_STATIC # endif # define G_NO_COPIES_OF_CLS(Cls) private: \ Cls(const Cls& other) = delete; \ Cls& operator=(const Cls& other) = delete # define G_NO_ASSIGNMENT_OF_CLS(Cls) private: \ Cls& operator=(const Cls& other) = delete # define G_NO_COPY_CONSTRUCTOR_OF_CLS(Cls) private: \ Cls(const Cls& other) = delete; // CAUTION: MSVC is stupidly picky: // // "The compiler ignores, without warning, any __declspec keywords // placed after * or & and in front of the variable identifier in a // declaration." // (https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160) // // So pointer return types must be handled differently (because of the // trailing *), or you get inscrutable compiler warnings like "error // C2059: syntax error: ''" // // In C++ 11, there is a standard syntax for attributes, and // GCC defines an attribute to use with this: [[gnu:noinline]]. // In the future, this is expected to become standard. #if defined(__GNUC__) || defined(__clang__) /* We used to check for GCC 4+ or 3.4+, but those compilers are laughably out of date. Just assume they support it. */ # define GREENLET_NOINLINE(name) __attribute__((noinline)) name # define GREENLET_NOINLINE_P(rtype, name) rtype __attribute__((noinline)) name # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) #elif defined(_MSC_VER) /* We used to check for && (_MSC_VER >= 1300) but that's also out of date. */ # define GREENLET_NOINLINE(name) __declspec(noinline) name # define GREENLET_NOINLINE_P(rtype, name) __declspec(noinline) rtype name # define UNUSED(x) UNUSED_ ## x #endif #if defined(_MSC_VER) # define G_NOEXCEPT_WIN32 noexcept #else # define G_NOEXCEPT_WIN32 #endif #endif greenlet-3.1.0/src/greenlet/greenlet_cpython_add_pending.hpp000066400000000000000000000136331467007427000243270ustar00rootroot00000000000000#ifndef GREENLET_CPYTHON_ADD_PENDING_HPP #define GREENLET_CPYTHON_ADD_PENDING_HPP #if (PY_VERSION_HEX >= 0x30800A0 && PY_VERSION_HEX < 0x3090000) && !(defined(_WIN32) || defined(WIN32)) // XXX: From Python 3.8a3 [1] up until Python 3.9a6 [2][3], // ``Py_AddPendingCall`` would try to produce a Python exception if // the interpreter was in the beginning of shutting down when this // function is called. However, ``Py_AddPendingCall`` doesn't require // the GIL, and we are absolutely not holding it when we make that // call. That means that trying to create the Python exception is // using the C API in an undefined state; here the C API detects this // and aborts the process with an error ("Fatal Python error: Python // memory allocator called without holding the GIL": Add -> // PyErr_SetString -> PyUnicode_New -> PyObject_Malloc). This arises // (obviously) in multi-threaded programs and happens if one thread is // exiting and cleaning up its thread-local data while the other // thread is trying to shut down the interpreter. A crash on shutdown // is still a crash and could result in data loss (e.g., daemon // threads are still running, pending signal handlers may be present, // buffers may not be flushed, there may be __del__ that need run, // etc), so we have to work around it. // // Of course, we can (and do) check for whether the interpreter is // shutting down before calling ``Py_AddPendingCall``, but that's a // race condition since we don't hold the GIL, and so we may not // actually get the right answer. Plus, ``Py_FinalizeEx`` actually // calls ``_Py_FinishPendingCalls`` (which sets the pending->finishing // flag, which is used to gate creating the exceptioen) *before* // publishing any other data that would let us detect the shutdown // (such as runtime->finalizing). So that point is moot. // // Our solution for those versions is to inline the same code, without // the problematic bit that sets the exception. Unfortunately, all of // the structure definitions are private/opaque, *and* we can't // actually count on being able to include their definitions from // ``internal/pycore_*``, because on some platforms those header files // are incomplete (i.e., on macOS with macports 3.8, the includes are // fine, but on Ubuntu jammy with 3.8 from ppa:deadsnakes or GitHub // Actions 3.8 (I think it's Ubuntu 18.04), they con't be used; at // least, I couldn't get them to work). So we need to define the // structures and _PyRuntime data member ourself. Yet more // unfortunately, _PyRuntime won't link on Windows, so we can only do // this on other platforms. // // [1] https://github.com/python/cpython/commit/842a2f07f2f08a935ef470bfdaeef40f87490cfc // [2] https://github.com/python/cpython/commit/cfc3c2f8b34d3864717ab584c5b6c260014ba55a // [3] https://github.com/python/cpython/issues/81308 # define GREENLET_BROKEN_PY_ADD_PENDING 1 // When defining these structures, the important thing is to get // binary compatibility, i.e., structure layout. For that, we only // need to define fields up to the ones we use; after that they're // irrelevant UNLESS the structure is included in another structure // *before* the structure we're interested in --- in that case, it // must be complete. Ellipsis indicate elided trailing members. // Pointer types are changed to void* to keep from having to define // more structures. // From "internal/pycore_atomic.h" // There are several different definitions of this, including the // plain ``int`` version, a ``volatile int`` and an ``_Atomic int`` // I don't think any of those change the size/layout. typedef struct _Py_atomic_int { volatile int _value; } _Py_atomic_int; // This needs too much infrastructure, so we just do a regular store. #define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) \ (ATOMIC_VAL)->_value = NEW_VAL // From "internal/pycore_pymem.h" #define NUM_GENERATIONS 3 struct gc_generation { PyGC_Head head; // We already have this defined. int threshold; int count; }; struct gc_generation_stats { Py_ssize_t collections; Py_ssize_t collected; Py_ssize_t uncollectable; }; struct _gc_runtime_state { void *trash_delete_later; int trash_delete_nesting; int enabled; int debug; struct gc_generation generations[NUM_GENERATIONS]; void *generation0; struct gc_generation permanent_generation; struct gc_generation_stats generation_stats[NUM_GENERATIONS]; int collecting; void *garbage; void *callbacks; Py_ssize_t long_lived_total; Py_ssize_t long_lived_pending; }; // From "internal/pycore_pystate.h" struct _pending_calls { int finishing; PyThread_type_lock lock; _Py_atomic_int calls_to_do; int async_exc; #define NPENDINGCALLS 32 struct { int (*func)(void *); void *arg; } calls[NPENDINGCALLS]; int first; int last; }; struct _ceval_runtime_state { int recursion_limit; int tracing_possible; _Py_atomic_int eval_breaker; _Py_atomic_int gil_drop_request; struct _pending_calls pending; // ... }; typedef struct pyruntimestate { int preinitializing; int preinitialized; int core_initialized; int initialized; void *finalizing; struct pyinterpreters { PyThread_type_lock mutex; void *head; void *main; int64_t next_id; } interpreters; // XXX Remove this field once we have a tp_* slot. struct _xidregistry { PyThread_type_lock mutex; void *head; } xidregistry; unsigned long main_thread; #define NEXITFUNCS 32 void (*exitfuncs[NEXITFUNCS])(void); int nexitfuncs; struct _gc_runtime_state gc; struct _ceval_runtime_state ceval; // ... } _PyRuntimeState; #define SIGNAL_PENDING_CALLS(ceval) \ do { \ _Py_atomic_store_relaxed(&(ceval)->pending.calls_to_do, 1); \ _Py_atomic_store_relaxed(&(ceval)->eval_breaker, 1); \ } while (0) extern _PyRuntimeState _PyRuntime; #else # define GREENLET_BROKEN_PY_ADD_PENDING 0 #endif #endif greenlet-3.1.0/src/greenlet/greenlet_cpython_compat.hpp000066400000000000000000000075741467007427000233650ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ #ifndef GREENLET_CPYTHON_COMPAT_H #define GREENLET_CPYTHON_COMPAT_H /** * Helpers for compatibility with multiple versions of CPython. */ #define PY_SSIZE_T_CLEAN #include "Python.h" #if PY_VERSION_HEX >= 0x30A00B1 # define GREENLET_PY310 1 #else # define GREENLET_PY310 0 #endif /* Python 3.10 beta 1 changed tstate->use_tracing to a nested cframe member. See https://github.com/python/cpython/pull/25276 We have to save and restore this as well. Python 3.13 removed PyThreadState.cframe (GH-108035). */ #if GREENLET_PY310 && PY_VERSION_HEX < 0x30D0000 # define GREENLET_USE_CFRAME 1 #else # define GREENLET_USE_CFRAME 0 #endif #if PY_VERSION_HEX >= 0x30B00A4 /* Greenlet won't compile on anything older than Python 3.11 alpha 4 (see https://bugs.python.org/issue46090). Summary of breaking internal changes: - Python 3.11 alpha 1 changed how frame objects are represented internally. - https://github.com/python/cpython/pull/30122 - Python 3.11 alpha 3 changed how recursion limits are stored. - https://github.com/python/cpython/pull/29524 - Python 3.11 alpha 4 changed how exception state is stored. It also includes a change to help greenlet save and restore the interpreter frame "data stack". - https://github.com/python/cpython/pull/30122 - https://github.com/python/cpython/pull/30234 */ # define GREENLET_PY311 1 #else # define GREENLET_PY311 0 #endif #if PY_VERSION_HEX >= 0x30C0000 # define GREENLET_PY312 1 #else # define GREENLET_PY312 0 #endif #if PY_VERSION_HEX >= 0x30D0000 # define GREENLET_PY313 1 #else # define GREENLET_PY313 0 #endif #ifndef Py_SET_REFCNT /* Py_REFCNT and Py_SIZE macros are converted to functions https://bugs.python.org/issue39573 */ # define Py_SET_REFCNT(obj, refcnt) Py_REFCNT(obj) = (refcnt) #endif #ifndef _Py_DEC_REFTOTAL /* _Py_DEC_REFTOTAL macro has been removed from Python 3.9 by: https://github.com/python/cpython/commit/49932fec62c616ec88da52642339d83ae719e924 The symbol we use to replace it was removed by at least 3.12. */ # ifdef Py_REF_DEBUG # if GREENLET_PY312 # define _Py_DEC_REFTOTAL # else # define _Py_DEC_REFTOTAL _Py_RefTotal-- # endif # else # define _Py_DEC_REFTOTAL # endif #endif // Define these flags like Cython does if we're on an old version. #ifndef Py_TPFLAGS_CHECKTYPES #define Py_TPFLAGS_CHECKTYPES 0 #endif #ifndef Py_TPFLAGS_HAVE_INDEX #define Py_TPFLAGS_HAVE_INDEX 0 #endif #ifndef Py_TPFLAGS_HAVE_NEWBUFFER #define Py_TPFLAGS_HAVE_NEWBUFFER 0 #endif #ifndef Py_TPFLAGS_HAVE_VERSION_TAG #define Py_TPFLAGS_HAVE_VERSION_TAG 0 #endif #define G_TPFLAGS_DEFAULT Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_HAVE_NEWBUFFER | Py_TPFLAGS_HAVE_GC #if PY_VERSION_HEX < 0x03090000 // The official version only became available in 3.9 # define PyObject_GC_IsTracked(o) _PyObject_GC_IS_TRACKED(o) #endif // bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2 #if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) static inline void PyThreadState_EnterTracing(PyThreadState *tstate) { tstate->tracing++; #if PY_VERSION_HEX >= 0x030A00A1 tstate->cframe->use_tracing = 0; #else tstate->use_tracing = 0; #endif } #endif // bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2 #if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) { tstate->tracing--; int use_tracing = (tstate->c_tracefunc != NULL || tstate->c_profilefunc != NULL); #if PY_VERSION_HEX >= 0x030A00A1 tstate->cframe->use_tracing = use_tracing; #else tstate->use_tracing = use_tracing; #endif } #endif #if !defined(Py_C_RECURSION_LIMIT) && defined(C_RECURSION_LIMIT) # define Py_C_RECURSION_LIMIT C_RECURSION_LIMIT #endif #endif /* GREENLET_CPYTHON_COMPAT_H */ greenlet-3.1.0/src/greenlet/greenlet_exceptions.hpp000066400000000000000000000071431467007427000225070ustar00rootroot00000000000000#ifndef GREENLET_EXCEPTIONS_HPP #define GREENLET_EXCEPTIONS_HPP #define PY_SSIZE_T_CLEAN #include #include #include #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wunused-function" #endif namespace greenlet { class PyErrOccurred : public std::runtime_error { public: // CAUTION: In debug builds, may run arbitrary Python code. static const PyErrOccurred from_current() { assert(PyErr_Occurred()); #ifndef NDEBUG // This is not exception safe, and // not necessarily safe in general (what if it switches?) // But we only do this in debug mode, where we are in // tight control of what exceptions are getting raised and // can prevent those issues. // You can't call PyObject_Str with a pending exception. PyObject* typ; PyObject* val; PyObject* tb; PyErr_Fetch(&typ, &val, &tb); PyObject* typs = PyObject_Str(typ); PyObject* vals = PyObject_Str(val ? val : typ); const char* typ_msg = PyUnicode_AsUTF8(typs); const char* val_msg = PyUnicode_AsUTF8(vals); PyErr_Restore(typ, val, tb); std::string msg(typ_msg); msg += ": "; msg += val_msg; PyErrOccurred ex(msg); Py_XDECREF(typs); Py_XDECREF(vals); return ex; #else return PyErrOccurred(); #endif } PyErrOccurred() : std::runtime_error("") { assert(PyErr_Occurred()); } PyErrOccurred(const std::string& msg) : std::runtime_error(msg) { assert(PyErr_Occurred()); } PyErrOccurred(PyObject* exc_kind, const char* const msg) : std::runtime_error(msg) { PyErr_SetString(exc_kind, msg); } PyErrOccurred(PyObject* exc_kind, const std::string msg) : std::runtime_error(msg) { // This copies the c_str, so we don't have any lifetime // issues to worry about. PyErr_SetString(exc_kind, msg.c_str()); } }; class TypeError : public PyErrOccurred { public: TypeError(const char* const what) : PyErrOccurred(PyExc_TypeError, what) { } TypeError(const std::string what) : PyErrOccurred(PyExc_TypeError, what) { } }; class ValueError : public PyErrOccurred { public: ValueError(const char* const what) : PyErrOccurred(PyExc_ValueError, what) { } }; class AttributeError : public PyErrOccurred { public: AttributeError(const char* const what) : PyErrOccurred(PyExc_AttributeError, what) { } }; /** * Calls `Py_FatalError` when constructed, so you can't actually * throw this. It just makes static analysis easier. */ class PyFatalError : public std::runtime_error { public: PyFatalError(const char* const msg) : std::runtime_error(msg) { Py_FatalError(msg); } }; static inline PyObject* Require(PyObject* p, const std::string& msg="") { if (!p) { throw PyErrOccurred(msg); } return p; }; static inline void Require(const int retval) { if (retval < 0) { throw PyErrOccurred(); } }; }; #ifdef __clang__ # pragma clang diagnostic pop #endif #endif greenlet-3.1.0/src/greenlet/greenlet_greenlet.hpp000066400000000000000000000662501467007427000221370ustar00rootroot00000000000000#ifndef GREENLET_GREENLET_HPP #define GREENLET_GREENLET_HPP /* * Declarations of the core data structures. */ #define PY_SSIZE_T_CLEAN #include #include "greenlet_compiler_compat.hpp" #include "greenlet_refs.hpp" #include "greenlet_cpython_compat.hpp" #include "greenlet_allocator.hpp" using greenlet::refs::OwnedObject; using greenlet::refs::OwnedGreenlet; using greenlet::refs::OwnedMainGreenlet; using greenlet::refs::BorrowedGreenlet; #if PY_VERSION_HEX < 0x30B00A6 # define _PyCFrame CFrame # define _PyInterpreterFrame _interpreter_frame #endif #if GREENLET_PY312 # define Py_BUILD_CORE # include "internal/pycore_frame.h" #endif // XXX: TODO: Work to remove all virtual functions // for speed of calling and size of objects (no vtable). // One pattern is the Curiously Recurring Template namespace greenlet { class ExceptionState { private: G_NO_COPIES_OF_CLS(ExceptionState); // Even though these are borrowed objects, we actually own // them, when they're not null. // XXX: Express that in the API. private: _PyErr_StackItem* exc_info; _PyErr_StackItem exc_state; public: ExceptionState(); void operator<<(const PyThreadState *const tstate) noexcept; void operator>>(PyThreadState* tstate) noexcept; void clear() noexcept; int tp_traverse(visitproc visit, void* arg) noexcept; void tp_clear() noexcept; }; template void operator<<(const PyThreadState *const tstate, T& exc); class PythonStateContext { protected: greenlet::refs::OwnedContext _context; public: inline const greenlet::refs::OwnedContext& context() const { return this->_context; } inline greenlet::refs::OwnedContext& context() { return this->_context; } inline void tp_clear() { this->_context.CLEAR(); } template inline static PyObject* context(T* tstate) { return tstate->context; } template inline static void context(T* tstate, PyObject* new_context) { tstate->context = new_context; tstate->context_ver++; } }; class SwitchingArgs; class PythonState : public PythonStateContext { public: typedef greenlet::refs::OwnedReference OwnedFrame; private: G_NO_COPIES_OF_CLS(PythonState); // We own this if we're suspended (although currently we don't // tp_traverse into it; that's a TODO). If we're running, it's // empty. If we get deallocated and *still* have a frame, it // won't be reachable from the place that normally decref's // it, so we need to do it (hence owning it). OwnedFrame _top_frame; #if GREENLET_USE_CFRAME _PyCFrame* cframe; int use_tracing; #endif #if GREENLET_PY312 int py_recursion_depth; int c_recursion_depth; #else int recursion_depth; #endif #if GREENLET_PY313 PyObject *delete_later; #else int trash_delete_nesting; #endif #if GREENLET_PY311 _PyInterpreterFrame* current_frame; _PyStackChunk* datastack_chunk; PyObject** datastack_top; PyObject** datastack_limit; #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. const OwnedFrame& top_frame() const noexcept; inline void operator<<(const PyThreadState *const tstate) noexcept; inline void operator>>(PyThreadState* tstate) noexcept; void clear() noexcept; int tp_traverse(visitproc visit, void* arg, bool visit_top_frame) noexcept; void tp_clear(bool own_top_frame) noexcept; void set_initial_state(const PyThreadState* const tstate) noexcept; #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; }; class StackState { // By having only plain C (POD) members, no virtual functions // or bases, we get a trivial assignment operator generated // for us. However, that's not safe since we do manage memory. // So we declare an assignment operator that only works if we // don't have any memory allocated. (We don't use // std::shared_ptr for reference counting just to keep this // object small) private: char* _stack_start; char* stack_stop; char* stack_copy; intptr_t _stack_saved; StackState* stack_prev; inline int copy_stack_to_heap_up_to(const char* const stop) noexcept; inline void free_stack_copy() noexcept; public: /** * Creates a started, but inactive, state, using *current* * as the previous. */ StackState(void* mark, StackState& current); /** * Creates an inactive, unstarted, state. */ StackState(); ~StackState(); StackState(const StackState& other); StackState& operator=(const StackState& other); inline void copy_heap_to_stack(const StackState& current) noexcept; inline int copy_stack_to_heap(char* const stackref, const StackState& current) noexcept; inline bool started() const noexcept; inline bool main() const noexcept; inline bool active() const noexcept; inline void set_active() noexcept; inline void set_inactive() noexcept; inline intptr_t stack_saved() const noexcept; inline char* stack_start() const noexcept; static inline StackState make_main() noexcept; #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); #endif class SwitchingArgs { private: G_NO_ASSIGNMENT_OF_CLS(SwitchingArgs); // If args and kwargs are both false (NULL), this is a *throw*, not a // switch. PyErr_... must have been called already. OwnedObject _args; OwnedObject _kwargs; public: SwitchingArgs() {} SwitchingArgs(const OwnedObject& args, const OwnedObject& kwargs) : _args(args), _kwargs(kwargs) {} SwitchingArgs(const SwitchingArgs& other) : _args(other._args), _kwargs(other._kwargs) {} const OwnedObject& args() { return this->_args; } const OwnedObject& kwargs() { return this->_kwargs; } /** * Moves ownership from the argument to this object. */ SwitchingArgs& operator<<=(SwitchingArgs& other) { if (this != &other) { this->_args = other._args; this->_kwargs = other._kwargs; other.CLEAR(); } return *this; } /** * Acquires ownership of the argument (consumes the reference). */ SwitchingArgs& operator<<=(PyObject* args) { this->_args = OwnedObject::consuming(args); this->_kwargs.CLEAR(); return *this; } /** * Acquires ownership of the argument. * * Sets the args to be the given value; clears the kwargs. */ SwitchingArgs& operator<<=(OwnedObject& args) { assert(&args != &this->_args); this->_args = args; this->_kwargs.CLEAR(); args.CLEAR(); return *this; } explicit operator bool() const noexcept { return this->_args || this->_kwargs; } inline void CLEAR() { this->_args.CLEAR(); this->_kwargs.CLEAR(); } const std::string as_str() const noexcept { return PyUnicode_AsUTF8( OwnedObject::consuming( PyUnicode_FromFormat( "SwitchingArgs(args=%R, kwargs=%R)", this->_args.borrow(), this->_kwargs.borrow() ) ).borrow() ); } }; class ThreadState; class UserGreenlet; class MainGreenlet; class Greenlet { private: G_NO_COPIES_OF_CLS(Greenlet); private: // XXX: Work to remove these. friend class ThreadState; friend class UserGreenlet; friend class MainGreenlet; protected: ExceptionState exception_state; SwitchingArgs switch_args; StackState stack_state; PythonState python_state; Greenlet(PyGreenlet* p, const StackState& initial_state); public: Greenlet(PyGreenlet* p); virtual ~Greenlet(); const OwnedObject context() const; // You MUST call this _very_ early in the switching process to // prepare anything that may need prepared. This might perform // garbage collections or otherwise run arbitrary Python code. // // One specific use of it is for Python 3.11+, preventing // running arbitrary code at unsafe times. See // PythonState::may_switch_away(). inline void may_switch_away() { this->python_state.may_switch_away(); } inline void context(refs::BorrowedObject new_context); inline SwitchingArgs& args() { return this->switch_args; } virtual const refs::BorrowedMainGreenlet main_greenlet() const = 0; inline intptr_t stack_saved() const noexcept { return this->stack_state.stack_saved(); } // This is used by the macro SLP_SAVE_STATE to compute the // difference in stack sizes. It might be nice to handle the // computation ourself, but the type of the result // varies by platform, so doing it in the macro is the // simplest way. inline const char* stack_start() const noexcept { return this->stack_state.stack_start(); } virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state); virtual OwnedObject g_switch() = 0; /** * Force the greenlet to appear dead. Used when it's not * possible to throw an exception into a greenlet anymore. * * This losses access to the thread state and the main greenlet. */ virtual void murder_in_place(); /** * Called when somebody notices we were running in a dead * thread to allow cleaning up resources (because we can't * raise GreenletExit into it anymore). * This is very similar to ``murder_in_place()``, except that * it DOES NOT lose the main greenlet or thread state. */ inline void deactivate_and_free(); // Called when some thread wants to deallocate a greenlet // object. // The thread may or may not be the same thread the greenlet // was running in. // The thread state will be null if the thread the greenlet // 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; inline bool is_currently_running_in_some_thread() const; virtual bool belongs_to_thread(const ThreadState* state) const; inline bool started() const { return this->stack_state.started(); } inline bool active() const { return this->stack_state.active(); } inline bool main() const { return this->stack_state.main(); } virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const = 0; virtual const OwnedGreenlet parent() const = 0; virtual void parent(const refs::BorrowedObject new_parent) = 0; inline const PythonState::OwnedFrame& top_frame() { return this->python_state.top_frame(); } virtual const OwnedObject& run() const = 0; virtual void run(const refs::BorrowedObject nrun) = 0; virtual int tp_traverse(visitproc visit, void* arg); virtual int tp_clear(); // Return the thread state that the greenlet is running in, or // null if the greenlet is not running or the thread is known // to have exited. virtual ThreadState* thread_state() const noexcept = 0; // Return true if the greenlet is known to have been running // (active) in a thread that has now exited. virtual bool was_running_in_dead_thread() const noexcept = 0; // Return a borrowed greenlet that is the Python object // this object represents. virtual BorrowedGreenlet self() const noexcept = 0; // For testing. If this returns true, we should pretend that // slp_switch() failed. virtual bool force_slp_switch_error() const noexcept; protected: inline void release_args(); // The functions that must not be inlined are declared virtual. // We also mark them as protected, not private, so that the // compiler is forced to call them through a function pointer. // (A sufficiently smart compiler could directly call a private // virtual function since it can never be overridden in a // subclass). // Also TODO: Switch away from integer error codes and to enums, // or throw exceptions when possible. struct switchstack_result_t { int status; Greenlet* the_new_current_greenlet; OwnedGreenlet origin_greenlet; switchstack_result_t() : status(0), the_new_current_greenlet(nullptr) {} switchstack_result_t(int err) : status(err), the_new_current_greenlet(nullptr) {} switchstack_result_t(int err, Greenlet* state, OwnedGreenlet& origin) : status(err), the_new_current_greenlet(state), origin_greenlet(origin) { } switchstack_result_t(int err, Greenlet* state, const BorrowedGreenlet& origin) : status(err), the_new_current_greenlet(state), origin_greenlet(origin) { } switchstack_result_t(const switchstack_result_t& other) : status(other.status), the_new_current_greenlet(other.the_new_current_greenlet), origin_greenlet(other.origin_greenlet) {} switchstack_result_t& operator=(const switchstack_result_t& other) { this->status = other.status; this->the_new_current_greenlet = other.the_new_current_greenlet; this->origin_greenlet = other.origin_greenlet; return *this; } }; OwnedObject on_switchstack_or_initialstub_failure( Greenlet* target, const switchstack_result_t& err, const bool target_was_me=false, const bool was_initial_stub=false); // Returns the previous greenlet we just switched away from. virtual OwnedGreenlet g_switchstack_success() noexcept; // Check the preconditions for switching to this greenlet; if they // aren't met, throws PyErrOccurred. Most callers will want to // catch this and clear the arguments inline void check_switch_allowed() const; class GreenletStartedWhileInPython : public std::runtime_error { public: GreenletStartedWhileInPython() : std::runtime_error("") {} }; protected: /** Perform a stack switch into this greenlet. This temporarily sets the global variable ``switching_thread_state`` to this greenlet; as soon as the call to ``slp_switch`` completes, this is reset to NULL. Consequently, this depends on the GIL. TODO: Adopt the stackman model and pass ``slp_switch`` a callback function and context pointer; this eliminates the need for global variables altogether. Because the stack switch happens in this function, this function can't use its own stack (local) variables, set before the switch, and then accessed after the switch. Further, you con't even access ``g_thread_state_global`` before and after the switch from the global variable. Because it is thread local some compilers cache it in a register/on the stack, notably new versions of MSVC; this breaks with strange crashes sometime later, because writing to anything in ``g_thread_state_global`` after the switch is actually writing to random memory. For this reason, we call a non-inlined function to finish the operation. (XXX: The ``/GT`` MSVC compiler argument probably fixes that.) It is very important that stack switch is 'atomic', i.e. no calls into other Python code allowed (except very few that are safe), because global variables are very fragile. (This should no longer be the case with thread-local variables.) */ // Made virtual to facilitate subclassing UserGreenlet for testing. virtual switchstack_result_t g_switchstack(void); class TracingGuard { private: PyThreadState* tstate; public: TracingGuard() : tstate(PyThreadState_GET()) { PyThreadState_EnterTracing(this->tstate); } ~TracingGuard() { PyThreadState_LeaveTracing(this->tstate); this->tstate = nullptr; } inline void CallTraceFunction(const OwnedObject& tracefunc, const greenlet::refs::ImmortalEventName& event, const BorrowedGreenlet& origin, const BorrowedGreenlet& target) { // TODO: This calls tracefunc(event, (origin, target)). Add a shortcut // function for that that's specialized to avoid the Py_BuildValue // string parsing, or start with just using "ON" format with PyTuple_Pack(2, // origin, target). That seems like what the N format is meant // for. // XXX: Why does event not automatically cast back to a PyObject? // It tries to call the "deleted constructor ImmortalEventName // const" instead. assert(tracefunc); assert(event); assert(origin); assert(target); greenlet::refs::NewReference retval( PyObject_CallFunction( tracefunc.borrow(), "O(OO)", event.borrow(), origin.borrow(), target.borrow() )); if (!retval) { throw PyErrOccurred::from_current(); } } }; static void g_calltrace(const OwnedObject& tracefunc, const greenlet::refs::ImmortalEventName& event, const greenlet::refs::BorrowedGreenlet& origin, const BorrowedGreenlet& target); private: OwnedObject g_switch_finish(const switchstack_result_t& err); }; class UserGreenlet : public Greenlet { private: static greenlet::PythonAllocator allocator; BorrowedGreenlet _self; OwnedMainGreenlet _main_greenlet; OwnedObject _run_callable; OwnedGreenlet _parent; public: static void* operator new(size_t UNUSED(count)); static void operator delete(void* ptr); UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent); virtual ~UserGreenlet(); virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const; virtual bool was_running_in_dead_thread() const noexcept; virtual ThreadState* thread_state() const noexcept; virtual OwnedObject g_switch(); virtual const OwnedObject& run() const { if (this->started() || !this->_run_callable) { throw AttributeError("run"); } return this->_run_callable; } virtual void run(const refs::BorrowedObject nrun); virtual const OwnedGreenlet parent() const; virtual void parent(const refs::BorrowedObject new_parent); virtual const refs::BorrowedMainGreenlet main_greenlet() const; virtual BorrowedGreenlet self() const noexcept; virtual void murder_in_place(); virtual bool belongs_to_thread(const ThreadState* state) const; virtual int tp_traverse(visitproc visit, void* arg); virtual int tp_clear(); class ParentIsCurrentGuard { private: OwnedGreenlet oldparent; UserGreenlet* greenlet; G_NO_COPIES_OF_CLS(ParentIsCurrentGuard); public: ParentIsCurrentGuard(UserGreenlet* p, const ThreadState& thread_state); ~ParentIsCurrentGuard(); }; virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state); protected: virtual switchstack_result_t g_initialstub(void* mark); private: // This function isn't meant to return. // This accepts raw pointers and the ownership of them at the // same time. The caller should use ``inner_bootstrap(origin.relinquish_ownership())``. void inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run); }; class BrokenGreenlet : public UserGreenlet { private: static greenlet::PythonAllocator allocator; public: bool _force_switch_error = false; bool _force_slp_switch_error = false; static void* operator new(size_t UNUSED(count)); static void operator delete(void* ptr); BrokenGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent) : UserGreenlet(p, the_parent) {} virtual ~BrokenGreenlet() {} virtual switchstack_result_t g_switchstack(void); virtual bool force_slp_switch_error() const noexcept; }; class MainGreenlet : public Greenlet { private: static greenlet::PythonAllocator allocator; refs::BorrowedMainGreenlet _self; ThreadState* _thread_state; G_NO_COPIES_OF_CLS(MainGreenlet); public: static void* operator new(size_t UNUSED(count)); static void operator delete(void* ptr); MainGreenlet(refs::BorrowedMainGreenlet::PyType*, ThreadState*); virtual ~MainGreenlet(); virtual const OwnedObject& run() const; virtual void run(const refs::BorrowedObject nrun); virtual const OwnedGreenlet parent() const; virtual void parent(const refs::BorrowedObject new_parent); virtual const refs::BorrowedMainGreenlet main_greenlet() const; virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const; virtual bool was_running_in_dead_thread() const noexcept; virtual ThreadState* thread_state() const noexcept; void thread_state(ThreadState*) noexcept; virtual OwnedObject g_switch(); virtual BorrowedGreenlet self() const noexcept; virtual int tp_traverse(visitproc visit, void* arg); }; // Instantiate one on the stack to save the GC state, // and then disable GC. When it goes out of scope, GC will be // restored to its original state. Sadly, these APIs are only // available on 3.10+; luckily, we only need them on 3.11+. #if GREENLET_PY310 class GCDisabledGuard { private: int was_enabled = 0; public: GCDisabledGuard() : was_enabled(PyGC_IsEnabled()) { PyGC_Disable(); } ~GCDisabledGuard() { if (this->was_enabled) { PyGC_Enable(); } } }; #endif OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept; //TODO: Greenlet::g_switch() should call this automatically on its //return value. As it is, the module code is calling it. static inline OwnedObject single_result(const OwnedObject& results) { if (results && PyTuple_Check(results.borrow()) && PyTuple_GET_SIZE(results.borrow()) == 1) { PyObject* result = PyTuple_GET_ITEM(results.borrow(), 0); assert(result); return OwnedObject::owning(result); } return results; } static OwnedObject g_handle_exit(const OwnedObject& greenlet_result); template void operator<<(const PyThreadState *const lhs, T& rhs) { rhs.operator<<(lhs); } } // namespace greenlet ; #endif greenlet-3.1.0/src/greenlet/greenlet_internal.hpp000066400000000000000000000052171467007427000221420ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ #ifndef GREENLET_INTERNAL_H #define GREENLET_INTERNAL_H #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wunused-function" # pragma clang diagnostic ignored "-Wmissing-field-initializers" # pragma clang diagnostic ignored "-Wunused-variable" #endif /** * Implementation helpers. * * C++ templates and inline functions should go here. */ #define PY_SSIZE_T_CLEAN #include "greenlet_compiler_compat.hpp" #include "greenlet_cpython_compat.hpp" #include "greenlet_exceptions.hpp" #include "greenlet_greenlet.hpp" #include "greenlet_allocator.hpp" #include #include #define GREENLET_MODULE struct _greenlet; typedef struct _greenlet PyGreenlet; namespace greenlet { class ThreadState; }; #define implementation_ptr_t greenlet::Greenlet* #include "greenlet.h" G_FP_TMPL_STATIC inline void greenlet::refs::MainGreenletExactChecker(void *p) { if (!p) { return; } // We control the class of the main greenlet exactly. if (Py_TYPE(p) != &PyGreenlet_Type) { std::string err("MainGreenlet: Expected exactly a greenlet, not a "); err += Py_TYPE(p)->tp_name; throw greenlet::TypeError(err); } // Greenlets from dead threads no longer respond to main() with a // true value; so in that case we need to perform an additional // check. Greenlet* g = ((PyGreenlet*)p)->pimpl; if (g->main()) { return; } if (!dynamic_cast(g)) { std::string err("MainGreenlet: Expected exactly a main greenlet, not a "); err += Py_TYPE(p)->tp_name; throw greenlet::TypeError(err); } } template inline greenlet::Greenlet* greenlet::refs::_OwnedGreenlet::operator->() const noexcept { return reinterpret_cast(this->p)->pimpl; } template inline greenlet::Greenlet* greenlet::refs::_BorrowedGreenlet::operator->() const noexcept { return reinterpret_cast(this->p)->pimpl; } #include #include extern PyTypeObject PyGreenlet_Type; /** * Forward declarations needed in multiple files. */ static PyGreenlet* green_create_main(greenlet::ThreadState*); static PyObject* green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs); static int green_is_gc(BorrowedGreenlet self); #ifdef __clang__ # pragma clang diagnostic pop #endif #endif // Local Variables: // flycheck-clang-include-path: ("../../include" "/opt/local/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10") // End: greenlet-3.1.0/src/greenlet/greenlet_refs.hpp000066400000000000000000001021101467007427000212530ustar00rootroot00000000000000#ifndef GREENLET_REFS_HPP #define GREENLET_REFS_HPP #define PY_SSIZE_T_CLEAN #include //#include "greenlet_internal.hpp" #include "greenlet_compiler_compat.hpp" #include "greenlet_cpython_compat.hpp" #include "greenlet_exceptions.hpp" struct _greenlet; struct _PyMainGreenlet; typedef struct _greenlet PyGreenlet; extern PyTypeObject PyGreenlet_Type; #ifdef GREENLET_USE_STDIO #include using std::cerr; using std::endl; #endif namespace greenlet { class Greenlet; namespace refs { // Type checkers throw a TypeError if the argument is not // null, and isn't of the required Python type. // (We can't use most of the defined type checkers // like PyList_Check, etc, directly, because they are // implemented as macros.) typedef void (*TypeChecker)(void*); G_FP_TMPL_STATIC inline void NoOpChecker(void*) { return; } G_FP_TMPL_STATIC inline void GreenletChecker(void *p) { if (!p) { return; } PyTypeObject* typ = Py_TYPE(p); // fast, common path. (PyObject_TypeCheck is a macro or // static inline function, and it also does a // direct comparison of the type pointers, but its fast // path only handles one type) if (typ == &PyGreenlet_Type) { return; } if (!PyObject_TypeCheck(p, &PyGreenlet_Type)) { std::string err("GreenletChecker: Expected any type of greenlet, not "); err += Py_TYPE(p)->tp_name; throw TypeError(err); } } G_FP_TMPL_STATIC inline void MainGreenletExactChecker(void *p); template class PyObjectPointer; template class OwnedReference; template class BorrowedReference; typedef BorrowedReference BorrowedObject; typedef OwnedReference OwnedObject; class ImmortalObject; class ImmortalString; template class _OwnedGreenlet; typedef _OwnedGreenlet OwnedGreenlet; typedef _OwnedGreenlet OwnedMainGreenlet; template class _BorrowedGreenlet; typedef _BorrowedGreenlet BorrowedGreenlet; G_FP_TMPL_STATIC inline void ContextExactChecker(void *p) { if (!p) { return; } if (!PyContext_CheckExact(p)) { throw TypeError( "greenlet context must be a contextvars.Context or None" ); } } typedef OwnedReference OwnedContext; } } namespace greenlet { namespace refs { // A set of classes to make reference counting rules in python // code explicit. // // Rules of use: // (1) Functions returning a new reference that the caller of the // function is expected to dispose of should return a // ``OwnedObject`` object. This object automatically releases its // reference when it goes out of scope. It works like a ``std::shared_ptr`` // and can be copied or used as a function parameter (but don't do // that). Note that constructing a ``OwnedObject`` from a // PyObject* steals the reference. // (2) Parameters to functions should be either a // ``OwnedObject&``, or, more generally, a ``PyObjectPointer&``. // If the function needs to create its own new reference, it can // do so by copying to a local ``OwnedObject``. // (3) Functions returning an existing pointer that is NOT // incref'd, and which the caller MUST NOT decref, // should return a ``BorrowedObject``. // // For a class with a single pointer member, whose constructor // does nothing but copy a pointer parameter into the member, and // which can then be converted back to the pointer type, compilers // generate code that's the same as just passing the pointer. // That is, func(BorrowedObject x) called like ``PyObject* p = // ...; f(p)`` has 0 overhead. Similarly, they "unpack" to the // pointer type with 0 overhead. // // If there are no virtual functions, no complex inheritance (maybe?) and // no destructor, these can be directly used as parameters in // Python callbacks like tp_init: the layout is the same as a // single pointer. Only subclasses with trivial constructors that // do nothing but set the single pointer member are safe to use // that way. // This is the base class for things that can be done with a // PyObject pointer. It assumes nothing about memory management. // NOTE: Nothing is virtual, so subclasses shouldn't add new // storage fields or try to override these methods. template class PyObjectPointer { public: typedef T PyType; protected: T* p; public: explicit PyObjectPointer(T* it=nullptr) : p(it) { TC(p); } // We don't allow automatic casting to PyObject* at this // level, because then we could be passed to Py_DECREF/INCREF, // but we want nothing to do with memory management. If you // know better, then you can use the get() method, like on a // std::shared_ptr. Except we name it borrow() to clarify that // if this is a reference-tracked object, the pointer you get // back will go away when the object does. // TODO: This should probably not exist here, but be moved // down to relevant sub-types. inline T* borrow() const noexcept { return this->p; } PyObject* borrow_o() const noexcept { return reinterpret_cast(this->p); } inline T* operator->() const noexcept { return this->p; } bool is_None() const noexcept { return this->p == Py_None; } inline PyObject* acquire_or_None() const noexcept { PyObject* result = this->p ? reinterpret_cast(this->p) : Py_None; Py_INCREF(result); return result; } explicit operator bool() const noexcept { return p != nullptr; } inline Py_ssize_t REFCNT() const noexcept { return p ? Py_REFCNT(p) : -42; } inline PyTypeObject* TYPE() const noexcept { return p ? Py_TYPE(p) : nullptr; } inline OwnedObject PyStr() const noexcept; inline const std::string as_str() const noexcept; inline OwnedObject PyGetAttr(const ImmortalObject& name) const noexcept; inline OwnedObject PyRequireAttr(const char* const name) const; inline OwnedObject PyRequireAttr(const ImmortalString& name) const; inline OwnedObject PyCall(const BorrowedObject& arg) const; inline OwnedObject PyCall(PyGreenlet* arg) const ; inline OwnedObject PyCall(PyObject* arg) const ; // PyObject_Call(this, args, kwargs); inline OwnedObject PyCall(const BorrowedObject args, const BorrowedObject kwargs) const; inline OwnedObject PyCall(const OwnedObject& args, const OwnedObject& kwargs) const; protected: void _set_raw_pointer(void* t) { TC(t); p = reinterpret_cast(t); } void* _get_raw_pointer() const { return p; } }; #ifdef GREENLET_USE_STDIO template std::ostream& operator<<(std::ostream& os, const PyObjectPointer& s) { const std::type_info& t = typeid(s); os << t.name() << "(addr=" << s.borrow() << ", refcnt=" << s.REFCNT() << ", value=" << s.as_str() << ")"; return os; } #endif template inline bool operator==(const PyObjectPointer& lhs, const void* const rhs) noexcept { return lhs.borrow_o() == rhs; } template inline bool operator==(const PyObjectPointer& lhs, const PyObjectPointer& rhs) noexcept { return lhs.borrow_o() == rhs.borrow_o(); } template inline bool operator!=(const PyObjectPointer& lhs, const PyObjectPointer& rhs) noexcept { return lhs.borrow_o() != rhs.borrow_o(); } template class OwnedReference : public PyObjectPointer { private: friend class OwnedList; protected: explicit OwnedReference(T* it) : PyObjectPointer(it) { } public: // Constructors static OwnedReference consuming(PyObject* p) { return OwnedReference(reinterpret_cast(p)); } static OwnedReference owning(T* p) { OwnedReference result(p); Py_XINCREF(result.p); return result; } OwnedReference() : PyObjectPointer(nullptr) {} explicit OwnedReference(const PyObjectPointer<>& other) : PyObjectPointer(nullptr) { T* op = other.borrow(); TC(op); this->p = other.borrow(); Py_XINCREF(this->p); } // It would be good to make use of the C++11 distinction // between move and copy operations, e.g., constructing from a // pointer should be a move operation. // In the common case of ``OwnedObject x = Py_SomeFunction()``, // the call to the copy constructor will be elided completely. OwnedReference(const OwnedReference& other) : PyObjectPointer(other.p) { Py_XINCREF(this->p); } static OwnedReference None() { Py_INCREF(Py_None); return OwnedReference(Py_None); } // We can assign from exactly our type without any extra checking OwnedReference& operator=(const OwnedReference& other) { Py_XINCREF(other.p); const T* tmp = this->p; this->p = other.p; Py_XDECREF(tmp); return *this; } OwnedReference& operator=(const BorrowedReference other) { return this->operator=(other.borrow()); } OwnedReference& operator=(T* const other) { TC(other); Py_XINCREF(other); T* tmp = this->p; this->p = other; Py_XDECREF(tmp); return *this; } // We can assign from an arbitrary reference type // if it passes our check. template OwnedReference& operator=(const OwnedReference& other) { X* op = other.borrow(); TC(op); return this->operator=(reinterpret_cast(op)); } inline void steal(T* other) { assert(this->p == nullptr); TC(other); this->p = other; } T* relinquish_ownership() { T* result = this->p; this->p = nullptr; return result; } T* acquire() const { // Return a new reference. // TODO: This may go away when we have reference objects // throughout the code. Py_XINCREF(this->p); return this->p; } // Nothing else declares a destructor, we're the leaf, so we // should be able to get away without virtual. ~OwnedReference() { Py_CLEAR(this->p); } void CLEAR() { Py_CLEAR(this->p); assert(this->p == nullptr); } }; static inline void operator<<=(PyObject*& target, OwnedObject& o) { target = o.relinquish_ownership(); } class NewReference : public OwnedObject { private: G_NO_COPIES_OF_CLS(NewReference); public: // Consumes the reference. Only use this // for API return values. NewReference(PyObject* it) : OwnedObject(it) { } }; class NewDictReference : public NewReference { private: G_NO_COPIES_OF_CLS(NewDictReference); public: NewDictReference() : NewReference(PyDict_New()) { if (!this->p) { throw PyErrOccurred(); } } void SetItem(const char* const key, PyObject* value) { Require(PyDict_SetItemString(this->p, key, value)); } void SetItem(const PyObjectPointer<>& key, PyObject* value) { Require(PyDict_SetItem(this->p, key.borrow_o(), value)); } }; template class _OwnedGreenlet: public OwnedReference { private: protected: _OwnedGreenlet(T* it) : OwnedReference(it) {} public: _OwnedGreenlet() : OwnedReference() {} _OwnedGreenlet(const _OwnedGreenlet& other) : OwnedReference(other) { } _OwnedGreenlet(OwnedMainGreenlet& other) : OwnedReference(reinterpret_cast(other.acquire())) { } _OwnedGreenlet(const BorrowedGreenlet& other); // Steals a reference. static _OwnedGreenlet consuming(PyGreenlet* it) { return _OwnedGreenlet(reinterpret_cast(it)); } inline _OwnedGreenlet& operator=(const OwnedGreenlet& other) { return this->operator=(other.borrow()); } inline _OwnedGreenlet& operator=(const BorrowedGreenlet& other); _OwnedGreenlet& operator=(const OwnedMainGreenlet& other) { PyGreenlet* owned = other.acquire(); Py_XDECREF(this->p); this->p = reinterpret_cast(owned); return *this; } _OwnedGreenlet& operator=(T* const other) { OwnedReference::operator=(other); return *this; } T* relinquish_ownership() { T* result = this->p; this->p = nullptr; return result; } PyObject* relinquish_ownership_o() { return reinterpret_cast(relinquish_ownership()); } inline Greenlet* operator->() const noexcept; inline operator Greenlet*() const noexcept; }; template class BorrowedReference : public PyObjectPointer { public: // Allow implicit creation from PyObject* pointers as we // transition to using these classes. Also allow automatic // conversion to PyObject* for passing to C API calls and even // for Py_INCREF/DECREF, because we ourselves do no memory management. BorrowedReference(T* it) : PyObjectPointer(it) {} BorrowedReference(const PyObjectPointer& ref) : PyObjectPointer(ref.borrow()) {} BorrowedReference() : PyObjectPointer(nullptr) {} operator T*() const { return this->p; } }; typedef BorrowedReference BorrowedObject; //typedef BorrowedReference BorrowedGreenlet; template class _BorrowedGreenlet : public BorrowedReference { public: _BorrowedGreenlet() : BorrowedReference(nullptr) {} _BorrowedGreenlet(T* it) : BorrowedReference(it) {} _BorrowedGreenlet(const BorrowedObject& it); _BorrowedGreenlet(const OwnedGreenlet& it) : BorrowedReference(it.borrow()) {} _BorrowedGreenlet& operator=(const BorrowedObject& other); // We get one of these for PyGreenlet, but one for PyObject // is handy as well operator PyObject*() const { return reinterpret_cast(this->p); } inline Greenlet* operator->() const noexcept; inline operator Greenlet*() const noexcept; }; typedef _BorrowedGreenlet BorrowedGreenlet; template _OwnedGreenlet::_OwnedGreenlet(const BorrowedGreenlet& other) : OwnedReference(reinterpret_cast(other.borrow())) { Py_XINCREF(this->p); } class BorrowedMainGreenlet : public _BorrowedGreenlet { public: BorrowedMainGreenlet(const OwnedMainGreenlet& it) : _BorrowedGreenlet(it.borrow()) {} BorrowedMainGreenlet(PyGreenlet* it=nullptr) : _BorrowedGreenlet(it) {} }; template _OwnedGreenlet& _OwnedGreenlet::operator=(const BorrowedGreenlet& other) { return this->operator=(other.borrow()); } class ImmortalObject : public PyObjectPointer<> { private: G_NO_ASSIGNMENT_OF_CLS(ImmortalObject); public: explicit ImmortalObject(PyObject* it) : PyObjectPointer<>(it) { } ImmortalObject(const ImmortalObject& other) : PyObjectPointer<>(other.p) { } /** * Become the new owner of the object. Does not change the * reference count. */ ImmortalObject& operator=(PyObject* it) { assert(this->p == nullptr); this->p = it; return *this; } static ImmortalObject consuming(PyObject* it) { return ImmortalObject(it); } inline operator PyObject*() const { return this->p; } }; class ImmortalString : public ImmortalObject { private: G_NO_COPIES_OF_CLS(ImmortalString); const char* str; public: ImmortalString(const char* const str) : ImmortalObject(str ? Require(PyUnicode_InternFromString(str)) : nullptr) { this->str = str; } inline ImmortalString& operator=(const char* const str) { if (!this->p) { this->p = Require(PyUnicode_InternFromString(str)); this->str = str; } else { assert(this->str == str); } return *this; } inline operator std::string() const { return this->str; } }; class ImmortalEventName : public ImmortalString { private: G_NO_COPIES_OF_CLS(ImmortalEventName); public: ImmortalEventName(const char* const str) : ImmortalString(str) {} }; class ImmortalException : public ImmortalObject { private: G_NO_COPIES_OF_CLS(ImmortalException); public: ImmortalException(const char* const name, PyObject* base=nullptr) : ImmortalObject(name // Python 2.7 isn't const correct ? Require(PyErr_NewException((char*)name, base, nullptr)) : nullptr) {} inline bool PyExceptionMatches() const { return PyErr_ExceptionMatches(this->p) > 0; } }; template inline OwnedObject PyObjectPointer::PyStr() const noexcept { if (!this->p) { return OwnedObject(); } return OwnedObject::consuming(PyObject_Str(reinterpret_cast(this->p))); } template inline const std::string PyObjectPointer::as_str() const noexcept { // NOTE: This is not Python exception safe. if (this->p) { // The Python APIs return a cached char* value that's only valid // as long as the original object stays around, and we're // about to (probably) toss it. Hence the copy to std::string. OwnedObject py_str = this->PyStr(); if (!py_str) { return "(nil)"; } return PyUnicode_AsUTF8(py_str.borrow()); } return "(nil)"; } template inline OwnedObject PyObjectPointer::PyGetAttr(const ImmortalObject& name) const noexcept { assert(this->p); return OwnedObject::consuming(PyObject_GetAttr(reinterpret_cast(this->p), name)); } template inline OwnedObject PyObjectPointer::PyRequireAttr(const char* const name) const { assert(this->p); return OwnedObject::consuming(Require(PyObject_GetAttrString(this->p, name), name)); } template inline OwnedObject PyObjectPointer::PyRequireAttr(const ImmortalString& name) const { assert(this->p); return OwnedObject::consuming(Require( PyObject_GetAttr( reinterpret_cast(this->p), name ), name )); } template inline OwnedObject PyObjectPointer::PyCall(const BorrowedObject& arg) const { return this->PyCall(arg.borrow()); } template inline OwnedObject PyObjectPointer::PyCall(PyGreenlet* arg) const { return this->PyCall(reinterpret_cast(arg)); } template inline OwnedObject PyObjectPointer::PyCall(PyObject* arg) const { assert(this->p); return OwnedObject::consuming(PyObject_CallFunctionObjArgs(this->p, arg, NULL)); } template inline OwnedObject PyObjectPointer::PyCall(const BorrowedObject args, const BorrowedObject kwargs) const { assert(this->p); return OwnedObject::consuming(PyObject_Call(this->p, args, kwargs)); } template inline OwnedObject PyObjectPointer::PyCall(const OwnedObject& args, const OwnedObject& kwargs) const { assert(this->p); return OwnedObject::consuming(PyObject_Call(this->p, args.borrow(), kwargs.borrow())); } G_FP_TMPL_STATIC inline void ListChecker(void * p) { if (!p) { return; } if (!PyList_Check(p)) { throw TypeError("Expected a list"); } } class OwnedList : public OwnedReference { private: G_NO_ASSIGNMENT_OF_CLS(OwnedList); public: // TODO: Would like to use move. explicit OwnedList(const OwnedObject& other) : OwnedReference(other) { } OwnedList& operator=(const OwnedObject& other) { if (other && PyList_Check(other.p)) { // Valid list. Own a new reference to it, discard the // reference to what we did own. PyObject* new_ptr = other.p; Py_INCREF(new_ptr); Py_XDECREF(this->p); this->p = new_ptr; } else { // Either the other object was NULL (an error) or it // wasn't a list. Either way, we're now invalidated. Py_XDECREF(this->p); this->p = nullptr; } return *this; } inline bool empty() const { return PyList_GET_SIZE(p) == 0; } inline Py_ssize_t size() const { return PyList_GET_SIZE(p); } inline BorrowedObject at(const Py_ssize_t index) const { return PyList_GET_ITEM(p, index); } inline void clear() { PyList_SetSlice(p, 0, PyList_GET_SIZE(p), NULL); } }; // Use this to represent the module object used at module init // time. // This could either be a borrowed (Py2) or new (Py3) reference; // either way, we don't want to do any memory management // on it here, Python itself will handle that. // XXX: Actually, that's not quite right. On Python 3, if an // exception occurs before we return to the interpreter, this will // leak; but all previous versions also had that problem. class CreatedModule : public PyObjectPointer<> { private: G_NO_COPIES_OF_CLS(CreatedModule); public: CreatedModule(PyModuleDef& mod_def) : PyObjectPointer<>( Require(PyModule_Create(&mod_def))) { } // PyAddObject(): Add a reference to the object to the module. // On return, the reference count of the object is unchanged. // // The docs warn that PyModule_AddObject only steals the // reference on success, so if it fails after we've incref'd // or allocated, we're responsible for the decref. void PyAddObject(const char* name, const long new_bool) { OwnedObject p = OwnedObject::consuming(Require(PyBool_FromLong(new_bool))); this->PyAddObject(name, p); } void PyAddObject(const char* name, const OwnedObject& new_object) { // The caller already owns a reference they will decref // when their variable goes out of scope, we still need to // incref/decref. this->PyAddObject(name, new_object.borrow()); } void PyAddObject(const char* name, const ImmortalObject& new_object) { this->PyAddObject(name, new_object.borrow()); } void PyAddObject(const char* name, PyTypeObject& type) { this->PyAddObject(name, reinterpret_cast(&type)); } void PyAddObject(const char* name, PyObject* new_object) { Py_INCREF(new_object); try { Require(PyModule_AddObject(this->p, name, new_object)); } catch (const PyErrOccurred&) { Py_DECREF(p); throw; } } }; class PyErrFetchParam : public PyObjectPointer<> { // Not an owned object, because we can't be initialized with // one, and we only sometimes acquire ownership. private: G_NO_COPIES_OF_CLS(PyErrFetchParam); public: // To allow declaring these and passing them to // PyErr_Fetch we implement the empty constructor, // and the address operator. PyErrFetchParam() : PyObjectPointer<>(nullptr) { } PyObject** operator&() { return &this->p; } // This allows us to pass one directly without the &, // BUT it has higher precedence than the bool operator // if it's not explicit. operator PyObject**() { return &this->p; } // We don't want to be able to pass these to Py_DECREF and // such so we don't have the implicit PyObject* conversion. inline PyObject* relinquish_ownership() { PyObject* result = this->p; this->p = nullptr; return result; } ~PyErrFetchParam() { Py_XDECREF(p); } }; class OwnedErrPiece : public OwnedObject { private: public: // Unlike OwnedObject, this increments the refcount. OwnedErrPiece(PyObject* p=nullptr) : OwnedObject(p) { this->acquire(); } PyObject** operator&() { return &this->p; } inline operator PyObject*() const { return this->p; } operator PyTypeObject*() const { return reinterpret_cast(this->p); } }; class PyErrPieces { private: OwnedErrPiece type; OwnedErrPiece instance; OwnedErrPiece traceback; bool restored; public: // Takes new references; if we're destroyed before // restoring the error, we drop the references. PyErrPieces(PyObject* t, PyObject* v, PyObject* tb) : type(t), instance(v), traceback(tb), restored(0) { this->normalize(); } PyErrPieces() : restored(0) { // PyErr_Fetch transfers ownership to us, so // we don't actually need to INCREF; but we *do* // need to DECREF if we're not restored. PyErrFetchParam t, v, tb; PyErr_Fetch(&t, &v, &tb); type.steal(t.relinquish_ownership()); instance.steal(v.relinquish_ownership()); traceback.steal(tb.relinquish_ownership()); } void PyErrRestore() { // can only do this once assert(!this->restored); this->restored = true; PyErr_Restore( this->type.relinquish_ownership(), this->instance.relinquish_ownership(), this->traceback.relinquish_ownership()); assert(!this->type && !this->instance && !this->traceback); } private: void normalize() { // First, check the traceback argument, replacing None, // with NULL if (traceback.is_None()) { traceback = nullptr; } if (traceback && !PyTraceBack_Check(traceback.borrow())) { throw PyErrOccurred(PyExc_TypeError, "throw() third argument must be a traceback object"); } if (PyExceptionClass_Check(type)) { // If we just had a type, we'll now have a type and // instance. // The type's refcount will have gone up by one // because of the instance and the instance will have // a refcount of one. Either way, we owned, and still // do own, exactly one reference. PyErr_NormalizeException(&type, &instance, &traceback); } else if (PyExceptionInstance_Check(type)) { /* Raising an instance --- usually that means an object that is a subclass of BaseException, but on Python 2, that can also mean an arbitrary old-style object. The value should be a dummy. */ if (instance && !instance.is_None()) { throw PyErrOccurred( PyExc_TypeError, "instance exception may not have a separate value"); } /* Normalize to raise , */ this->instance = this->type; this->type = PyExceptionInstance_Class(instance.borrow()); /* It would be tempting to do this: Py_ssize_t type_count = Py_REFCNT(Py_TYPE(instance.borrow())); this->type = PyExceptionInstance_Class(instance.borrow()); assert(this->type.REFCNT() == type_count + 1); But that doesn't work on Python 2 in the case of old-style instances: The result of Py_TYPE is going to be the global shared that all old-style classes have, while the return of Instance_Class() will be the Python-level class object. The two are unrelated. */ } else { /* Not something you can raise. throw() fails. */ PyErr_Format(PyExc_TypeError, "exceptions must be classes, or instances, not %s", Py_TYPE(type.borrow())->tp_name); throw PyErrOccurred(); } } }; // PyArg_Parse's O argument returns a borrowed reference. class PyArgParseParam : public BorrowedObject { private: G_NO_COPIES_OF_CLS(PyArgParseParam); public: explicit PyArgParseParam(PyObject* p=nullptr) : BorrowedObject(p) { } inline PyObject** operator&() { return &this->p; } }; };}; #endif greenlet-3.1.0/src/greenlet/greenlet_slp_switch.hpp000066400000000000000000000061761467007427000225120ustar00rootroot00000000000000#ifndef GREENLET_SLP_SWITCH_HPP #define GREENLET_SLP_SWITCH_HPP #include "greenlet_compiler_compat.hpp" #include "greenlet_refs.hpp" /* * the following macros are spliced into the OS/compiler * specific code, in order to simplify maintenance. */ // We can save about 10% of the time it takes to switch greenlets if // we thread the thread state through the slp_save_state() and the // following slp_restore_state() calls from // slp_switch()->g_switchstack() (which already needs to access it). // // However: // // that requires changing the prototypes and implementations of the // switching functions. If we just change the prototype of // slp_switch() to accept the argument and update the macros, without // changing the implementation of slp_switch(), we get crashes on // 64-bit Linux and 32-bit x86 (for reasons that aren't 100% clear); // on the other hand, 64-bit macOS seems to be fine. Also, 64-bit // windows is an issue because slp_switch is written fully in assembly // and currently ignores its argument so some code would have to be // adjusted there to pass the argument on to the // ``slp_save_state_asm()`` function (but interestingly, because of // the calling convention, the extra argument is just ignored and // things function fine, albeit slower, if we just modify // ``slp_save_state_asm`()` to fetch the pointer to pass to the // macro.) // // Our compromise is to use a *glabal*, untracked, weak, pointer // to the necessary thread state during the process of switching only. // This is safe because we're protected by the GIL, and if we're // running this code, the thread isn't exiting. This also nets us a // 10-12% speed improvement. static greenlet::Greenlet* volatile switching_thread_state = nullptr; extern "C" { static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref); static void GREENLET_NOINLINE(slp_restore_state_trampoline)(); } #define SLP_SAVE_STATE(stackref, stsizediff) \ do { \ assert(switching_thread_state); \ stackref += STACK_MAGIC; \ if (slp_save_state_trampoline((char*)stackref)) \ return -1; \ if (!switching_thread_state->active()) \ return 1; \ stsizediff = switching_thread_state->stack_start() - (char*)stackref; \ } while (0) #define SLP_RESTORE_STATE() slp_restore_state_trampoline() #define SLP_EVAL extern "C" { #define slp_switch GREENLET_NOINLINE(slp_switch) #include "slp_platformselect.h" } #undef slp_switch #ifndef STACK_MAGIC # error \ "greenlet needs to be ported to this platform, or taught how to detect your compiler properly." #endif /* !STACK_MAGIC */ #ifdef EXTERNAL_ASM /* CCP addition: Make these functions, to be called from assembler. * The token include file for the given platform should enable the * EXTERNAL_ASM define so that this is included. */ extern "C" { intptr_t slp_save_state_asm(intptr_t* ref) { intptr_t diff; SLP_SAVE_STATE(ref, diff); return diff; } void slp_restore_state_asm(void) { SLP_RESTORE_STATE(); } extern int slp_switch(void); }; #endif #endif greenlet-3.1.0/src/greenlet/greenlet_thread_state.hpp000066400000000000000000000502061467007427000227730ustar00rootroot00000000000000#ifndef GREENLET_THREAD_STATE_HPP #define GREENLET_THREAD_STATE_HPP #include #include #include "greenlet_internal.hpp" #include "greenlet_refs.hpp" #include "greenlet_thread_support.hpp" using greenlet::refs::BorrowedObject; using greenlet::refs::BorrowedGreenlet; using greenlet::refs::BorrowedMainGreenlet; using greenlet::refs::OwnedMainGreenlet; using greenlet::refs::OwnedObject; using greenlet::refs::OwnedGreenlet; using greenlet::refs::OwnedList; using greenlet::refs::PyErrFetchParam; using greenlet::refs::PyArgParseParam; using greenlet::refs::ImmortalString; using greenlet::refs::CreatedModule; using greenlet::refs::PyErrPieces; using greenlet::refs::NewReference; namespace greenlet { /** * Thread-local state of greenlets. * * Each native thread will get exactly one of these objects, * automatically accessed through the best available thread-local * mechanism the compiler supports (``thread_local`` for C++11 * compilers or ``__thread``/``declspec(thread)`` for older GCC/clang * or MSVC, respectively.) * * Previously, we kept thread-local state mostly in a bunch of * ``static volatile`` variables in the main greenlet file.. This had * the problem of requiring extra checks, loops, and great care * accessing these variables if we potentially invoked any Python code * that could release the GIL, because the state could change out from * under us. Making the variables thread-local solves this problem. * * When we detected that a greenlet API accessing the current greenlet * was invoked from a different thread than the greenlet belonged to, * we stored a reference to the greenlet in the Python thread * dictionary for the thread the greenlet belonged to. This could lead * to memory leaks if the thread then exited (because of a reference * cycle, as greenlets referred to the thread dictionary, and deleting * non-current greenlets leaked their frame plus perhaps arguments on * the C stack). If a thread exited while still having running * greenlet objects (perhaps that had just switched back to the main * greenlet), and did not invoke one of the greenlet APIs *in that * thread, immediately before it exited, without some other thread * then being invoked*, such a leak was guaranteed. * * This can be partly solved by using compiler thread-local variables * instead of the Python thread dictionary, thus avoiding a cycle. * * To fully solve this problem, we need a reliable way to know that a * thread is done and we should clean up the main greenlet. On POSIX, * we can use the destructor function of ``pthread_key_create``, but * there's nothing similar on Windows; a C++11 thread local object * reliably invokes its destructor when the thread it belongs to exits * (non-C++11 compilers offer ``__thread`` or ``declspec(thread)`` to * create thread-local variables, but they can't hold C++ objects that * invoke destructors; the C++11 version is the most portable solution * I found). When the thread exits, we can drop references and * otherwise manipulate greenlets and frames that we know can no * longer be switched to. For compilers that don't support C++11 * thread locals, we have a solution that uses the python thread * dictionary, though it may not collect everything as promptly as * other compilers do, if some other library is using the thread * dictionary and has a cycle or extra reference. * * There are two small wrinkles. The first is that when the thread * exits, it is too late to actually invoke Python APIs: the Python * thread state is gone, and the GIL is released. To solve *this* * problem, our destructor uses ``Py_AddPendingCall`` to transfer the * destruction work to the main thread. (This is not an issue for the * dictionary solution.) * * The second is that once the thread exits, the thread local object * is invalid and we can't even access a pointer to it, so we can't * pass it to ``Py_AddPendingCall``. This is handled by actually using * a second object that's thread local (ThreadStateCreator) and having * it dynamically allocate this object so it can live until the * pending call runs. */ class ThreadState { private: // As of commit 08ad1dd7012b101db953f492e0021fb08634afad // this class needed 56 bytes in o Py_DEBUG build // on 64-bit macOS 11. // Adding the vector takes us up to 80 bytes () /* Strong reference to the main greenlet */ OwnedMainGreenlet main_greenlet; /* Strong reference to the current greenlet. */ OwnedGreenlet current_greenlet; /* Strong reference to the trace function, if any. */ OwnedObject tracefunc; typedef std::vector > deleteme_t; /* A vector of raw PyGreenlet pointers representing things that need deleted when this thread is running. The vector owns the references, but you need to manually INCREF/DECREF as you use them. We don't use a vector because we make copy of this vector, and that would become O(n) as all the refcounts are incremented in the copy. */ deleteme_t deleteme; #ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED void* exception_state; #endif static std::clock_t _clocks_used_doing_gc; static ImmortalString get_referrers_name; static PythonAllocator allocator; G_NO_COPIES_OF_CLS(ThreadState); public: static void* operator new(size_t UNUSED(count)) { return ThreadState::allocator.allocate(1); } static void operator delete(void* ptr) { return ThreadState::allocator.deallocate(static_cast(ptr), 1); } static void init() { ThreadState::get_referrers_name = "get_referrers"; ThreadState::_clocks_used_doing_gc = 0; } ThreadState() : main_greenlet(OwnedMainGreenlet::consuming(green_create_main(this))), current_greenlet(main_greenlet) { if (!this->main_greenlet) { // We failed to create the main greenlet. That's bad. throw PyFatalError("Failed to create main greenlet"); } // The main greenlet starts with 1 refs: The returned one. We // then copied it to the current greenlet. assert(this->main_greenlet.REFCNT() == 2); #ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED this->exception_state = slp_get_exception_state(); #endif } inline void restore_exception_state() { #ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED // It's probably important this be inlined and only call C // functions to avoid adding an SEH frame. slp_set_exception_state(this->exception_state); #endif } inline bool has_main_greenlet() { return !!this->main_greenlet; } // Called from the ThreadStateCreator when we're in non-standard // threading mode. In that case, there is an object in the Python // thread state dictionary that points to us. The main greenlet // also traverses into us, in which case it's crucial not to // traverse back into the main greenlet. int tp_traverse(visitproc visit, void* arg, bool traverse_main=true) { if (traverse_main) { Py_VISIT(main_greenlet.borrow_o()); } if (traverse_main || current_greenlet != main_greenlet) { Py_VISIT(current_greenlet.borrow_o()); } Py_VISIT(tracefunc.borrow()); return 0; } inline BorrowedMainGreenlet borrow_main_greenlet() const { assert(this->main_greenlet); assert(this->main_greenlet.REFCNT() >= 2); return this->main_greenlet; }; inline OwnedMainGreenlet get_main_greenlet() { return this->main_greenlet; } /** * In addition to returning a new reference to the currunt * greenlet, this performs any maintenance needed. */ inline OwnedGreenlet get_current() { /* green_dealloc() cannot delete greenlets from other threads, so it stores them in the thread dict; delete them now. */ this->clear_deleteme_list(); //assert(this->current_greenlet->main_greenlet == this->main_greenlet); //assert(this->main_greenlet->main_greenlet == this->main_greenlet); return this->current_greenlet; } /** * As for non-const get_current(); */ inline BorrowedGreenlet borrow_current() { this->clear_deleteme_list(); return this->current_greenlet; } /** * Does no maintenance. */ inline OwnedGreenlet get_current() const { return this->current_greenlet; } template inline bool is_current(const refs::PyObjectPointer& obj) const { return this->current_greenlet.borrow_o() == obj.borrow_o(); } inline void set_current(const OwnedGreenlet& target) { this->current_greenlet = target; } private: /** * Deref and remove the greenlets from the deleteme list. Must be * holding the GIL. * * If *murder* is true, then we must be called from a different * thread than the one that these greenlets were running in. * In that case, if the greenlet was actually running, we destroy * the frame reference and otherwise make it appear dead before * proceeding; otherwise, we would try (and fail) to raise an * exception in it and wind up right back in this list. */ inline void clear_deleteme_list(const bool murder=false) { if (!this->deleteme.empty()) { // It's possible we could add items to this list while // running Python code if there's a thread switch, so we // need to defensively copy it before that can happen. deleteme_t copy = this->deleteme; this->deleteme.clear(); // in case things come back on the list for(deleteme_t::iterator it = copy.begin(), end = copy.end(); it != end; ++it ) { PyGreenlet* to_del = *it; if (murder) { // Force each greenlet to appear dead; we can't raise an // exception into it anymore anyway. to_del->pimpl->murder_in_place(); } // The only reference to these greenlets should be in // this list, decreffing them should let them be // deleted again, triggering calls to green_dealloc() // in the correct thread (if we're not murdering). // This may run arbitrary Python code and switch // threads or greenlets! Py_DECREF(to_del); if (PyErr_Occurred()) { PyErr_WriteUnraisable(nullptr); PyErr_Clear(); } } } } public: /** * Returns a new reference, or a false object. */ inline OwnedObject get_tracefunc() const { return tracefunc; }; inline void set_tracefunc(BorrowedObject tracefunc) { assert(tracefunc); if (tracefunc == BorrowedObject(Py_None)) { this->tracefunc.CLEAR(); } else { this->tracefunc = tracefunc; } } /** * Given a reference to a greenlet that some other thread * attempted to delete (has a refcount of 0) store it for later * deletion when the thread this state belongs to is current. */ inline void delete_when_thread_running(PyGreenlet* to_del) { Py_INCREF(to_del); this->deleteme.push_back(to_del); } /** * Set to std::clock_t(-1) to disable. */ inline static std::clock_t& clocks_used_doing_gc() { return ThreadState::_clocks_used_doing_gc; } ~ThreadState() { if (!PyInterpreterState_Head()) { // We shouldn't get here (our callers protect us) // but if we do, all we can do is bail early. return; } // We should not have an "origin" greenlet; that only exists // for the temporary time during a switch, which should not // be in progress as the thread dies. //assert(!this->switching_state.origin); this->tracefunc.CLEAR(); // Forcibly GC as much as we can. this->clear_deleteme_list(true); // The pending call did this. assert(this->main_greenlet->thread_state() == nullptr); // If the main greenlet is the current greenlet, // then we "fell off the end" and the thread died. // It's possible that there is some other greenlet that // switched to us, leaving a reference to the main greenlet // on the stack, somewhere uncollectible. Try to detect that. if (this->current_greenlet == this->main_greenlet && this->current_greenlet) { assert(this->current_greenlet->is_currently_running_in_some_thread()); // Drop one reference we hold. this->current_greenlet.CLEAR(); assert(!this->current_greenlet); // Only our reference to the main greenlet should be left, // But hold onto the pointer in case we need to do extra cleanup. PyGreenlet* old_main_greenlet = this->main_greenlet.borrow(); Py_ssize_t cnt = this->main_greenlet.REFCNT(); this->main_greenlet.CLEAR(); if (ThreadState::_clocks_used_doing_gc != std::clock_t(-1) && cnt == 2 && Py_REFCNT(old_main_greenlet) == 1) { // Highly likely that the reference is somewhere on // the stack, not reachable by GC. Verify. // XXX: This is O(n) in the total number of objects. // TODO: Add a way to disable this at runtime, and // another way to report on it. std::clock_t begin = std::clock(); NewReference gc(PyImport_ImportModule("gc")); if (gc) { OwnedObject get_referrers = gc.PyRequireAttr(ThreadState::get_referrers_name); OwnedList refs(get_referrers.PyCall(old_main_greenlet)); if (refs && refs.empty()) { assert(refs.REFCNT() == 1); // We found nothing! So we left a dangling // reference: Probably the last thing some // other greenlet did was call // 'getcurrent().parent.switch()' to switch // back to us. Clean it up. This will be the // case on CPython 3.7 and newer, as they use // an internal calling conversion that avoids // creating method objects and storing them on // the stack. Py_DECREF(old_main_greenlet); } else if (refs && refs.size() == 1 && PyCFunction_Check(refs.at(0)) && Py_REFCNT(refs.at(0)) == 2) { assert(refs.REFCNT() == 1); // Ok, we found a C method that refers to the // main greenlet, and its only referenced // twice, once in the list we just created, // once from...somewhere else. If we can't // find where else, then this is a leak. // This happens in older versions of CPython // that create a bound method object somewhere // on the stack that we'll never get back to. if (PyCFunction_GetFunction(refs.at(0).borrow()) == (PyCFunction)green_switch) { BorrowedObject function_w = refs.at(0); refs.clear(); // destroy the reference // from the list. // back to one reference. Can *it* be // found? assert(function_w.REFCNT() == 1); refs = get_referrers.PyCall(function_w); if (refs && refs.empty()) { // Nope, it can't be found so it won't // ever be GC'd. Drop it. Py_CLEAR(function_w); } } } std::clock_t end = std::clock(); ThreadState::_clocks_used_doing_gc += (end - begin); } } } // We need to make sure this greenlet appears to be dead, // because otherwise deallocing it would fail to raise an // exception in it (the thread is dead) and put it back in our // deleteme list. if (this->current_greenlet) { this->current_greenlet->murder_in_place(); this->current_greenlet.CLEAR(); } if (this->main_greenlet) { // Couldn't have been the main greenlet that was running // when the thread exited (because we already cleared this // pointer if it was). This shouldn't be possible? // If the main greenlet was current when the thread died (it // should be, right?) then we cleared its self pointer above // when we cleared the current greenlet's main greenlet pointer. // assert(this->main_greenlet->main_greenlet == this->main_greenlet // || !this->main_greenlet->main_greenlet); // // self reference, probably gone // this->main_greenlet->main_greenlet.CLEAR(); // This will actually go away when the ivar is destructed. this->main_greenlet.CLEAR(); } if (PyErr_Occurred()) { PyErr_WriteUnraisable(NULL); PyErr_Clear(); } } }; ImmortalString ThreadState::get_referrers_name(nullptr); PythonAllocator ThreadState::allocator; std::clock_t ThreadState::_clocks_used_doing_gc(0); template class ThreadStateCreator { private: // Initialized to 1, and, if still 1, created on access. // Set to 0 on destruction. ThreadState* _state; G_NO_COPIES_OF_CLS(ThreadStateCreator); public: // Only one of these, auto created per thread ThreadStateCreator() : _state((ThreadState*)1) { } ~ThreadStateCreator() { ThreadState* tmp = this->_state; this->_state = nullptr; if (tmp && tmp != (ThreadState*)1) { Destructor x(tmp); } } inline ThreadState& state() { // The main greenlet will own this pointer when it is created, // which will be right after this. The plan is to give every // greenlet a pointer to the main greenlet for the thread it // runs in; if we are doing something cross-thread, we need to // access the pointer from the main greenlet. Deleting the // thread, and hence the thread-local storage, will delete the // state pointer in the main greenlet. if (this->_state == (ThreadState*)1) { // XXX: Assuming allocation never fails this->_state = new ThreadState; // For non-standard threading, we need to store an object // in the Python thread state dictionary so that it can be // DECREF'd when the thread ends (ideally; the dict could // last longer) and clean this object up. } if (!this->_state) { throw std::runtime_error("Accessing state after destruction."); } return *this->_state; } operator ThreadState&() { return this->state(); } operator ThreadState*() { return &this->state(); } inline int tp_traverse(visitproc visit, void* arg) { if (this->_state) { return this->_state->tp_traverse(visit, arg); } return 0; } }; // We can't use the PythonAllocator for this, because we push to it // from the thread state destructor, which doesn't have the GIL, // and Python's allocators can only be called with the GIL. typedef std::vector cleanup_queue_t; }; // namespace greenlet #endif greenlet-3.1.0/src/greenlet/greenlet_thread_state_dict_cleanup.hpp000066400000000000000000000073671467007427000255170ustar00rootroot00000000000000#ifndef GREENLET_THREAD_STATE_DICT_CLEANUP_HPP #define GREENLET_THREAD_STATE_DICT_CLEANUP_HPP #include "greenlet_internal.hpp" #include "greenlet_thread_state.hpp" #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif #ifndef G_THREAD_STATE_DICT_CLEANUP_TYPE // shut the compiler up if it looks at this file in isolation #define ThreadStateCreator int #endif // Define a Python object that goes in the Python thread state dict // when the greenlet thread state is created, and which owns the // reference to the greenlet thread local state. // When the thread state dict is cleaned up, so too is the thread // state. This works best if we make sure there are no circular // references to the thread state. typedef struct _PyGreenletCleanup { PyObject_HEAD ThreadStateCreator* thread_state_creator; } PyGreenletCleanup; static void cleanup_do_dealloc(PyGreenletCleanup* self) { ThreadStateCreator* tmp = self->thread_state_creator; self->thread_state_creator = nullptr; if (tmp) { delete tmp; } } static void cleanup_dealloc(PyGreenletCleanup* self) { PyObject_GC_UnTrack(self); cleanup_do_dealloc(self); } static int cleanup_clear(PyGreenletCleanup* self) { // This method is never called by our test cases. cleanup_do_dealloc(self); return 0; } static int cleanup_traverse(PyGreenletCleanup* self, visitproc visit, void* arg) { if (self->thread_state_creator) { return self->thread_state_creator->tp_traverse(visit, arg); } return 0; } static int cleanup_is_gc(PyGreenlet* UNUSED(self)) { return 1; } static PyTypeObject PyGreenletCleanup_Type = { PyVarObject_HEAD_INIT(NULL, 0) "greenlet._greenlet.ThreadStateCleanup", sizeof(struct _PyGreenletCleanup), 0, /* tp_itemsize */ /* methods */ (destructor)cleanup_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as _number*/ 0, /* tp_as _sequence*/ 0, /* tp_as _mapping*/ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer*/ G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "Internal use only", /* tp_doc */ (traverseproc)cleanup_traverse, /* tp_traverse */ (inquiry)cleanup_clear, /* tp_clear */ 0, /* tp_richcompare */ // XXX: Don't our flags promise a weakref? 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ PyType_GenericNew, /* tp_new */ PyObject_GC_Del, /* tp_free */ (inquiry)cleanup_is_gc, /* tp_is_gc */ }; #ifdef __clang__ # pragma clang diagnostic pop #endif #endif greenlet-3.1.0/src/greenlet/greenlet_thread_support.hpp000066400000000000000000000015431467007427000233670ustar00rootroot00000000000000#ifndef GREENLET_THREAD_SUPPORT_HPP #define GREENLET_THREAD_SUPPORT_HPP /** * Defines various utility functions to help greenlet integrate well * with threads. This used to be needed when we supported Python * 2.7 on Windows, which used a very old compiler. We wrote an * alternative implementation using Python APIs and POSIX or Windows * APIs, but that's no longer needed. So this file is a shadow of its * former self --- but may be needed in the future. */ #include #include #include #include "greenlet_compiler_compat.hpp" namespace greenlet { typedef std::mutex Mutex; typedef std::lock_guard LockGuard; class LockInitError : public std::runtime_error { public: LockInitError(const char* what) : std::runtime_error(what) {}; }; }; #endif /* GREENLET_THREAD_SUPPORT_HPP */ greenlet-3.1.0/src/greenlet/platform/000077500000000000000000000000001467007427000175475ustar00rootroot00000000000000greenlet-3.1.0/src/greenlet/platform/__init__.py000066400000000000000000000000001467007427000216460ustar00rootroot00000000000000greenlet-3.1.0/src/greenlet/platform/setup_switch_x64_masm.cmd000066400000000000000000000002171467007427000244730ustar00rootroot00000000000000call "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" amd64 ml64 /nologo /c /Fo switch_x64_masm.obj switch_x64_masm.asm greenlet-3.1.0/src/greenlet/platform/switch_aarch64_gcc.h000066400000000000000000000103231467007427000233440ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 07-Sep-16 Add clang support using x register naming. Fredrik Fornwall * 13-Apr-13 Add support for strange GCC caller-save decisions * 08-Apr-13 File creation. Michael Matz * * NOTES * * Simply save all callee saved registers * */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REGS_TO_SAVE "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", \ "x27", "x28", "x30" /* aka lr */, \ "v8", "v9", "v10", "v11", \ "v12", "v13", "v14", "v15" /* * Recall: asm asm-qualifiers ( AssemblerTemplate : OutputOperands [ : InputOperands [ : Clobbers ] ]) or (if asm-qualifiers contains 'goto') asm asm-qualifiers ( AssemblerTemplate : OutputOperands : InputOperands : Clobbers : GotoLabels) and OutputOperands are [ [asmSymbolicName] ] constraint (cvariablename) When a name is given, refer to it as ``%[the name]``. When not given, ``%i`` where ``i`` is the zero-based index. constraints starting with ``=`` means only writing; ``+`` means reading and writing. This is followed by ``r`` (must be register) or ``m`` (must be memory) and these can be combined. The ``cvariablename`` is actually an lvalue expression. In AArch65, 31 general purpose registers. If named X0... they are 64-bit. If named W0... they are the bottom 32 bits of the corresponding 64 bit register. XZR and WZR are hardcoded to 0, and ignore writes. Arguments are in X0..X7. C++ uses X0 for ``this``. X0 holds simple return values (?) Whenever a W register is written, the top half of the X register is zeroed. */ static int slp_switch(void) { int err; void *fp; /* Windowz uses a 32-bit long on a 64-bit platform, unlike the rest of the world, and in theory we can be compiled with GCC/llvm on 64-bit windows. So we need a fixed-width type. */ int64_t *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("str x29, %0" : "=m"(fp) : : ); __asm__ ("mov %0, sp" : "=r" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "add sp,sp,%0\n" "add x29,x29,%0\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); /* SLP_SAVE_STATE macro contains some return statements (of -1 and 1). It falls through only when the return value of slp_save_state() is zero, which is placed in x0. In that case we (slp_switch) also want to return zero (also in x0 of course). Now, some GCC versions (seen with 4.8) think it's a good idea to save/restore x0 around the call to slp_restore_state(), instead of simply zeroing it at the return below. But slp_restore_state writes random values to the stack slot used for this save/restore (from when it once was saved above in SLP_SAVE_STATE, when it was still uninitialized), so "restoring" that precious zero actually makes us return random values. There are some ways to make GCC not use that zero value in the normal return path (e.g. making err volatile, but that costs a little stack space), and the simplest is to call a function that returns an unknown value (which happens to be zero), so the saved/restored value is unused. Thus, this line stores a 0 into the ``err`` variable (which must be held in a register for this instruction, of course). The ``w`` qualifier causes the instruction to use W0 instead of X0, otherwise we get a warning about a value size mismatch (because err is an int, and aarch64 platforms are LP64: 32-bit int, 64 bit long and pointer). */ __asm__ volatile ("mov %w0, #0" : "=r" (err)); } __asm__ volatile ("ldr x29, %0" : : "m" (fp) :); __asm__ volatile ("" : : : REGS_TO_SAVE); return err; } #endif greenlet-3.1.0/src/greenlet/platform/switch_alpha_unix.h000066400000000000000000000012371467007427000234340ustar00rootroot00000000000000#define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REGS_TO_SAVE "$9", "$10", "$11", "$12", "$13", "$14", "$15", \ "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", "$f8", "$f9" static int slp_switch(void) { int ret; long *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("mov $30, %0" : "=r" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "addq $30, %0, $30\n\t" : /* no outputs */ : "r" (stsizediff) ); SLP_RESTORE_STATE(); } __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("mov $31, %0" : "=r" (ret) : ); return ret; } #endif greenlet-3.1.0/src/greenlet/platform/switch_amd64_unix.h000066400000000000000000000052741467007427000232670ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 3-May-13 Ralf Schmitt * Add support for strange GCC caller-save decisions * (ported from switch_aarch64_gcc.h) * 18-Aug-11 Alexey Borzenkov * Correctly save rbp, csr and cw * 01-Apr-04 Hye-Shik Chang * Ported from i386 to amd64. * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for spark * 31-Avr-02 Armin Rigo * Added ebx, esi and edi register-saves. * 01-Mar-02 Samual M. Rushing * Ported from i386. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL /* #define STACK_MAGIC 3 */ /* the above works fine with gcc 2.96, but 2.95.3 wants this */ #define STACK_MAGIC 0 #define REGS_TO_SAVE "r12", "r13", "r14", "r15" static int slp_switch(void) { int err; void* rbp; void* rbx; unsigned int csr; unsigned short cw; /* This used to be declared 'register', but that does nothing in modern compilers and is explicitly forbidden in some new standards. */ long *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("fstcw %0" : "=m" (cw)); __asm__ volatile ("stmxcsr %0" : "=m" (csr)); __asm__ volatile ("movq %%rbp, %0" : "=m" (rbp)); __asm__ volatile ("movq %%rbx, %0" : "=m" (rbx)); __asm__ ("movq %%rsp, %0" : "=g" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "addq %0, %%rsp\n" "addq %0, %%rbp\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); __asm__ volatile ("xorq %%rax, %%rax" : "=a" (err)); } __asm__ volatile ("movq %0, %%rbx" : : "m" (rbx)); __asm__ volatile ("movq %0, %%rbp" : : "m" (rbp)); __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); __asm__ volatile ("fldcw %0" : : "m" (cw)); __asm__ volatile ("" : : : REGS_TO_SAVE); return err; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_arm32_gcc.h000066400000000000000000000046571467007427000230550ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 14-Aug-06 File creation. Ported from Arm Thumb. Sylvain Baro * 3-Sep-06 Commented out saving of r1-r3 (r4 already commented out) as I * read that these do not need to be saved. Also added notes and * errors related to the frame pointer. Richard Tew. * * NOTES * * It is not possible to detect if fp is used or not, so the supplied * switch function needs to support it, so that you can remove it if * it does not apply to you. * * POSSIBLE ERRORS * * "fp cannot be used in asm here" * * - Try commenting out "fp" in REGS_TO_SAVE. * */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REG_SP "sp" #define REG_SPSP "sp,sp" #ifdef __thumb__ #define REG_FP "r7" #define REG_FPFP "r7,r7" #define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r8", "r9", "r10", "r11", "lr" #else #define REG_FP "fp" #define REG_FPFP "fp,fp" #define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r7", "r8", "r9", "r10", "lr" #endif #if defined(__SOFTFP__) #define REGS_TO_SAVE REGS_TO_SAVE_GENERAL #elif defined(__VFP_FP__) #define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "d8", "d9", "d10", "d11", \ "d12", "d13", "d14", "d15" #elif defined(__MAVERICK__) #define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "mvf4", "mvf5", "mvf6", "mvf7", \ "mvf8", "mvf9", "mvf10", "mvf11", \ "mvf12", "mvf13", "mvf14", "mvf15" #else #define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "f4", "f5", "f6", "f7" #endif static int #ifdef __GNUC__ __attribute__((optimize("no-omit-frame-pointer"))) #endif slp_switch(void) { void *fp; int *stackref, stsizediff; int result; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("mov r0," REG_FP "\n\tstr r0,%0" : "=m" (fp) : : "r0"); __asm__ ("mov %0," REG_SP : "=r" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "add " REG_SPSP ",%0\n" "add " REG_FPFP ",%0\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); } __asm__ volatile ("ldr r0,%1\n\tmov " REG_FP ",r0\n\tmov %0, #0" : "=r" (result) : "m" (fp) : "r0"); __asm__ volatile ("" : : : REGS_TO_SAVE); return result; } #endif greenlet-3.1.0/src/greenlet/platform/switch_arm32_ios.h000066400000000000000000000035441467007427000231050ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 31-May-15 iOS support. Ported from arm32. Proton * * NOTES * * It is not possible to detect if fp is used or not, so the supplied * switch function needs to support it, so that you can remove it if * it does not apply to you. * * POSSIBLE ERRORS * * "fp cannot be used in asm here" * * - Try commenting out "fp" in REGS_TO_SAVE. * */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REG_SP "sp" #define REG_SPSP "sp,sp" #define REG_FP "r7" #define REG_FPFP "r7,r7" #define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r8", "r10", "r11", "lr" #define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "d8", "d9", "d10", "d11", \ "d12", "d13", "d14", "d15" static int #ifdef __GNUC__ __attribute__((optimize("no-omit-frame-pointer"))) #endif slp_switch(void) { void *fp; int *stackref, stsizediff, result; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("str " REG_FP ",%0" : "=m" (fp)); __asm__ ("mov %0," REG_SP : "=r" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "add " REG_SPSP ",%0\n" "add " REG_FPFP ",%0\n" : : "r" (stsizediff) : REGS_TO_SAVE /* Clobber registers, force compiler to * recalculate address of void *fp from REG_SP or REG_FP */ ); SLP_RESTORE_STATE(); } __asm__ volatile ( "ldr " REG_FP ", %1\n\t" "mov %0, #0" : "=r" (result) : "m" (fp) : REGS_TO_SAVE /* Force compiler to restore saved registers after this */ ); return result; } #endif greenlet-3.1.0/src/greenlet/platform/switch_arm64_masm.asm000066400000000000000000000023351467007427000236030ustar00rootroot00000000000000 AREA switch_arm64_masm, CODE, READONLY; GLOBAL slp_switch [FUNC] EXTERN slp_save_state_asm EXTERN slp_restore_state_asm slp_switch ; push callee saved registers to stack stp x19, x20, [sp, #-16]! stp x21, x22, [sp, #-16]! stp x23, x24, [sp, #-16]! stp x25, x26, [sp, #-16]! stp x27, x28, [sp, #-16]! stp x29, x30, [sp, #-16]! stp d8, d9, [sp, #-16]! stp d10, d11, [sp, #-16]! stp d12, d13, [sp, #-16]! stp d14, d15, [sp, #-16]! ; call slp_save_state_asm with stack pointer mov x0, sp bl slp_save_state_asm ; early return for return value of 1 and -1 cmp x0, #-1 b.eq RETURN cmp x0, #1 b.eq RETURN ; increment stack and frame pointer add sp, sp, x0 add x29, x29, x0 bl slp_restore_state_asm ; store return value for successful completion of routine mov x0, #0 RETURN ; pop registers from stack ldp d14, d15, [sp], #16 ldp d12, d13, [sp], #16 ldp d10, d11, [sp], #16 ldp d8, d9, [sp], #16 ldp x29, x30, [sp], #16 ldp x27, x28, [sp], #16 ldp x25, x26, [sp], #16 ldp x23, x24, [sp], #16 ldp x21, x22, [sp], #16 ldp x19, x20, [sp], #16 ret END greenlet-3.1.0/src/greenlet/platform/switch_arm64_masm.obj000066400000000000000000000013521467007427000235730ustar00rootroot00000000000000dŞ7Ţqa€/4Co|dŕ @`.debug$SŚô@BóSż©ő[ż©÷cż©ůkż©űsż©ý{ż©č'żmę/żmě7żmî?żmŕ‘”±ŕTń T˙c ‹˝‹”€Ňî?Álě7Álę/Álč'Álý{Á¨űsÁ¨ůkÁ¨÷cÁ¨ő[Á¨óSÁ¨Ŕ_Ö, H ń€BC:\Users\niysai01\Workspace\greenlet\switch_arm64_masm.obj:<öVuMicrosoft (R) ARM Macro Assembler@comp.idVu˙˙@feat.00˙˙.fileţ˙gC:\Users\niysai01\Workspace\greenlet\src\greenlet\platform\switch_arm64_masm.asm|.debug$SŚ)RETURNP? Jswitch_arm64_masmslp_save_state_asmslp_restore_state_asmslp_switchgreenlet-3.1.0/src/greenlet/platform/switch_arm64_msvc.h000066400000000000000000000006161467007427000232650ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 21-Oct-21 Niyas Sait * First version to enable win/arm64 support. */ #define STACK_REFPLUS 1 #define STACK_MAGIC 0 /* Use the generic support for an external assembly language slp_switch function. */ #define EXTERNAL_ASM #ifdef SLP_EVAL /* This always uses the external masm assembly file. */ #endifgreenlet-3.1.0/src/greenlet/platform/switch_csky_gcc.h000066400000000000000000000024631467007427000230730ustar00rootroot00000000000000#ifdef SLP_EVAL #define STACK_MAGIC 0 #define REG_FP "r8" #ifdef __CSKYABIV2__ #define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r7", "r9", "r10", "r11", "r15",\ "r16", "r17", "r18", "r19", "r20", "r21", "r22",\ "r23", "r24", "r25" #if defined (__CSKY_HARD_FLOAT__) || (__CSKY_VDSP__) #define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "vr8", "vr9", "vr10", "vr11", "vr12",\ "vr13", "vr14", "vr15" #else #define REGS_TO_SAVE REGS_TO_SAVE_GENERAL #endif #else #define REGS_TO_SAVE "r9", "r10", "r11", "r12", "r13", "r15" #endif static int #ifdef __GNUC__ __attribute__((optimize("no-omit-frame-pointer"))) #endif slp_switch(void) { int *stackref, stsizediff; int result; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ ("mov %0, sp" : "=r" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "addu sp,%0\n" "addu "REG_FP",%0\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); } __asm__ volatile ("movi %0, 0" : "=r" (result)); __asm__ volatile ("" : : : REGS_TO_SAVE); return result; } #endif greenlet-3.1.0/src/greenlet/platform/switch_loongarch64_linux.h000066400000000000000000000014131467007427000246450ustar00rootroot00000000000000#define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REGS_TO_SAVE "s0", "s1", "s2", "s3", "s4", "s5", \ "s6", "s7", "s8", "fp", \ "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31" static int slp_switch(void) { int ret; long *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("move %0, $sp" : "=r" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "add.d $sp, $sp, %0\n\t" : /* no outputs */ : "r" (stsizediff) ); SLP_RESTORE_STATE(); } __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("move %0, $zero" : "=r" (ret) : ); return ret; } #endif greenlet-3.1.0/src/greenlet/platform/switch_m68k_gcc.h000066400000000000000000000016401467007427000227030ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 2014-01-06 Andreas Schwab * File created. */ #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REGS_TO_SAVE "%d2", "%d3", "%d4", "%d5", "%d6", "%d7", \ "%a2", "%a3", "%a4" static int slp_switch(void) { int err; int *stackref, stsizediff; void *fp, *a5; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("move.l %%fp, %0" : "=m"(fp)); __asm__ volatile ("move.l %%a5, %0" : "=m"(a5)); __asm__ ("move.l %%sp, %0" : "=r"(stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ("add.l %0, %%sp; add.l %0, %%fp" : : "r"(stsizediff)); SLP_RESTORE_STATE(); __asm__ volatile ("clr.l %0" : "=g" (err)); } __asm__ volatile ("move.l %0, %%a5" : : "m"(a5)); __asm__ volatile ("move.l %0, %%fp" : : "m"(fp)); __asm__ volatile ("" : : : REGS_TO_SAVE); return err; } #endif greenlet-3.1.0/src/greenlet/platform/switch_mips_unix.h000066400000000000000000000026221467007427000233160ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 20-Sep-14 Matt Madison * Re-code the saving of the gp register for MIPS64. * 05-Jan-08 Thiemo Seufer * Ported from ppc. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REGS_TO_SAVE "$16", "$17", "$18", "$19", "$20", "$21", "$22", \ "$23", "$30" static int slp_switch(void) { int err; int *stackref, stsizediff; #ifdef __mips64 uint64_t gpsave; #endif __asm__ __volatile__ ("" : : : REGS_TO_SAVE); #ifdef __mips64 __asm__ __volatile__ ("sd $28,%0" : "=m" (gpsave) : : ); #endif __asm__ ("move %0, $29" : "=r" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ __volatile__ ( #ifdef __mips64 "daddu $29, %0\n" #else "addu $29, %0\n" #endif : /* no outputs */ : "r" (stsizediff) ); SLP_RESTORE_STATE(); } #ifdef __mips64 __asm__ __volatile__ ("ld $28,%0" : : "m" (gpsave) : ); #endif __asm__ __volatile__ ("" : : : REGS_TO_SAVE); __asm__ __volatile__ ("move %0, $0" : "=r" (err)); return err; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_ppc64_aix.h000066400000000000000000000074241467007427000231050ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 16-Oct-20 Jesse Gorzinski * Copied from Linux PPC64 implementation * 04-Sep-18 Alexey Borzenkov * Workaround a gcc bug using manual save/restore of r30 * 21-Mar-18 Tulio Magno Quites Machado Filho * Added r30 to the list of saved registers in order to fully comply with * both ppc64 ELFv1 ABI and the ppc64le ELFv2 ABI, that classify this * register as a nonvolatile register used for local variables. * 21-Mar-18 Laszlo Boszormenyi * Save r2 (TOC pointer) manually. * 10-Dec-13 Ulrich Weigand * Support ELFv2 ABI. Save float/vector registers. * 09-Mar-12 Michael Ellerman * 64-bit implementation, copied from 32-bit. * 07-Sep-05 (py-dev mailing list discussion) * removed 'r31' from the register-saved. !!!! WARNING !!!! * It means that this file can no longer be compiled statically! * It is now only suitable as part of a dynamic library! * 14-Jan-04 Bob Ippolito * added cr2-cr4 to the registers to be saved. * Open questions: Should we save FP registers? * What about vector registers? * Differences between darwin and unix? * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 04-Oct-02 Gustavo Niemeyer * Ported from MacOS version. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for sparc * 29-Jun-02 Christian Tismer * Added register 13-29, 31 saves. The same way as * Armin Rigo did for the x86_unix version. * This seems to be now fully functional! * 04-Mar-02 Hye-Shik Chang * Ported from i386. * 31-Jul-12 Trevor Bowen * Changed memory constraints to register only. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 6 #if defined(__ALTIVEC__) #define ALTIVEC_REGS \ "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", \ "v28", "v29", "v30", "v31", #else #define ALTIVEC_REGS #endif #define REGS_TO_SAVE "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ "r31", \ "fr14", "fr15", "fr16", "fr17", "fr18", "fr19", "fr20", "fr21", \ "fr22", "fr23", "fr24", "fr25", "fr26", "fr27", "fr28", "fr29", \ "fr30", "fr31", \ ALTIVEC_REGS \ "cr2", "cr3", "cr4" static int slp_switch(void) { int err; long *stackref, stsizediff; void * toc; void * r30; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("std 2, %0" : "=m" (toc)); __asm__ volatile ("std 30, %0" : "=m" (r30)); __asm__ ("mr %0, 1" : "=r" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "mr 11, %0\n" "add 1, 1, 11\n" : /* no outputs */ : "r" (stsizediff) : "11" ); SLP_RESTORE_STATE(); } __asm__ volatile ("ld 30, %0" : : "m" (r30)); __asm__ volatile ("ld 2, %0" : : "m" (toc)); __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("li %0, 0" : "=r" (err)); return err; } #endif greenlet-3.1.0/src/greenlet/platform/switch_ppc64_linux.h000066400000000000000000000073471467007427000234670ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 04-Sep-18 Alexey Borzenkov * Workaround a gcc bug using manual save/restore of r30 * 21-Mar-18 Tulio Magno Quites Machado Filho * Added r30 to the list of saved registers in order to fully comply with * both ppc64 ELFv1 ABI and the ppc64le ELFv2 ABI, that classify this * register as a nonvolatile register used for local variables. * 21-Mar-18 Laszlo Boszormenyi * Save r2 (TOC pointer) manually. * 10-Dec-13 Ulrich Weigand * Support ELFv2 ABI. Save float/vector registers. * 09-Mar-12 Michael Ellerman * 64-bit implementation, copied from 32-bit. * 07-Sep-05 (py-dev mailing list discussion) * removed 'r31' from the register-saved. !!!! WARNING !!!! * It means that this file can no longer be compiled statically! * It is now only suitable as part of a dynamic library! * 14-Jan-04 Bob Ippolito * added cr2-cr4 to the registers to be saved. * Open questions: Should we save FP registers? * What about vector registers? * Differences between darwin and unix? * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 04-Oct-02 Gustavo Niemeyer * Ported from MacOS version. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for sparc * 29-Jun-02 Christian Tismer * Added register 13-29, 31 saves. The same way as * Armin Rigo did for the x86_unix version. * This seems to be now fully functional! * 04-Mar-02 Hye-Shik Chang * Ported from i386. * 31-Jul-12 Trevor Bowen * Changed memory constraints to register only. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #if _CALL_ELF == 2 #define STACK_MAGIC 4 #else #define STACK_MAGIC 6 #endif #if defined(__ALTIVEC__) #define ALTIVEC_REGS \ "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", \ "v28", "v29", "v30", "v31", #else #define ALTIVEC_REGS #endif #define REGS_TO_SAVE "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ "r31", \ "fr14", "fr15", "fr16", "fr17", "fr18", "fr19", "fr20", "fr21", \ "fr22", "fr23", "fr24", "fr25", "fr26", "fr27", "fr28", "fr29", \ "fr30", "fr31", \ ALTIVEC_REGS \ "cr2", "cr3", "cr4" static int slp_switch(void) { int err; long *stackref, stsizediff; void * toc; void * r30; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("std 2, %0" : "=m" (toc)); __asm__ volatile ("std 30, %0" : "=m" (r30)); __asm__ ("mr %0, 1" : "=r" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "mr 11, %0\n" "add 1, 1, 11\n" : /* no outputs */ : "r" (stsizediff) : "11" ); SLP_RESTORE_STATE(); } __asm__ volatile ("ld 30, %0" : : "m" (r30)); __asm__ volatile ("ld 2, %0" : : "m" (toc)); __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("li %0, 0" : "=r" (err)); return err; } #endif greenlet-3.1.0/src/greenlet/platform/switch_ppc_aix.h000066400000000000000000000055751467007427000227400ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 07-Mar-11 Floris Bruynooghe * Do not add stsizediff to general purpose * register (GPR) 30 as this is a non-volatile and * unused by the PowerOpen Environment, therefore * this was modifying a user register instead of the * frame pointer (which does not seem to exist). * 07-Sep-05 (py-dev mailing list discussion) * removed 'r31' from the register-saved. !!!! WARNING !!!! * It means that this file can no longer be compiled statically! * It is now only suitable as part of a dynamic library! * 14-Jan-04 Bob Ippolito * added cr2-cr4 to the registers to be saved. * Open questions: Should we save FP registers? * What about vector registers? * Differences between darwin and unix? * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 04-Oct-02 Gustavo Niemeyer * Ported from MacOS version. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for sparc * 29-Jun-02 Christian Tismer * Added register 13-29, 31 saves. The same way as * Armin Rigo did for the x86_unix version. * This seems to be now fully functional! * 04-Mar-02 Hye-Shik Chang * Ported from i386. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 3 /* !!!!WARNING!!!! need to add "r31" in the next line if this header file * is meant to be compiled non-dynamically! */ #define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ "cr2", "cr3", "cr4" static int slp_switch(void) { int err; int *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ ("mr %0, 1" : "=r" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "mr 11, %0\n" "add 1, 1, 11\n" : /* no outputs */ : "r" (stsizediff) : "11" ); SLP_RESTORE_STATE(); } __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("li %0, 0" : "=r" (err)); return err; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_ppc_linux.h000066400000000000000000000053071467007427000233070ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 07-Sep-05 (py-dev mailing list discussion) * removed 'r31' from the register-saved. !!!! WARNING !!!! * It means that this file can no longer be compiled statically! * It is now only suitable as part of a dynamic library! * 14-Jan-04 Bob Ippolito * added cr2-cr4 to the registers to be saved. * Open questions: Should we save FP registers? * What about vector registers? * Differences between darwin and unix? * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 04-Oct-02 Gustavo Niemeyer * Ported from MacOS version. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for sparc * 29-Jun-02 Christian Tismer * Added register 13-29, 31 saves. The same way as * Armin Rigo did for the x86_unix version. * This seems to be now fully functional! * 04-Mar-02 Hye-Shik Chang * Ported from i386. * 31-Jul-12 Trevor Bowen * Changed memory constraints to register only. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 3 /* !!!!WARNING!!!! need to add "r31" in the next line if this header file * is meant to be compiled non-dynamically! */ #define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ "cr2", "cr3", "cr4" static int slp_switch(void) { int err; int *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ ("mr %0, 1" : "=r" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "mr 11, %0\n" "add 1, 1, 11\n" "add 30, 30, 11\n" : /* no outputs */ : "r" (stsizediff) : "11" ); SLP_RESTORE_STATE(); } __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("li %0, 0" : "=r" (err)); return err; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_ppc_macosx.h000066400000000000000000000051001467007427000234310ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 07-Sep-05 (py-dev mailing list discussion) * removed 'r31' from the register-saved. !!!! WARNING !!!! * It means that this file can no longer be compiled statically! * It is now only suitable as part of a dynamic library! * 14-Jan-04 Bob Ippolito * added cr2-cr4 to the registers to be saved. * Open questions: Should we save FP registers? * What about vector registers? * Differences between darwin and unix? * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for sparc * 29-Jun-02 Christian Tismer * Added register 13-29, 31 saves. The same way as * Armin Rigo did for the x86_unix version. * This seems to be now fully functional! * 04-Mar-02 Hye-Shik Chang * Ported from i386. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 3 /* !!!!WARNING!!!! need to add "r31" in the next line if this header file * is meant to be compiled non-dynamically! */ #define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ "cr2", "cr3", "cr4" static int slp_switch(void) { int err; int *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ ("; asm block 2\n\tmr %0, r1" : "=g" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "; asm block 3\n" "\tmr r11, %0\n" "\tadd r1, r1, r11\n" "\tadd r30, r30, r11\n" : /* no outputs */ : "g" (stsizediff) : "r11" ); SLP_RESTORE_STATE(); } __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("li %0, 0" : "=r" (err)); return err; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_ppc_unix.h000066400000000000000000000051341467007427000231310ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 07-Sep-05 (py-dev mailing list discussion) * removed 'r31' from the register-saved. !!!! WARNING !!!! * It means that this file can no longer be compiled statically! * It is now only suitable as part of a dynamic library! * 14-Jan-04 Bob Ippolito * added cr2-cr4 to the registers to be saved. * Open questions: Should we save FP registers? * What about vector registers? * Differences between darwin and unix? * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 04-Oct-02 Gustavo Niemeyer * Ported from MacOS version. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for sparc * 29-Jun-02 Christian Tismer * Added register 13-29, 31 saves. The same way as * Armin Rigo did for the x86_unix version. * This seems to be now fully functional! * 04-Mar-02 Hye-Shik Chang * Ported from i386. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 3 /* !!!!WARNING!!!! need to add "r31" in the next line if this header file * is meant to be compiled non-dynamically! */ #define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ "cr2", "cr3", "cr4" static int slp_switch(void) { int err; int *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ ("mr %0, 1" : "=g" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "mr 11, %0\n" "add 1, 1, 11\n" "add 30, 30, 11\n" : /* no outputs */ : "g" (stsizediff) : "11" ); SLP_RESTORE_STATE(); } __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("li %0, 0" : "=r" (err)); return err; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_riscv_unix.h000066400000000000000000000015411467007427000234730ustar00rootroot00000000000000#define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REGS_TO_SAVE "s1", "s2", "s3", "s4", "s5", \ "s6", "s7", "s8", "s9", "s10", "s11", "fs0", "fs1", \ "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", \ "fs10", "fs11" static int slp_switch(void) { long fp; int ret; long *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("mv %0, fp" : "=r" (fp) : ); __asm__ volatile ("mv %0, sp" : "=r" (stackref) : ); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "add sp, sp, %0\n\t" "add fp, fp, %0\n\t" : /* no outputs */ : "r" (stsizediff) ); SLP_RESTORE_STATE(); } __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("ld fp, %0" : : "m" (fp)); __asm__ volatile ("mv %0, zero" : "=r" (ret) : ); return ret; } #endif greenlet-3.1.0/src/greenlet/platform/switch_s390_unix.h000066400000000000000000000053131467007427000230440ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 25-Jan-12 Alexey Borzenkov * Fixed Linux/S390 port to work correctly with * different optimization options both on 31-bit * and 64-bit. Thanks to Stefan Raabe for lots * of testing. * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 06-Oct-02 Gustavo Niemeyer * Ported to Linux/S390. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #ifdef __s390x__ #define STACK_MAGIC 20 /* 20 * 8 = 160 bytes of function call area */ #else #define STACK_MAGIC 24 /* 24 * 4 = 96 bytes of function call area */ #endif /* Technically, r11-r13 also need saving, but function prolog starts with stm(g) and since there are so many saved registers already it won't be optimized, resulting in all r6-r15 being saved */ #define REGS_TO_SAVE "r6", "r7", "r8", "r9", "r10", "r14", \ "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", \ "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15" static int slp_switch(void) { int ret; long *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); #ifdef __s390x__ __asm__ volatile ("lgr %0, 15" : "=r" (stackref) : ); #else __asm__ volatile ("lr %0, 15" : "=r" (stackref) : ); #endif { SLP_SAVE_STATE(stackref, stsizediff); /* N.B. r11 may be used as the frame pointer, and in that case it cannot be clobbered and needs offsetting just like the stack pointer (but in cases where frame pointer isn't used we might clobber it accidentally). What's scary is that r11 is 2nd (and even 1st when GOT is used) callee saved register that gcc would chose for surviving function calls. However, since r6-r10 are clobbered above, their cost for reuse is reduced, so gcc IRA will chose them over r11 (not seeing r11 is implicitly saved), making it relatively safe to offset in all cases. :) */ __asm__ volatile ( #ifdef __s390x__ "agr 15, %0\n\t" "agr 11, %0" #else "ar 15, %0\n\t" "ar 11, %0" #endif : /* no outputs */ : "r" (stsizediff) ); SLP_RESTORE_STATE(); } __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("lhi %0, 0" : "=r" (ret) : ); return ret; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_sh_gcc.h000066400000000000000000000016051467007427000225310ustar00rootroot00000000000000#define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REGS_TO_SAVE "r8", "r9", "r10", "r11", "r13", \ "fr12", "fr13", "fr14", "fr15" // r12 Global context pointer, GP // r14 Frame pointer, FP // r15 Stack pointer, SP static int slp_switch(void) { int err; void* fp; int *stackref, stsizediff; __asm__ volatile("" : : : REGS_TO_SAVE); __asm__ volatile("mov.l r14, %0" : "=m"(fp) : :); __asm__("mov r15, %0" : "=r"(stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile( "add %0, r15\n" "add %0, r14\n" : /* no outputs */ : "r"(stsizediff)); SLP_RESTORE_STATE(); __asm__ volatile("mov r0, %0" : "=r"(err) : :); } __asm__ volatile("mov.l %0, r14" : : "m"(fp) :); __asm__ volatile("" : : : REGS_TO_SAVE); return err; } #endif greenlet-3.1.0/src/greenlet/platform/switch_sparc_sun_gcc.h000066400000000000000000000053551467007427000241220ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 16-May-15 Alexey Borzenkov * Move stack spilling code inside save/restore functions * 30-Aug-13 Floris Bruynooghe Clean the register windows again before returning. This does not clobber the PIC register as it leaves the current window intact and is required for multi- threaded code to work correctly. * 08-Mar-11 Floris Bruynooghe * No need to set return value register explicitly * before the stack and framepointer are adjusted * as none of the other registers are influenced by * this. Also don't needlessly clean the windows * ('ta %0" :: "i" (ST_CLEAN_WINDOWS)') as that * clobbers the gcc PIC register (%l7). * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * added support for SunOS sparc with gcc */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #if defined(__sparcv9) #define SLP_FLUSHW __asm__ volatile ("flushw") #else #define SLP_FLUSHW __asm__ volatile ("ta 3") /* ST_FLUSH_WINDOWS */ #endif /* On sparc we need to spill register windows inside save/restore functions */ #define SLP_BEFORE_SAVE_STATE() SLP_FLUSHW #define SLP_BEFORE_RESTORE_STATE() SLP_FLUSHW static int slp_switch(void) { int err; int *stackref, stsizediff; /* Put current stack pointer into stackref. * Register spilling is done in save/restore. */ __asm__ volatile ("mov %%sp, %0" : "=r" (stackref)); { /* Thou shalt put SLP_SAVE_STATE into a local block */ /* Copy the current stack onto the heap */ SLP_SAVE_STATE(stackref, stsizediff); /* Increment stack and frame pointer by stsizediff */ __asm__ volatile ( "add %0, %%sp, %%sp\n\t" "add %0, %%fp, %%fp" : : "r" (stsizediff)); /* Copy new stack from it's save store on the heap */ SLP_RESTORE_STATE(); __asm__ volatile ("mov %1, %0" : "=r" (err) : "i" (0)); return err; } } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_x32_unix.h000066400000000000000000000027451467007427000227700ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 17-Aug-12 Fantix King * Ported from amd64. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 #define REGS_TO_SAVE "r12", "r13", "r14", "r15" static int slp_switch(void) { void* ebp; void* ebx; unsigned int csr; unsigned short cw; int err; int *stackref, stsizediff; __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("fstcw %0" : "=m" (cw)); __asm__ volatile ("stmxcsr %0" : "=m" (csr)); __asm__ volatile ("movl %%ebp, %0" : "=m" (ebp)); __asm__ volatile ("movl %%ebx, %0" : "=m" (ebx)); __asm__ ("movl %%esp, %0" : "=g" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "addl %0, %%esp\n" "addl %0, %%ebp\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); } __asm__ volatile ("movl %0, %%ebx" : : "m" (ebx)); __asm__ volatile ("movl %0, %%ebp" : : "m" (ebp)); __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); __asm__ volatile ("fldcw %0" : : "m" (cw)); __asm__ volatile ("" : : : REGS_TO_SAVE); __asm__ volatile ("xorl %%eax, %%eax" : "=a" (err)); return err; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/platform/switch_x64_masm.asm000066400000000000000000000034611467007427000232740ustar00rootroot00000000000000; ; stack switching code for MASM on x641 ; Kristjan Valur Jonsson, sept 2005 ; ;prototypes for our calls slp_save_state_asm PROTO slp_restore_state_asm PROTO pushxmm MACRO reg sub rsp, 16 .allocstack 16 movaps [rsp], reg ; faster than movups, but we must be aligned ; .savexmm128 reg, offset (don't know what offset is, no documentation) ENDM popxmm MACRO reg movaps reg, [rsp] ; faster than movups, but we must be aligned add rsp, 16 ENDM pushreg MACRO reg push reg .pushreg reg ENDM popreg MACRO reg pop reg ENDM .code slp_switch PROC FRAME ;realign stack to 16 bytes after return address push, makes the following faster sub rsp,8 .allocstack 8 pushxmm xmm15 pushxmm xmm14 pushxmm xmm13 pushxmm xmm12 pushxmm xmm11 pushxmm xmm10 pushxmm xmm9 pushxmm xmm8 pushxmm xmm7 pushxmm xmm6 pushreg r15 pushreg r14 pushreg r13 pushreg r12 pushreg rbp pushreg rbx pushreg rdi pushreg rsi sub rsp, 10h ;allocate the singlefunction argument (must be multiple of 16) .allocstack 10h .endprolog lea rcx, [rsp+10h] ;load stack base that we are saving call slp_save_state_asm ;pass stackpointer, return offset in eax cmp rax, 1 je EXIT1 cmp rax, -1 je EXIT2 ;actual stack switch: add rsp, rax call slp_restore_state_asm xor rax, rax ;return 0 EXIT: add rsp, 10h popreg rsi popreg rdi popreg rbx popreg rbp popreg r12 popreg r13 popreg r14 popreg r15 popxmm xmm6 popxmm xmm7 popxmm xmm8 popxmm xmm9 popxmm xmm10 popxmm xmm11 popxmm xmm12 popxmm xmm13 popxmm xmm14 popxmm xmm15 add rsp, 8 ret EXIT1: mov rax, 1 jmp EXIT EXIT2: sar rax, 1 jmp EXIT slp_switch ENDP ENDgreenlet-3.1.0/src/greenlet/platform/switch_x64_masm.obj000066400000000000000000000020661467007427000232660ustar00rootroot00000000000000d†ŔëKć.textÜě P`.data@PŔ.pdata  @0@.xdata,*@@@.debug$SV@BHěHěD)<$HěD)4$HěD),$HěD)$$HěD)$HěD)$HěD) $HěD)$Hě)<$Hě)4$AWAVAUATUSWVHěHŤL$čHř„‚Hř˙„HŕčH3ŔHÄ^_[]A\A]A^A_(4$HÄ(<$HÄD($HÄD( $HÄD($HÄD($HÄD($$HÄD(,$HÄD(4$HÄD(<$HÄHÄĂHÇŔëŠHŃřë…r Ž   llh`gpf0ePdŔbĐ`ŕ^đXPG>5,#ń‚GD:\Dev\Compile\Greenlet\greenlet-hg\platform\switch_x64_masm.obj7<Đ xMicrosoft (R) Macro Assembler@comp.id x•˙˙.text.data.pdata .xdata,.debug$S  - 8Bslp_save_state_asmslp_restore_state_asmslp_switch$xdatasymgreenlet-3.1.0/src/greenlet/platform/switch_x64_msvc.h000066400000000000000000000034151467007427000227550ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 26-Sep-02 Christian Tismer * again as a result of virtualized stack access, * the compiler used less registers. Needed to * explicit mention registers in order to get them saved. * Thanks to Jeff Senn for pointing this out and help. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for sparc * 01-Mar-02 Christian Tismer * Initial final version after lots of iterations for i386. */ /* Avoid alloca redefined warning on mingw64 */ #ifndef alloca #define alloca _alloca #endif #define STACK_REFPLUS 1 #define STACK_MAGIC 0 /* Use the generic support for an external assembly language slp_switch function. */ #define EXTERNAL_ASM #ifdef SLP_EVAL /* This always uses the external masm assembly file. */ #endif /* * further self-processing support */ /* we have IsBadReadPtr available, so we can peek at objects */ /* #define STACKLESS_SPY #ifdef IMPLEMENT_STACKLESSMODULE #include "Windows.h" #define CANNOT_READ_MEM(p, bytes) IsBadReadPtr(p, bytes) static int IS_ON_STACK(void*p) { int stackref; intptr_t stackbase = ((intptr_t)&stackref) & 0xfffff000; return (intptr_t)p >= stackbase && (intptr_t)p < stackbase + 0x00100000; } #endif */greenlet-3.1.0/src/greenlet/platform/switch_x86_msvc.h000066400000000000000000000310461467007427000227620ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 26-Sep-02 Christian Tismer * again as a result of virtualized stack access, * the compiler used less registers. Needed to * explicit mention registers in order to get them saved. * Thanks to Jeff Senn for pointing this out and help. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for sparc * 01-Mar-02 Christian Tismer * Initial final version after lots of iterations for i386. */ #define alloca _alloca #define STACK_REFPLUS 1 #ifdef SLP_EVAL #define STACK_MAGIC 0 /* Some magic to quell warnings and keep slp_switch() from crashing when built with VC90. Disable global optimizations, and the warning: frame pointer register 'ebp' modified by inline assembly code. We used to just disable global optimizations ("g") but upstream stackless Python, as well as stackman, turn off all optimizations. References: https://github.com/stackless-dev/stackman/blob/dbc72fe5207a2055e658c819fdeab9731dee78b9/stackman/platforms/switch_x86_msvc.h https://github.com/stackless-dev/stackless/blob/main-slp/Stackless/platf/switch_x86_msvc.h */ #define WIN32_LEAN_AND_MEAN #include #pragma optimize("", off) /* so that autos are stored on the stack */ #pragma warning(disable:4731) #pragma warning(disable:4733) /* disable warning about modifying FS[0] */ /** * Most modern compilers and environments handle C++ exceptions without any * special help from us. MSVC on 32-bit windows is an exception. There, C++ * exceptions are dealt with using Windows' Structured Exception Handling * (SEH). * * SEH is implemented as a singly linked list of nodes. The * head of this list is stored in the Thread Information Block, which itself * is pointed to from the FS register. It's the first field in the structure, * or offset 0, so we can access it using assembly FS:[0], or the compiler * intrinsics and field offset information from the headers (as we do below). * Somewhat unusually, the tail of the list doesn't have prev == NULL, it has * prev == 0xFFFFFFFF. * * SEH was designed for C, and traditionally uses the MSVC compiler * intrinsincs __try{}/__except{}. It is also utilized for C++ exceptions by * MSVC; there, every throw of a C++ exception raises a SEH error with the * ExceptionCode 0xE06D7363; the SEH handler list is then traversed to * deal with the exception. * * If the SEH list is corrupt, then when a C++ exception is thrown the program * will abruptly exit with exit code 1. This does not use std::terminate(), so * std::set_terminate() is useless to debug this. * * The SEH list is closely tied to the call stack; entering a function that * uses __try{} or most C++ functions will push a new handler onto the front * of the list. Returning from the function will remove the handler. Saving * and restoring the head node of the SEH list (FS:[0]) per-greenlet is NOT * ENOUGH to make SEH or exceptions work. * * Stack switching breaks SEH because the call stack no longer necessarily * matches the SEH list. For example, given greenlet A that switches to * greenlet B, at the moment of entering greenlet B, we will have any SEH * handlers from greenlet A on the SEH list; greenlet B can then add its own * handlers to the SEH list. When greenlet B switches back to greenlet A, * greenlet B's handlers would still be on the SEH stack, but when switch() * returns control to greenlet A, we have replaced the contents of the stack * in memory, so all the address that greenlet B added to the SEH list are now * invalid: part of the call stack has been unwound, but the SEH list was out * of sync with the call stack. The net effect is that exception handling * stops working. * * Thus, when switching greenlets, we need to be sure that the SEH list * matches the effective call stack, "cutting out" any handlers that were * pushed by the greenlet that switched out and which are no longer valid. * * The easiest way to do this is to capture the SEH list at the time the main * greenlet for a thread is created, and, when initially starting a greenlet, * start a new SEH list for it, which contains nothing but the handler * established for the new greenlet itself, with the tail being the handlers * for the main greenlet. If we then save and restore the SEH per-greenlet, * they won't interfere with each others SEH lists. (No greenlet can unwind * the call stack past the handlers established by the main greenlet). * * By observation, a new thread starts with three SEH handlers on the list. By * the time we get around to creating the main greenlet, though, there can be * many more, established by transient calls that lead to the creation of the * main greenlet. Therefore, 3 is a magic constant telling us when to perform * the initial slice. * * All of this can be debugged using a vectored exception handler, which * operates independently of the SEH handler list, and is called first. * Walking the SEH list at key points can also be helpful. * * References: * https://en.wikipedia.org/wiki/Win32_Thread_Information_Block * https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 * https://docs.microsoft.com/en-us/cpp/cpp/try-except-statement?view=msvc-160 * https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-160 * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling * https://docs.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler * https://bytepointer.com/resources/pietrek_crash_course_depths_of_win32_seh.htm */ #define GREENLET_NEEDS_EXCEPTION_STATE_SAVED typedef struct _GExceptionRegistration { struct _GExceptionRegistration* prev; void* handler_f; } GExceptionRegistration; static void slp_set_exception_state(const void *const seh_state) { // Because the stack from from which we do this is ALSO a handler, and // that one we want to keep, we need to relink the current SEH handler // frame to point to this one, cutting out the middle men, as it were. // // Entering a try block doesn't change the SEH frame, but entering a // function containing a try block does. GExceptionRegistration* current_seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); current_seh_state->prev = (GExceptionRegistration*)seh_state; } static GExceptionRegistration* x86_slp_get_third_oldest_handler() { GExceptionRegistration* a = NULL; /* Closest to the top */ GExceptionRegistration* b = NULL; /* second */ GExceptionRegistration* c = NULL; GExceptionRegistration* seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); a = b = c = seh_state; while (seh_state && seh_state != (GExceptionRegistration*)0xFFFFFFFF) { if ((void*)seh_state->prev < (void*)100) { fprintf(stderr, "\tERROR: Broken SEH chain.\n"); return NULL; } a = b; b = c; c = seh_state; seh_state = seh_state->prev; } return a ? a : (b ? b : c); } static void* slp_get_exception_state() { // XXX: There appear to be three SEH handlers on the stack already at the // start of the thread. Is that a guarantee? Almost certainly not. Yet in // all observed cases it has been three. This is consistent with // faulthandler off or on, and optimizations off or on. It may not be // consistent with other operating system versions, though: we only have // CI on one or two versions (don't ask what there are). // In theory we could capture the number of handlers on the chain when // PyInit__greenlet is called: there are probably only the default // handlers at that point (unless we're embedded and people have used // __try/__except or a C++ handler)? return x86_slp_get_third_oldest_handler(); } static int slp_switch(void) { /* MASM syntax is typically reversed from other assemblers. It is usually */ int *stackref, stsizediff; /* store the structured exception state for this stack */ DWORD seh_state = __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); __asm mov stackref, esp; /* modify EBX, ESI and EDI in order to get them preserved */ __asm mov ebx, ebx; __asm xchg esi, edi; { SLP_SAVE_STATE(stackref, stsizediff); __asm { mov eax, stsizediff add esp, eax add ebp, eax } SLP_RESTORE_STATE(); } __writefsdword(FIELD_OFFSET(NT_TIB, ExceptionList), seh_state); return 0; } /* re-enable ebp warning and global optimizations. */ #pragma optimize("", on) #pragma warning(default:4731) #pragma warning(default:4733) /* disable warning about modifying FS[0] */ #endif /* * further self-processing support */ /* we have IsBadReadPtr available, so we can peek at objects */ #define STACKLESS_SPY #ifdef GREENLET_DEBUG #define CANNOT_READ_MEM(p, bytes) IsBadReadPtr(p, bytes) static int IS_ON_STACK(void*p) { int stackref; int stackbase = ((int)&stackref) & 0xfffff000; return (int)p >= stackbase && (int)p < stackbase + 0x00100000; } static void x86_slp_show_seh_chain() { GExceptionRegistration* seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); fprintf(stderr, "====== SEH Chain ======\n"); while (seh_state && seh_state != (GExceptionRegistration*)0xFFFFFFFF) { fprintf(stderr, "\tSEH_chain addr: %p handler: %p prev: %p\n", seh_state, seh_state->handler_f, seh_state->prev); if ((void*)seh_state->prev < (void*)100) { fprintf(stderr, "\tERROR: Broken chain.\n"); break; } seh_state = seh_state->prev; } fprintf(stderr, "====== End SEH Chain ======\n"); fflush(NULL); return; } //addVectoredExceptionHandler constants: //CALL_FIRST means call this exception handler first; //CALL_LAST means call this exception handler last #define CALL_FIRST 1 #define CALL_LAST 0 LONG WINAPI GreenletVectorHandler(PEXCEPTION_POINTERS ExceptionInfo) { // We get one of these for every C++ exception, with code // E06D7363 // This is a special value that means "C++ exception from MSVC" // https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 // // Install in the module init function with: // AddVectoredExceptionHandler(CALL_FIRST, GreenletVectorHandler); PEXCEPTION_RECORD ExceptionRecord = ExceptionInfo->ExceptionRecord; fprintf(stderr, "GOT VECTORED EXCEPTION:\n" "\tExceptionCode : %p\n" "\tExceptionFlags : %p\n" "\tExceptionAddr : %p\n" "\tNumberparams : %ld\n", ExceptionRecord->ExceptionCode, ExceptionRecord->ExceptionFlags, ExceptionRecord->ExceptionAddress, ExceptionRecord->NumberParameters ); if (ExceptionRecord->ExceptionFlags & 1) { fprintf(stderr, "\t\tEH_NONCONTINUABLE\n" ); } if (ExceptionRecord->ExceptionFlags & 2) { fprintf(stderr, "\t\tEH_UNWINDING\n" ); } if (ExceptionRecord->ExceptionFlags & 4) { fprintf(stderr, "\t\tEH_EXIT_UNWIND\n" ); } if (ExceptionRecord->ExceptionFlags & 8) { fprintf(stderr, "\t\tEH_STACK_INVALID\n" ); } if (ExceptionRecord->ExceptionFlags & 0x10) { fprintf(stderr, "\t\tEH_NESTED_CALL\n" ); } if (ExceptionRecord->ExceptionFlags & 0x20) { fprintf(stderr, "\t\tEH_TARGET_UNWIND\n" ); } if (ExceptionRecord->ExceptionFlags & 0x40) { fprintf(stderr, "\t\tEH_COLLIDED_UNWIND\n" ); } fprintf(stderr, "\n"); fflush(NULL); for(DWORD i = 0; i < ExceptionRecord->NumberParameters; i++) { fprintf(stderr, "\t\t\tParam %ld: %lX\n", i, ExceptionRecord->ExceptionInformation[i]); } if (ExceptionRecord->NumberParameters == 3) { fprintf(stderr, "\tAbout to traverse SEH chain\n"); // C++ Exception records have 3 params. x86_slp_show_seh_chain(); } return EXCEPTION_CONTINUE_SEARCH; } #endif greenlet-3.1.0/src/greenlet/platform/switch_x86_unix.h000066400000000000000000000057631467007427000230040ustar00rootroot00000000000000/* * this is the internal transfer function. * * HISTORY * 3-May-13 Ralf Schmitt * Add support for strange GCC caller-save decisions * (ported from switch_aarch64_gcc.h) * 19-Aug-11 Alexey Borzenkov * Correctly save ebp, ebx and cw * 07-Sep-05 (py-dev mailing list discussion) * removed 'ebx' from the register-saved. !!!! WARNING !!!! * It means that this file can no longer be compiled statically! * It is now only suitable as part of a dynamic library! * 24-Nov-02 Christian Tismer * needed to add another magic constant to insure * that f in slp_eval_frame(PyFrameObject *f) * STACK_REFPLUS will probably be 1 in most cases. * gets included into the saved stack area. * 17-Sep-02 Christian Tismer * after virtualizing stack save/restore, the * stack size shrunk a bit. Needed to introduce * an adjustment STACK_MAGIC per platform. * 15-Sep-02 Gerd Woetzel * slightly changed framework for spark * 31-Avr-02 Armin Rigo * Added ebx, esi and edi register-saves. * 01-Mar-02 Samual M. Rushing * Ported from i386. */ #define STACK_REFPLUS 1 #ifdef SLP_EVAL /* #define STACK_MAGIC 3 */ /* the above works fine with gcc 2.96, but 2.95.3 wants this */ #define STACK_MAGIC 0 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) # define ATTR_NOCLONE __attribute__((noclone)) #else # define ATTR_NOCLONE #endif static int slp_switch(void) { int err; #ifdef _WIN32 void *seh; #endif void *ebp, *ebx; unsigned short cw; int *stackref, stsizediff; __asm__ volatile ("" : : : "esi", "edi"); __asm__ volatile ("fstcw %0" : "=m" (cw)); __asm__ volatile ("movl %%ebp, %0" : "=m" (ebp)); __asm__ volatile ("movl %%ebx, %0" : "=m" (ebx)); #ifdef _WIN32 __asm__ volatile ( "movl %%fs:0x0, %%eax\n" "movl %%eax, %0\n" : "=m" (seh) : : "eax"); #endif __asm__ ("movl %%esp, %0" : "=g" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "addl %0, %%esp\n" "addl %0, %%ebp\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); __asm__ volatile ("xorl %%eax, %%eax" : "=a" (err)); } #ifdef _WIN32 __asm__ volatile ( "movl %0, %%eax\n" "movl %%eax, %%fs:0x0\n" : : "m" (seh) : "eax"); #endif __asm__ volatile ("movl %0, %%ebx" : : "m" (ebx)); __asm__ volatile ("movl %0, %%ebp" : : "m" (ebp)); __asm__ volatile ("fldcw %0" : : "m" (cw)); __asm__ volatile ("" : : : "esi", "edi"); return err; } #endif /* * further self-processing support */ /* * if you want to add self-inspection tools, place them * here. See the x86_msvc for the necessary defines. * These features are highly experimental und not * essential yet. */ greenlet-3.1.0/src/greenlet/slp_platformselect.h000066400000000000000000000073661467007427000220120ustar00rootroot00000000000000/* * Platform Selection for Stackless Python */ #ifdef __cplusplus extern "C" { #endif #if defined(MS_WIN32) && !defined(MS_WIN64) && defined(_M_IX86) && defined(_MSC_VER) # include "platform/switch_x86_msvc.h" /* MS Visual Studio on X86 */ #elif defined(MS_WIN64) && defined(_M_X64) && defined(_MSC_VER) || defined(__MINGW64__) # include "platform/switch_x64_msvc.h" /* MS Visual Studio on X64 */ #elif defined(MS_WIN64) && defined(_M_ARM64) # include "platform/switch_arm64_msvc.h" /* MS Visual Studio on ARM64 */ #elif defined(__GNUC__) && defined(__amd64__) && defined(__ILP32__) # include "platform/switch_x32_unix.h" /* gcc on amd64 with x32 ABI */ #elif defined(__GNUC__) && defined(__amd64__) # include "platform/switch_amd64_unix.h" /* gcc on amd64 */ #elif defined(__GNUC__) && defined(__i386__) # include "platform/switch_x86_unix.h" /* gcc on X86 */ #elif defined(__GNUC__) && defined(__powerpc64__) && (defined(__linux__) || defined(__FreeBSD__)) # include "platform/switch_ppc64_linux.h" /* gcc on PowerPC 64-bit */ #elif defined(__GNUC__) && defined(__PPC__) && (defined(__linux__) || defined(__FreeBSD__)) # include "platform/switch_ppc_linux.h" /* gcc on PowerPC */ #elif defined(__GNUC__) && defined(__ppc__) && defined(__APPLE__) # include "platform/switch_ppc_macosx.h" /* Apple MacOS X on PowerPC */ #elif defined(__GNUC__) && defined(__powerpc64__) && defined(_AIX) # include "platform/switch_ppc64_aix.h" /* gcc on AIX/PowerPC 64-bit */ #elif defined(__GNUC__) && defined(_ARCH_PPC) && defined(_AIX) # include "platform/switch_ppc_aix.h" /* gcc on AIX/PowerPC */ #elif defined(__GNUC__) && defined(__powerpc__) && defined(__NetBSD__) #include "platform/switch_ppc_unix.h" /* gcc on NetBSD/powerpc */ #elif defined(__GNUC__) && defined(sparc) # include "platform/switch_sparc_sun_gcc.h" /* SunOS sparc with gcc */ #elif defined(__SUNPRO_C) && defined(sparc) && defined(sun) # iiclude "platform/switch_sparc_sun_gcc.h" /* SunStudio on amd64 */ #elif defined(__SUNPRO_C) && defined(__amd64__) && defined(sun) # include "platform/switch_amd64_unix.h" /* SunStudio on amd64 */ #elif defined(__SUNPRO_C) && defined(__i386__) && defined(sun) # include "platform/switch_x86_unix.h" /* SunStudio on x86 */ #elif defined(__GNUC__) && defined(__s390__) && defined(__linux__) # include "platform/switch_s390_unix.h" /* Linux/S390 */ #elif defined(__GNUC__) && defined(__s390x__) && defined(__linux__) # include "platform/switch_s390_unix.h" /* Linux/S390 zSeries (64-bit) */ #elif defined(__GNUC__) && defined(__arm__) # ifdef __APPLE__ # include # endif # if TARGET_OS_IPHONE # include "platform/switch_arm32_ios.h" /* iPhone OS on arm32 */ # else # include "platform/switch_arm32_gcc.h" /* gcc using arm32 */ # endif #elif defined(__GNUC__) && defined(__mips__) && defined(__linux__) # include "platform/switch_mips_unix.h" /* Linux/MIPS */ #elif defined(__GNUC__) && defined(__aarch64__) # include "platform/switch_aarch64_gcc.h" /* Aarch64 ABI */ #elif defined(__GNUC__) && defined(__mc68000__) # include "platform/switch_m68k_gcc.h" /* gcc on m68k */ #elif defined(__GNUC__) && defined(__csky__) #include "platform/switch_csky_gcc.h" /* gcc on csky */ # elif defined(__GNUC__) && defined(__riscv) # include "platform/switch_riscv_unix.h" /* gcc on RISC-V */ #elif defined(__GNUC__) && defined(__alpha__) # include "platform/switch_alpha_unix.h" /* gcc on DEC Alpha */ #elif defined(MS_WIN32) && defined(__llvm__) && defined(__aarch64__) # include "platform/switch_aarch64_gcc.h" /* LLVM Aarch64 ABI for Windows */ #elif defined(__GNUC__) && defined(__loongarch64) && defined(__linux__) # include "platform/switch_loongarch64_linux.h" /* LoongArch64 */ #elif defined(__GNUC__) && defined(__sh__) # include "platform/switch_sh_gcc.h" /* SuperH */ #endif #ifdef __cplusplus }; #endif greenlet-3.1.0/src/greenlet/tests/000077500000000000000000000000001467007427000170655ustar00rootroot00000000000000greenlet-3.1.0/src/greenlet/tests/__init__.py000066400000000000000000000220361467007427000212010ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Tests for greenlet. """ import os import sys import unittest from gc import collect from gc import get_objects from threading import active_count as active_thread_count from time import sleep from time import time import psutil from greenlet import greenlet as RawGreenlet from greenlet import getcurrent from greenlet._greenlet import get_pending_cleanup_count from greenlet._greenlet import get_total_main_greenlets from . import leakcheck PY312 = sys.version_info[:2] >= (3, 12) PY313 = sys.version_info[:2] >= (3, 13) WIN = sys.platform.startswith("win") RUNNING_ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') or RUNNING_ON_GITHUB_ACTIONS RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR') RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR RUNNING_ON_MANYLINUX = os.environ.get('GREENLET_MANYLINUX') class TestCaseMetaClass(type): # wrap each test method with # a) leak checks def __new__(cls, classname, bases, classDict): # pylint and pep8 fight over what this should be called (mcs or cls). # pylint gets it right, but we can't scope disable pep8, so we go with # its convention. # pylint: disable=bad-mcs-classmethod-argument check_totalrefcount = True # Python 3: must copy, we mutate the classDict. Interestingly enough, # it doesn't actually error out, but under 3.6 we wind up wrapping # and re-wrapping the same items over and over and over. for key, value in list(classDict.items()): if key.startswith('test') and callable(value): classDict.pop(key) if check_totalrefcount: value = leakcheck.wrap_refcount(value) classDict[key] = value return type.__new__(cls, classname, bases, classDict) class TestCase(TestCaseMetaClass( "NewBase", (unittest.TestCase,), {})): cleanup_attempt_sleep_duration = 0.001 cleanup_max_sleep_seconds = 1 def wait_for_pending_cleanups(self, initial_active_threads=None, initial_main_greenlets=None): initial_active_threads = initial_active_threads or self.threads_before_test initial_main_greenlets = initial_main_greenlets or self.main_greenlets_before_test sleep_time = self.cleanup_attempt_sleep_duration # NOTE: This is racy! A Python-level thread object may be dead # and gone, but the C thread may not yet have fired its # destructors and added to the queue. There's no particular # way to know that's about to happen. We try to watch the # Python threads to make sure they, at least, have gone away. # Counting the main greenlets, which we can easily do deterministically, # also helps. # Always sleep at least once to let other threads run sleep(sleep_time) quit_after = time() + self.cleanup_max_sleep_seconds # TODO: We could add an API that calls us back when a particular main greenlet is deleted? # It would have to drop the GIL while ( get_pending_cleanup_count() or active_thread_count() > initial_active_threads or (not self.expect_greenlet_leak and get_total_main_greenlets() > initial_main_greenlets)): sleep(sleep_time) if time() > quit_after: print("Time limit exceeded.") print("Threads: Waiting for only", initial_active_threads, "-->", active_thread_count()) print("MGlets : Waiting for only", initial_main_greenlets, "-->", get_total_main_greenlets()) break collect() def count_objects(self, kind=list, exact_kind=True): # pylint:disable=unidiomatic-typecheck # Collect the garbage. for _ in range(3): collect() if exact_kind: return sum( 1 for x in get_objects() if type(x) is kind ) # instances return sum( 1 for x in get_objects() if isinstance(x, kind) ) greenlets_before_test = 0 threads_before_test = 0 main_greenlets_before_test = 0 expect_greenlet_leak = False def count_greenlets(self): """ Find all the greenlets and subclasses tracked by the GC. """ return self.count_objects(RawGreenlet, False) def setUp(self): # Ensure the main greenlet exists, otherwise the first test # gets a false positive leak super().setUp() getcurrent() self.threads_before_test = active_thread_count() self.main_greenlets_before_test = get_total_main_greenlets() self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test) self.greenlets_before_test = self.count_greenlets() def tearDown(self): if getattr(self, 'skipTearDown', False): return self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test) super().tearDown() def get_expected_returncodes_for_aborted_process(self): import signal # The child should be aborted in an unusual way. On POSIX # platforms, this is done with abort() and signal.SIGABRT, # which is reflected in a negative return value; however, on # Windows, even though we observe the child print "Fatal # Python error: Aborted" and in older versions of the C # runtime "This application has requested the Runtime to # terminate it in an unusual way," it always has an exit code # of 3. This is interesting because 3 is the error code for # ERROR_PATH_NOT_FOUND; BUT: the C runtime abort() function # also uses this code. # # If we link to the static C library on Windows, the error # code changes to '0xc0000409' (hex(3221226505)), which # apparently is STATUS_STACK_BUFFER_OVERRUN; but "What this # means is that nowadays when you get a # STATUS_STACK_BUFFER_OVERRUN, it doesn’t actually mean that # there is a stack buffer overrun. It just means that the # application decided to terminate itself with great haste." # # # On windows, we've also seen '0xc0000005' (hex(3221225477)). # That's "Access Violation" # # See # https://devblogs.microsoft.com/oldnewthing/20110519-00/?p=10623 # and # https://docs.microsoft.com/en-us/previous-versions/k089yyh0(v=vs.140)?redirectedfrom=MSDN # and # https://devblogs.microsoft.com/oldnewthing/20190108-00/?p=100655 expected_exit = ( -signal.SIGABRT, # But beginning on Python 3.11, the faulthandler # that prints the C backtraces sometimes segfaults after # reporting the exception but before printing the stack. # This has only been seen on linux/gcc. -signal.SIGSEGV, ) if not WIN else ( 3, 0xc0000409, 0xc0000005, ) return expected_exit def get_process_uss(self): """ Return the current process's USS in bytes. uss is available on Linux, macOS, Windows. Also known as "Unique Set Size", this is the memory which is unique to a process and which would be freed if the process was terminated right now. If this is not supported by ``psutil``, this raises the :exc:`unittest.SkipTest` exception. """ try: return psutil.Process().memory_full_info().uss except AttributeError as e: raise unittest.SkipTest("uss not supported") from e def run_script(self, script_name, show_output=True): import subprocess script = os.path.join( os.path.dirname(__file__), script_name, ) try: return subprocess.check_output([sys.executable, script], encoding='utf-8', stderr=subprocess.STDOUT) except subprocess.CalledProcessError as ex: if show_output: print('-----') print('Failed to run script', script) print('~~~~~') print(ex.output) print('------') raise def assertScriptRaises(self, script_name, exitcodes=None): import subprocess with self.assertRaises(subprocess.CalledProcessError) as exc: output = self.run_script(script_name, show_output=False) __traceback_info__ = output # We're going to fail the assertion if we get here, at least # preserve the output in the traceback. if exitcodes is None: exitcodes = self.get_expected_returncodes_for_aborted_process() self.assertIn(exc.exception.returncode, exitcodes) return exc.exception greenlet-3.1.0/src/greenlet/tests/_test_extension.c000066400000000000000000000132151467007427000224450ustar00rootroot00000000000000/* This is a set of functions used by test_extension_interface.py to test the * Greenlet C API. */ #include "../greenlet.h" #ifndef Py_RETURN_NONE # define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None #endif #define TEST_MODULE_NAME "_test_extension" static PyObject* test_switch(PyObject* self, PyObject* greenlet) { PyObject* result = NULL; if (greenlet == NULL || !PyGreenlet_Check(greenlet)) { PyErr_BadArgument(); return NULL; } result = PyGreenlet_Switch((PyGreenlet*)greenlet, NULL, NULL); if (result == NULL) { if (!PyErr_Occurred()) { PyErr_SetString(PyExc_AssertionError, "greenlet.switch() failed for some reason."); } return NULL; } Py_INCREF(result); return result; } static PyObject* test_switch_kwargs(PyObject* self, PyObject* args, PyObject* kwargs) { PyGreenlet* g = NULL; PyObject* result = NULL; PyArg_ParseTuple(args, "O!", &PyGreenlet_Type, &g); if (g == NULL || !PyGreenlet_Check(g)) { PyErr_BadArgument(); return NULL; } result = PyGreenlet_Switch(g, NULL, kwargs); if (result == NULL) { if (!PyErr_Occurred()) { PyErr_SetString(PyExc_AssertionError, "greenlet.switch() failed for some reason."); } return NULL; } Py_XINCREF(result); return result; } static PyObject* test_getcurrent(PyObject* self) { PyGreenlet* g = PyGreenlet_GetCurrent(); if (g == NULL || !PyGreenlet_Check(g) || !PyGreenlet_ACTIVE(g)) { PyErr_SetString(PyExc_AssertionError, "getcurrent() returned an invalid greenlet"); Py_XDECREF(g); return NULL; } Py_DECREF(g); Py_RETURN_NONE; } static PyObject* test_setparent(PyObject* self, PyObject* arg) { PyGreenlet* current; PyGreenlet* greenlet = NULL; if (arg == NULL || !PyGreenlet_Check(arg)) { PyErr_BadArgument(); return NULL; } if ((current = PyGreenlet_GetCurrent()) == NULL) { return NULL; } greenlet = (PyGreenlet*)arg; if (PyGreenlet_SetParent(greenlet, current)) { Py_DECREF(current); return NULL; } Py_DECREF(current); if (PyGreenlet_Switch(greenlet, NULL, NULL) == NULL) { return NULL; } Py_RETURN_NONE; } static PyObject* test_new_greenlet(PyObject* self, PyObject* callable) { PyObject* result = NULL; PyGreenlet* greenlet = PyGreenlet_New(callable, NULL); if (!greenlet) { return NULL; } result = PyGreenlet_Switch(greenlet, NULL, NULL); Py_CLEAR(greenlet); if (result == NULL) { return NULL; } Py_INCREF(result); return result; } static PyObject* test_raise_dead_greenlet(PyObject* self) { PyErr_SetString(PyExc_GreenletExit, "test GreenletExit exception."); return NULL; } static PyObject* test_raise_greenlet_error(PyObject* self) { PyErr_SetString(PyExc_GreenletError, "test greenlet.error exception"); return NULL; } static PyObject* test_throw(PyObject* self, PyGreenlet* g) { const char msg[] = "take that sucka!"; PyObject* msg_obj = Py_BuildValue("s", msg); PyGreenlet_Throw(g, PyExc_ValueError, msg_obj, NULL); Py_DECREF(msg_obj); if (PyErr_Occurred()) { return NULL; } Py_RETURN_NONE; } static PyObject* test_throw_exact(PyObject* self, PyObject* args) { PyGreenlet* g = NULL; PyObject* typ = NULL; PyObject* val = NULL; PyObject* tb = NULL; if (!PyArg_ParseTuple(args, "OOOO:throw", &g, &typ, &val, &tb)) { return NULL; } PyGreenlet_Throw(g, typ, val, tb); if (PyErr_Occurred()) { return NULL; } Py_RETURN_NONE; } static PyMethodDef test_methods[] = { {"test_switch", (PyCFunction)test_switch, METH_O, "Switch to the provided greenlet sending provided arguments, and \n" "return the results."}, {"test_switch_kwargs", (PyCFunction)test_switch_kwargs, METH_VARARGS | METH_KEYWORDS, "Switch to the provided greenlet sending the provided keyword args."}, {"test_getcurrent", (PyCFunction)test_getcurrent, METH_NOARGS, "Test PyGreenlet_GetCurrent()"}, {"test_setparent", (PyCFunction)test_setparent, METH_O, "Se the parent of the provided greenlet and switch to it."}, {"test_new_greenlet", (PyCFunction)test_new_greenlet, METH_O, "Test PyGreenlet_New()"}, {"test_raise_dead_greenlet", (PyCFunction)test_raise_dead_greenlet, METH_NOARGS, "Just raise greenlet.GreenletExit"}, {"test_raise_greenlet_error", (PyCFunction)test_raise_greenlet_error, METH_NOARGS, "Just raise greenlet.error"}, {"test_throw", (PyCFunction)test_throw, METH_O, "Throw a ValueError at the provided greenlet"}, {"test_throw_exact", (PyCFunction)test_throw_exact, METH_VARARGS, "Throw exactly the arguments given at the provided greenlet"}, {NULL, NULL, 0, NULL} }; #define INITERROR return NULL static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, TEST_MODULE_NAME, NULL, 0, test_methods, NULL, NULL, NULL, NULL}; PyMODINIT_FUNC PyInit__test_extension(void) { PyObject* module = NULL; module = PyModule_Create(&moduledef); if (module == NULL) { return NULL; } PyGreenlet_Import(); return module; } greenlet-3.1.0/src/greenlet/tests/_test_extension_cpp.cpp000066400000000000000000000146451467007427000236570ustar00rootroot00000000000000/* This is a set of functions used to test C++ exceptions are not * broken during greenlet switches */ #include "../greenlet.h" #include "../greenlet_compiler_compat.hpp" #include #include struct exception_t { int depth; exception_t(int depth) : depth(depth) {} }; /* Functions are called via pointers to prevent inlining */ static void (*p_test_exception_throw_nonstd)(int depth); static void (*p_test_exception_throw_std)(); static PyObject* (*p_test_exception_switch_recurse)(int depth, int left); static void test_exception_throw_nonstd(int depth) { throw exception_t(depth); } static void test_exception_throw_std() { throw std::runtime_error("Thrown from an extension."); } static PyObject* test_exception_switch_recurse(int depth, int left) { if (left > 0) { return p_test_exception_switch_recurse(depth, left - 1); } PyObject* result = NULL; PyGreenlet* self = PyGreenlet_GetCurrent(); if (self == NULL) return NULL; try { if (PyGreenlet_Switch(PyGreenlet_GET_PARENT(self), NULL, NULL) == NULL) { Py_DECREF(self); return NULL; } p_test_exception_throw_nonstd(depth); PyErr_SetString(PyExc_RuntimeError, "throwing C++ exception didn't work"); } catch (const exception_t& e) { if (e.depth != depth) PyErr_SetString(PyExc_AssertionError, "depth mismatch"); else result = PyLong_FromLong(depth); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "unexpected C++ exception"); } Py_DECREF(self); return result; } /* test_exception_switch(int depth) * - recurses depth times * - switches to parent inside try/catch block * - throws an exception that (expected to be caught in the same function) * - verifies depth matches (exceptions shouldn't be caught in other greenlets) */ static PyObject* test_exception_switch(PyObject* UNUSED(self), PyObject* args) { int depth; if (!PyArg_ParseTuple(args, "i", &depth)) return NULL; return p_test_exception_switch_recurse(depth, depth); } static PyObject* py_test_exception_throw_nonstd(PyObject* self, PyObject* args) { if (!PyArg_ParseTuple(args, "")) return NULL; p_test_exception_throw_nonstd(0); PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw"); return NULL; } static PyObject* py_test_exception_throw_std(PyObject* self, PyObject* args) { if (!PyArg_ParseTuple(args, "")) return NULL; p_test_exception_throw_std(); PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw"); 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) * - creates new greenlet g2 to run g2func * - switches to g2 inside try/catch block * - verifies that no exception has been caught * * it is used together with test_exception_throw to verify that unhandled * exceptions thrown in one greenlet do not propagate to other greenlet nor * segfault the process. */ static PyObject* test_exception_switch_and_do_in_g2(PyObject* self, PyObject* args) { PyObject* g2func = NULL; PyObject* result = NULL; if (!PyArg_ParseTuple(args, "O", &g2func)) return NULL; PyGreenlet* g2 = PyGreenlet_New(g2func, NULL); if (!g2) { return NULL; } try { result = PyGreenlet_Switch(g2, NULL, NULL); if (!result) { return NULL; } } catch (const exception_t& e) { /* if we are here the memory can be already corrupted and the program * might crash before below py-level exception might become printed. * -> print something to stderr to make it clear that we had entered * this catch block. * See comments in inner_bootstrap() */ #if defined(WIN32) || defined(_WIN32) fprintf(stderr, "C++ exception unexpectedly caught in g1\n"); PyErr_SetString(PyExc_AssertionError, "C++ exception unexpectedly caught in g1"); Py_XDECREF(result); return NULL; #else throw; #endif } Py_XDECREF(result); Py_RETURN_NONE; } static PyMethodDef test_methods[] = { {"test_exception_switch", (PyCFunction)&test_exception_switch, METH_VARARGS, "Switches to parent twice, to test exception handling and greenlet " "switching."}, {"test_exception_switch_and_do_in_g2", (PyCFunction)&test_exception_switch_and_do_in_g2, METH_VARARGS, "Creates new greenlet g2 to run g2func and switches to it inside try/catch " "block. Used together with test_exception_throw to verify that unhandled " "C++ exceptions thrown in a greenlet doe not corrupt memory."}, {"test_exception_throw_nonstd", (PyCFunction)&py_test_exception_throw_nonstd, METH_VARARGS, "Throws non-standard C++ exception. Calling this function directly should abort the process." }, {"test_exception_throw_std", (PyCFunction)&py_test_exception_throw_std, 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} }; static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "greenlet.tests._test_extension_cpp", NULL, 0, test_methods, NULL, NULL, NULL, NULL}; PyMODINIT_FUNC PyInit__test_extension_cpp(void) { PyObject* module = NULL; module = PyModule_Create(&moduledef); if (module == NULL) { return NULL; } PyGreenlet_Import(); if (_PyGreenlet_API == NULL) { return NULL; } p_test_exception_throw_nonstd = test_exception_throw_nonstd; p_test_exception_throw_std = test_exception_throw_std; p_test_exception_switch_recurse = test_exception_switch_recurse; return module; } greenlet-3.1.0/src/greenlet/tests/fail_clearing_run_switches.py000066400000000000000000000023571467007427000250220ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ If we have a run callable passed to the constructor or set as an attribute, but we don't actually use that (because ``__getattribute__`` or the like interferes), then when we clear callable before beginning to run, there's an opportunity for Python code to run. """ import greenlet g = None main = greenlet.getcurrent() results = [] class RunCallable: def __del__(self): results.append(('RunCallable', '__del__')) main.switch('from RunCallable') class G(greenlet.greenlet): def __getattribute__(self, name): if name == 'run': results.append(('G.__getattribute__', 'run')) return run_func return object.__getattribute__(self, name) def run_func(): results.append(('run_func', 'enter')) g = G(RunCallable()) # Try to start G. It will get to the point where it deletes # its run callable C++ variable in inner_bootstrap. That triggers # the __del__ method, which switches back to main before g # actually even starts running. x = g.switch() results.append(('main: g.switch()', x)) # In the C++ code, this results in g->g_switch() appearing to return, even though # it has yet to run. print('In main with', x, flush=True) g.switch() print('RESULTS', results) greenlet-3.1.0/src/greenlet/tests/fail_cpp_exception.py000066400000000000000000000017311467007427000232740ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Helper for testing a C++ exception throw aborts the process. Takes one argument, the name of the function in :mod:`_test_extension_cpp` to call. """ import sys import greenlet from greenlet.tests import _test_extension_cpp print('fail_cpp_exception is running') def run_unhandled_exception_in_greenlet_aborts(): def _(): _test_extension_cpp.test_exception_switch_and_do_in_g2( _test_extension_cpp.test_exception_throw_nonstd ) g1 = greenlet.greenlet(_) g1.switch() func_name = sys.argv[1] try: func = getattr(_test_extension_cpp, func_name) except AttributeError: if func_name == run_unhandled_exception_in_greenlet_aborts.__name__: func = run_unhandled_exception_in_greenlet_aborts elif func_name == 'run_as_greenlet_target': g = greenlet.greenlet(_test_extension_cpp.test_exception_throw_std) func = g.switch else: raise print('raising', func, flush=True) func() greenlet-3.1.0/src/greenlet/tests/fail_initialstub_already_started.py000066400000000000000000000036511467007427000262150ustar00rootroot00000000000000""" Testing initialstub throwing an already started exception. """ import greenlet a = None b = None c = None main = greenlet.getcurrent() # If we switch into a dead greenlet, # we go looking for its parents. # if a parent is not yet started, we start it. results = [] def a_run(*args): #results.append('A') results.append(('Begin A', args)) def c_run(): results.append('Begin C') b.switch('From C') results.append('C done') class A(greenlet.greenlet): pass class B(greenlet.greenlet): doing_it = False def __getattribute__(self, name): if name == 'run' and not self.doing_it: assert greenlet.getcurrent() is c self.doing_it = True results.append('Switch to b from B.__getattribute__ in ' + type(greenlet.getcurrent()).__name__) b.switch() results.append('B.__getattribute__ back from main in ' + type(greenlet.getcurrent()).__name__) if name == 'run': name = '_B_run' return object.__getattribute__(self, name) def _B_run(self, *arg): results.append(('Begin B', arg)) results.append('_B_run switching to main') main.switch('From B') class C(greenlet.greenlet): pass a = A(a_run) b = B(parent=a) c = C(c_run, b) # Start a child; while running, it will start B, # but starting B will ALSO start B. result = c.switch() results.append(('main from c', result)) # Switch back to C, which was in the middle of switching # already. This will throw the ``GreenletStartedWhileInPython`` # exception, which results in parent A getting started (B is finished) c.switch() results.append(('A dead?', a.dead, 'B dead?', b.dead, 'C dead?', c.dead)) # A and B should both be dead now. assert a.dead assert b.dead assert not c.dead result = c.switch() results.append(('main from c.2', result)) # Now C is dead assert c.dead print("RESULTS:", results) greenlet-3.1.0/src/greenlet/tests/fail_slp_switch.py000066400000000000000000000010141467007427000226050ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ A test helper for seeing what happens when slp_switch() fails. """ # pragma: no cover import greenlet print('fail_slp_switch is running', flush=True) runs = [] def func(): runs.append(1) greenlet.getcurrent().parent.switch() runs.append(2) greenlet.getcurrent().parent.switch() runs.append(3) g = greenlet._greenlet.UnswitchableGreenlet(func) g.switch() assert runs == [1] g.switch() assert runs == [1, 2] g.force_slp_switch_error = True # This should crash. g.switch() greenlet-3.1.0/src/greenlet/tests/fail_switch_three_greenlets.py000066400000000000000000000016741467007427000252020ustar00rootroot00000000000000""" Uses a trace function to switch greenlets at unexpected times. In the trace function, we switch from the current greenlet to another greenlet, which switches """ import greenlet g1 = None g2 = None switch_to_g2 = False def tracefunc(*args): print('TRACE', *args) global switch_to_g2 if switch_to_g2: switch_to_g2 = False g2.switch() print('\tLEAVE TRACE', *args) def g1_run(): print('In g1_run') global switch_to_g2 switch_to_g2 = True from_parent = greenlet.getcurrent().parent.switch() print('Return to g1_run') print('From parent', from_parent) def g2_run(): #g1.switch() greenlet.getcurrent().parent.switch() greenlet.settrace(tracefunc) g1 = greenlet.greenlet(g1_run) g2 = greenlet.greenlet(g2_run) # This switch didn't actually finish! # And if it did, it would raise TypeError # because g1_run() doesn't take any arguments. g1.switch(1) print('Back in main') g1.switch(2) greenlet-3.1.0/src/greenlet/tests/fail_switch_three_greenlets2.py000066400000000000000000000024051467007427000252550ustar00rootroot00000000000000""" Like fail_switch_three_greenlets, but the call into g1_run would actually be valid. """ import greenlet g1 = None g2 = None switch_to_g2 = True results = [] def tracefunc(*args): results.append(('trace', args[0])) print('TRACE', *args) global switch_to_g2 if switch_to_g2: switch_to_g2 = False g2.switch('g2 from tracefunc') print('\tLEAVE TRACE', *args) def g1_run(arg): results.append(('g1 arg', arg)) print('In g1_run') from_parent = greenlet.getcurrent().parent.switch('from g1_run') results.append(('g1 from parent', from_parent)) return 'g1 done' def g2_run(arg): #g1.switch() results.append(('g2 arg', arg)) parent = greenlet.getcurrent().parent.switch('from g2_run') global switch_to_g2 switch_to_g2 = False results.append(('g2 from parent', parent)) return 'g2 done' greenlet.settrace(tracefunc) g1 = greenlet.greenlet(g1_run) g2 = greenlet.greenlet(g2_run) x = g1.switch('g1 from main') results.append(('main g1', x)) print('Back in main', x) x = g1.switch('g2 from main') results.append(('main g2', x)) print('back in amain again', x) x = g1.switch('g1 from main 2') results.append(('main g1.2', x)) x = g2.switch() results.append(('main g2.2', x)) print("RESULTS:", results) greenlet-3.1.0/src/greenlet/tests/fail_switch_two_greenlets.py000066400000000000000000000014611467007427000246760ustar00rootroot00000000000000""" Uses a trace function to switch greenlets at unexpected times. In the trace function, we switch from the current greenlet to another greenlet, which switches """ import greenlet g1 = None g2 = None switch_to_g2 = False def tracefunc(*args): print('TRACE', *args) global switch_to_g2 if switch_to_g2: switch_to_g2 = False g2.switch() print('\tLEAVE TRACE', *args) def g1_run(): print('In g1_run') global switch_to_g2 switch_to_g2 = True greenlet.getcurrent().parent.switch() print('Return to g1_run') print('Falling off end of g1_run') def g2_run(): g1.switch() print('Falling off end of g2') greenlet.settrace(tracefunc) g1 = greenlet.greenlet(g1_run) g2 = greenlet.greenlet(g2_run) g1.switch() print('Falling off end of main') g2.switch() greenlet-3.1.0/src/greenlet/tests/leakcheck.py000066400000000000000000000272741467007427000213650ustar00rootroot00000000000000# Copyright (c) 2018 gevent community # Copyright (c) 2021 greenlet community # # This was originally part of gevent's test suite. The main author # (Jason Madden) vendored a copy of it into greenlet. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function import os import sys import gc from functools import wraps import unittest import objgraph # graphviz 0.18 (Nov 7 2021), available only on Python 3.6 and newer, # has added type hints (sigh). It wants to use ``typing.Literal`` for # some stuff, but that's only available on Python 3.9+. If that's not # found, it creates a ``unittest.mock.MagicMock`` object and annotates # with that. These are GC'able objects, and doing almost *anything* # with them results in an explosion of objects. For example, trying to # compare them for equality creates new objects. This causes our # leakchecks to fail, with reports like: # # greenlet.tests.leakcheck.LeakCheckError: refcount increased by [337, 1333, 343, 430, 530, 643, 769] # _Call 1820 +546 # dict 4094 +76 # MagicProxy 585 +73 # tuple 2693 +66 # _CallList 24 +3 # weakref 1441 +1 # function 5996 +1 # type 736 +1 # cell 592 +1 # MagicMock 8 +1 # # To avoid this, we *could* filter this type of object out early. In # principle it could leak, but we don't use mocks in greenlet, so it # doesn't leak from us. However, a further issue is that ``MagicMock`` # objects have subobjects that are also GC'able, like ``_Call``, and # those create new mocks of their own too. So we'd have to filter them # as well, and they're not public. That's OK, we can workaround the # problem by being very careful to never compare by equality or other # user-defined operators, only using object identity or other builtin # functions. RUNNING_ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') or RUNNING_ON_GITHUB_ACTIONS RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR') RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR RUNNING_ON_MANYLINUX = os.environ.get('GREENLET_MANYLINUX') SKIP_LEAKCHECKS = RUNNING_ON_MANYLINUX or os.environ.get('GREENLET_SKIP_LEAKCHECKS') SKIP_FAILING_LEAKCHECKS = os.environ.get('GREENLET_SKIP_FAILING_LEAKCHECKS') ONLY_FAILING_LEAKCHECKS = os.environ.get('GREENLET_ONLY_FAILING_LEAKCHECKS') def ignores_leakcheck(func): """ Ignore the given object during leakchecks. Can be applied to a method, in which case the method will run, but will not be subject to leak checks. If applied to a class, the entire class will be skipped during leakchecks. This is intended to be used for classes that are very slow and cause problems such as test timeouts; typically it will be used for classes that are subclasses of a base class and specify variants of behaviour (such as pool sizes). """ func.ignore_leakcheck = True return func def fails_leakcheck(func): """ Mark that the function is known to leak. """ func.fails_leakcheck = True if SKIP_FAILING_LEAKCHECKS: func = unittest.skip("Skipping known failures")(func) return func class LeakCheckError(AssertionError): pass if hasattr(sys, 'getobjects'): # In a Python build with ``--with-trace-refs``, make objgraph # trace *all* the objects, not just those that are tracked by the # GC class _MockGC(object): def get_objects(self): return sys.getobjects(0) # pylint:disable=no-member def __getattr__(self, name): return getattr(gc, name) objgraph.gc = _MockGC() fails_strict_leakcheck = fails_leakcheck else: def fails_strict_leakcheck(func): """ Decorator for a function that is known to fail when running strict (``sys.getobjects()``) leakchecks. This type of leakcheck finds all objects, even those, such as strings, which are not tracked by the garbage collector. """ return func class ignores_types_in_strict_leakcheck(object): def __init__(self, types): self.types = types def __call__(self, func): func.leakcheck_ignore_types = self.types return func class _RefCountChecker(object): # Some builtin things that we ignore # XXX: Those things were ignored by gevent, but they're important here, # presumably. IGNORED_TYPES = () #(tuple, dict, types.FrameType, types.TracebackType) def __init__(self, testcase, function): self.testcase = testcase self.function = function self.deltas = [] self.peak_stats = {} self.ignored_types = () # The very first time we are called, we have already been # self.setUp() by the test runner, so we don't need to do it again. self.needs_setUp = False def _include_object_p(self, obj): # pylint:disable=too-many-return-statements # # See the comment block at the top. We must be careful to # avoid invoking user-defined operations. if obj is self: return False kind = type(obj) # ``self._include_object_p == obj`` returns NotImplemented # for non-function objects, which causes the interpreter # to try to reverse the order of arguments...which leads # to the explosion of mock objects. We don't want that, so we implement # the check manually. if kind == type(self._include_object_p): try: # pylint:disable=not-callable exact_method_equals = self._include_object_p.__eq__(obj) except AttributeError: # Python 2.7 methods may only have __cmp__, and that raises a # TypeError for non-method arguments # pylint:disable=no-member exact_method_equals = self._include_object_p.__cmp__(obj) == 0 if exact_method_equals is not NotImplemented and exact_method_equals: return False # Similarly, we need to check identity in our __dict__ to avoid mock explosions. for x in self.__dict__.values(): if obj is x: return False if kind in self.ignored_types or kind in self.IGNORED_TYPES: return False return True def _growth(self): return objgraph.growth(limit=None, peak_stats=self.peak_stats, filter=self._include_object_p) def _report_diff(self, growth): if not growth: return "" lines = [] width = max(len(name) for name, _, _ in growth) for name, count, delta in growth: lines.append('%-*s%9d %+9d' % (width, name, count, delta)) diff = '\n'.join(lines) return diff def _run_test(self, args, kwargs): gc_enabled = gc.isenabled() gc.disable() if self.needs_setUp: self.testcase.setUp() self.testcase.skipTearDown = False try: self.function(self.testcase, *args, **kwargs) finally: self.testcase.tearDown() self.testcase.doCleanups() self.testcase.skipTearDown = True self.needs_setUp = True if gc_enabled: gc.enable() 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: sys.modules['urllib.parse'].clear_cache() return self._growth() def _check_deltas(self, growth): # Return false when we have decided there is no leak, # true if we should keep looping, raises an assertion # if we have decided there is a leak. deltas = self.deltas if not deltas: # We haven't run yet, no data, keep looping return True if gc.garbage: raise LeakCheckError("Generated uncollectable garbage %r" % (gc.garbage,)) # the following configurations are classified as "no leak" # [0, 0] # [x, 0, 0] # [... a, b, c, d] where a+b+c+d = 0 # # the following configurations are classified as "leak" # [... z, z, z] where z > 0 if deltas[-2:] == [0, 0] and len(deltas) in (2, 3): return False if deltas[-3:] == [0, 0, 0]: return False if len(deltas) >= 4 and sum(deltas[-4:]) == 0: return False if len(deltas) >= 3 and deltas[-1] > 0 and deltas[-1] == deltas[-2] and deltas[-2] == deltas[-3]: diff = self._report_diff(growth) raise LeakCheckError('refcount increased by %r\n%s' % (deltas, diff)) # OK, we don't know for sure yet. Let's search for more if sum(deltas[-3:]) <= 0 or sum(deltas[-4:]) <= 0 or deltas[-4:].count(0) >= 2: # this is suspicious, so give a few more runs limit = 11 else: limit = 7 if len(deltas) >= limit: raise LeakCheckError('refcount increased by %r\n%s' % (deltas, self._report_diff(growth))) # We couldn't decide yet, keep going return True def __call__(self, args, kwargs): for _ in range(3): gc.collect() expect_failure = getattr(self.function, 'fails_leakcheck', False) if expect_failure: self.testcase.expect_greenlet_leak = True self.ignored_types = getattr(self.function, "leakcheck_ignore_types", ()) # Capture state before; the incremental will be # updated by each call to _growth_after growth = self._growth() try: while self._check_deltas(growth): self._run_test(args, kwargs) growth = self._growth_after() self.deltas.append(sum((stat[2] for stat in growth))) except LeakCheckError: if not expect_failure: raise else: if expect_failure: raise LeakCheckError("Expected %s to leak but it did not." % (self.function,)) def wrap_refcount(method): if getattr(method, 'ignore_leakcheck', False) or SKIP_LEAKCHECKS: return method @wraps(method) def wrapper(self, *args, **kwargs): # pylint:disable=too-many-branches if getattr(self, 'ignore_leakcheck', False): raise unittest.SkipTest("This class ignored during leakchecks") if ONLY_FAILING_LEAKCHECKS and not getattr(method, 'fails_leakcheck', False): raise unittest.SkipTest("Only running tests that fail leakchecks.") return _RefCountChecker(self, method)(args, kwargs) return wrapper greenlet-3.1.0/src/greenlet/tests/test_contextvars.py000066400000000000000000000241541467007427000230640ustar00rootroot00000000000000from __future__ import print_function import gc import sys import unittest from functools import partial from unittest import skipUnless from unittest import skipIf from greenlet import greenlet from greenlet import getcurrent from . import TestCase try: from contextvars import Context from contextvars import ContextVar from contextvars import copy_context # From the documentation: # # Important: Context Variables should be created at the top module # level and never in closures. Context objects hold strong # references to context variables which prevents context variables # from being properly garbage collected. ID_VAR = ContextVar("id", default=None) VAR_VAR = ContextVar("var", default=None) ContextVar = None except ImportError: Context = ContextVar = copy_context = None # We don't support testing if greenlet's built-in context var support is disabled. @skipUnless(Context is not None, "ContextVar not supported") class ContextVarsTests(TestCase): def _new_ctx_run(self, *args, **kwargs): return copy_context().run(*args, **kwargs) def _increment(self, greenlet_id, callback, counts, expect): ctx_var = ID_VAR if expect is None: self.assertIsNone(ctx_var.get()) else: self.assertEqual(ctx_var.get(), expect) ctx_var.set(greenlet_id) for _ in range(2): counts[ctx_var.get()] += 1 callback() def _test_context(self, propagate_by): # pylint:disable=too-many-branches ID_VAR.set(0) callback = getcurrent().switch counts = dict((i, 0) for i in range(5)) lets = [ greenlet(partial( partial( copy_context().run, self._increment ) if propagate_by == "run" else self._increment, greenlet_id=i, callback=callback, counts=counts, expect=( i - 1 if propagate_by == "share" else 0 if propagate_by in ("set", "run") else None ) )) for i in range(1, 5) ] for let in lets: if propagate_by == "set": let.gr_context = copy_context() elif propagate_by == "share": let.gr_context = getcurrent().gr_context for i in range(2): counts[ID_VAR.get()] += 1 for let in lets: let.switch() if propagate_by == "run": # Must leave each context.run() in reverse order of entry for let in reversed(lets): let.switch() else: # No context.run(), so fine to exit in any order. for let in lets: let.switch() for let in lets: self.assertTrue(let.dead) # When using run(), we leave the run() as the greenlet dies, # and there's no context "underneath". When not using run(), # gr_context still reflects the context the greenlet was # running in. if propagate_by == 'run': self.assertIsNone(let.gr_context) else: self.assertIsNotNone(let.gr_context) if propagate_by == "share": self.assertEqual(counts, {0: 1, 1: 1, 2: 1, 3: 1, 4: 6}) else: self.assertEqual(set(counts.values()), set([2])) def test_context_propagated_by_context_run(self): self._new_ctx_run(self._test_context, "run") def test_context_propagated_by_setting_attribute(self): self._new_ctx_run(self._test_context, "set") def test_context_not_propagated(self): self._new_ctx_run(self._test_context, None) def test_context_shared(self): self._new_ctx_run(self._test_context, "share") def test_break_ctxvars(self): let1 = greenlet(copy_context().run) let2 = greenlet(copy_context().run) let1.switch(getcurrent().switch) let2.switch(getcurrent().switch) # Since let2 entered the current context and let1 exits its own, the # interpreter emits: # RuntimeError: cannot exit context: thread state references a different context object let1.switch() def test_not_broken_if_using_attribute_instead_of_context_run(self): let1 = greenlet(getcurrent().switch) let2 = greenlet(getcurrent().switch) let1.gr_context = copy_context() let2.gr_context = copy_context() let1.switch() let2.switch() let1.switch() let2.switch() def test_context_assignment_while_running(self): # pylint:disable=too-many-statements ID_VAR.set(None) def target(): self.assertIsNone(ID_VAR.get()) self.assertIsNone(gr.gr_context) # Context is created on first use ID_VAR.set(1) self.assertIsInstance(gr.gr_context, Context) self.assertEqual(ID_VAR.get(), 1) self.assertEqual(gr.gr_context[ID_VAR], 1) # Clearing the context makes it get re-created as another # empty context when next used old_context = gr.gr_context gr.gr_context = None # assign None while running self.assertIsNone(ID_VAR.get()) self.assertIsNone(gr.gr_context) ID_VAR.set(2) self.assertIsInstance(gr.gr_context, Context) self.assertEqual(ID_VAR.get(), 2) self.assertEqual(gr.gr_context[ID_VAR], 2) new_context = gr.gr_context getcurrent().parent.switch((old_context, new_context)) # parent switches us back to old_context self.assertEqual(ID_VAR.get(), 1) gr.gr_context = new_context # assign non-None while running self.assertEqual(ID_VAR.get(), 2) getcurrent().parent.switch() # parent switches us back to no context self.assertIsNone(ID_VAR.get()) self.assertIsNone(gr.gr_context) gr.gr_context = old_context self.assertEqual(ID_VAR.get(), 1) getcurrent().parent.switch() # parent switches us back to no context self.assertIsNone(ID_VAR.get()) self.assertIsNone(gr.gr_context) gr = greenlet(target) with self.assertRaisesRegex(AttributeError, "can't delete context attribute"): del gr.gr_context self.assertIsNone(gr.gr_context) old_context, new_context = gr.switch() self.assertIs(new_context, gr.gr_context) self.assertEqual(old_context[ID_VAR], 1) self.assertEqual(new_context[ID_VAR], 2) self.assertEqual(new_context.run(ID_VAR.get), 2) gr.gr_context = old_context # assign non-None while suspended gr.switch() self.assertIs(gr.gr_context, new_context) gr.gr_context = None # assign None while suspended gr.switch() self.assertIs(gr.gr_context, old_context) gr.gr_context = None gr.switch() self.assertIsNone(gr.gr_context) # Make sure there are no reference leaks gr = None gc.collect() self.assertEqual(sys.getrefcount(old_context), 2) self.assertEqual(sys.getrefcount(new_context), 2) def test_context_assignment_different_thread(self): import threading VAR_VAR.set(None) ctx = Context() is_running = threading.Event() should_suspend = threading.Event() did_suspend = threading.Event() should_exit = threading.Event() holder = [] def greenlet_in_thread_fn(): VAR_VAR.set(1) is_running.set() should_suspend.wait(10) VAR_VAR.set(2) getcurrent().parent.switch() holder.append(VAR_VAR.get()) def thread_fn(): gr = greenlet(greenlet_in_thread_fn) gr.gr_context = ctx holder.append(gr) gr.switch() did_suspend.set() should_exit.wait(10) gr.switch() del gr greenlet() # trigger cleanup thread = threading.Thread(target=thread_fn, daemon=True) thread.start() is_running.wait(10) gr = holder[0] # Can't access or modify context if the greenlet is running # in a different thread with self.assertRaisesRegex(ValueError, "running in a different"): getattr(gr, 'gr_context') with self.assertRaisesRegex(ValueError, "running in a different"): gr.gr_context = None should_suspend.set() did_suspend.wait(10) # OK to access and modify context if greenlet is suspended self.assertIs(gr.gr_context, ctx) self.assertEqual(gr.gr_context[VAR_VAR], 2) gr.gr_context = None should_exit.set() thread.join(10) self.assertEqual(holder, [gr, None]) # Context can still be accessed/modified when greenlet is dead: self.assertIsNone(gr.gr_context) gr.gr_context = ctx self.assertIs(gr.gr_context, ctx) # Otherwise we leak greenlets on some platforms. # XXX: Should be able to do this automatically del holder[:] gr = None thread = None def test_context_assignment_wrong_type(self): g = greenlet() with self.assertRaisesRegex(TypeError, "greenlet context must be a contextvars.Context or None"): g.gr_context = self @skipIf(Context is not None, "ContextVar supported") class NoContextVarsTests(TestCase): def test_contextvars_errors(self): let1 = greenlet(getcurrent().switch) self.assertFalse(hasattr(let1, 'gr_context')) with self.assertRaises(AttributeError): getattr(let1, 'gr_context') with self.assertRaises(AttributeError): let1.gr_context = None let1.switch() with self.assertRaises(AttributeError): getattr(let1, 'gr_context') with self.assertRaises(AttributeError): let1.gr_context = None del let1 if __name__ == '__main__': unittest.main() greenlet-3.1.0/src/greenlet/tests/test_cpp.py000066400000000000000000000052601467007427000212630ustar00rootroot00000000000000from __future__ import print_function from __future__ import absolute_import import subprocess import unittest import greenlet from . import _test_extension_cpp from . import TestCase from . import WIN class CPPTests(TestCase): def test_exception_switch(self): greenlets = [] for i in range(4): g = greenlet.greenlet(_test_extension_cpp.test_exception_switch) g.switch(i) greenlets.append(g) for i, g in enumerate(greenlets): self.assertEqual(g.switch(), i) def _do_test_unhandled_exception(self, target): import os import sys script = os.path.join( os.path.dirname(__file__), 'fail_cpp_exception.py', ) args = [sys.executable, script, target.__name__ if not isinstance(target, str) else target] __traceback_info__ = args with self.assertRaises(subprocess.CalledProcessError) as exc: subprocess.check_output( args, encoding='utf-8', stderr=subprocess.STDOUT ) ex = exc.exception expected_exit = self.get_expected_returncodes_for_aborted_process() self.assertIn(ex.returncode, expected_exit) self.assertIn('fail_cpp_exception is running', ex.output) return ex.output def test_unhandled_nonstd_exception_aborts(self): # verify that plain unhandled throw aborts self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_nonstd) def test_unhandled_std_exception_aborts(self): # verify that plain unhandled throw aborts self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_std) @unittest.skipIf(WIN, "XXX: This does not crash on Windows") # Meaning the exception is getting lost somewhere... def test_unhandled_std_exception_as_greenlet_function_aborts(self): # verify that plain unhandled throw aborts output = self._do_test_unhandled_exception('run_as_greenlet_target') self.assertIn( # We really expect this to be prefixed with "greenlet: Unhandled C++ exception:" # as added by our handler for std::exception (see TUserGreenlet.cpp), but # that's not correct everywhere --- our handler never runs before std::terminate # gets called (for example, on arm32). 'Thrown from an extension.', output ) def test_unhandled_exception_in_greenlet_aborts(self): # verify that unhandled throw called in greenlet aborts too self._do_test_unhandled_exception('run_unhandled_exception_in_greenlet_aborts') if __name__ == '__main__': unittest.main() greenlet-3.1.0/src/greenlet/tests/test_extension_interface.py000066400000000000000000000073651467007427000245450ustar00rootroot00000000000000from __future__ import print_function from __future__ import absolute_import import sys import greenlet from . import _test_extension from . import TestCase # pylint:disable=c-extension-no-member class CAPITests(TestCase): def test_switch(self): self.assertEqual( 50, _test_extension.test_switch(greenlet.greenlet(lambda: 50))) def test_switch_kwargs(self): def adder(x, y): return x * y g = greenlet.greenlet(adder) self.assertEqual(6, _test_extension.test_switch_kwargs(g, x=3, y=2)) def test_setparent(self): # pylint:disable=disallowed-name def foo(): def bar(): greenlet.getcurrent().parent.switch() # This final switch should go back to the main greenlet, since # the test_setparent() function in the C extension should have # reparented this greenlet. greenlet.getcurrent().parent.switch() raise AssertionError("Should never have reached this code") child = greenlet.greenlet(bar) child.switch() greenlet.getcurrent().parent.switch(child) greenlet.getcurrent().parent.throw( AssertionError("Should never reach this code")) foo_child = greenlet.greenlet(foo).switch() self.assertEqual(None, _test_extension.test_setparent(foo_child)) def test_getcurrent(self): _test_extension.test_getcurrent() def test_new_greenlet(self): self.assertEqual(-15, _test_extension.test_new_greenlet(lambda: -15)) def test_raise_greenlet_dead(self): self.assertRaises( greenlet.GreenletExit, _test_extension.test_raise_dead_greenlet) def test_raise_greenlet_error(self): self.assertRaises( greenlet.error, _test_extension.test_raise_greenlet_error) def test_throw(self): seen = [] def foo(): # pylint:disable=disallowed-name try: greenlet.getcurrent().parent.switch() except ValueError: seen.append(sys.exc_info()[1]) except greenlet.GreenletExit: raise AssertionError g = greenlet.greenlet(foo) g.switch() _test_extension.test_throw(g) self.assertEqual(len(seen), 1) self.assertTrue( isinstance(seen[0], ValueError), "ValueError was not raised in foo()") self.assertEqual( str(seen[0]), 'take that sucka!', "message doesn't match") def test_non_traceback_param(self): with self.assertRaises(TypeError) as exc: _test_extension.test_throw_exact( greenlet.getcurrent(), Exception, Exception(), self ) self.assertEqual(str(exc.exception), "throw() third argument must be a traceback object") def test_instance_of_wrong_type(self): with self.assertRaises(TypeError) as exc: _test_extension.test_throw_exact( greenlet.getcurrent(), Exception(), BaseException(), None, ) self.assertEqual(str(exc.exception), "instance exception may not have a separate value") def test_not_throwable(self): with self.assertRaises(TypeError) as exc: _test_extension.test_throw_exact( greenlet.getcurrent(), "abc", None, None, ) self.assertEqual(str(exc.exception), "exceptions must be classes, or instances, not str") if __name__ == '__main__': import unittest unittest.main() greenlet-3.1.0/src/greenlet/tests/test_gc.py000066400000000000000000000055531467007427000210770ustar00rootroot00000000000000import gc import weakref import greenlet from . import TestCase from .leakcheck import fails_leakcheck # These only work with greenlet gc support # which is no longer optional. assert greenlet.GREENLET_USE_GC class GCTests(TestCase): def test_dead_circular_ref(self): o = weakref.ref(greenlet.greenlet(greenlet.getcurrent).switch()) gc.collect() if o() is not None: import sys print("O IS NOT NONE.", sys.getrefcount(o())) self.assertIsNone(o()) self.assertFalse(gc.garbage, gc.garbage) def test_circular_greenlet(self): class circular_greenlet(greenlet.greenlet): self = None o = circular_greenlet() o.self = o o = weakref.ref(o) gc.collect() self.assertIsNone(o()) self.assertFalse(gc.garbage, gc.garbage) def test_inactive_ref(self): class inactive_greenlet(greenlet.greenlet): def __init__(self): greenlet.greenlet.__init__(self, run=self.run) def run(self): pass o = inactive_greenlet() o = weakref.ref(o) gc.collect() self.assertIsNone(o()) self.assertFalse(gc.garbage, gc.garbage) @fails_leakcheck def test_finalizer_crash(self): # This test is designed to crash when active greenlets # are made garbage collectable, until the underlying # problem is resolved. How does it work: # - order of object creation is important # - array is created first, so it is moved to unreachable first # - we create a cycle between a greenlet and this array # - we create an object that participates in gc, is only # referenced by a greenlet, and would corrupt gc lists # on destruction, the easiest is to use an object with # a finalizer # - because array is the first object in unreachable it is # cleared first, which causes all references to greenlet # to disappear and causes greenlet to be destroyed, but since # it is still live it causes a switch during gc, which causes # an object with finalizer to be destroyed, which causes stack # corruption and then a crash class object_with_finalizer(object): def __del__(self): pass array = [] parent = greenlet.getcurrent() def greenlet_body(): greenlet.getcurrent().object = object_with_finalizer() try: parent.switch() except greenlet.GreenletExit: print("Got greenlet exit!") finally: del greenlet.getcurrent().object g = greenlet.greenlet(greenlet_body) g.array = array array.append(g) g.switch() del array del g greenlet.getcurrent() gc.collect() greenlet-3.1.0/src/greenlet/tests/test_generator.py000066400000000000000000000023301467007427000224620ustar00rootroot00000000000000 from greenlet import greenlet from . import TestCase class genlet(greenlet): parent = None def __init__(self, *args, **kwds): self.args = args self.kwds = kwds def run(self): fn, = self.fn fn(*self.args, **self.kwds) def __iter__(self): return self def __next__(self): self.parent = greenlet.getcurrent() result = self.switch() if self: return result raise StopIteration next = __next__ def Yield(value): g = greenlet.getcurrent() while not isinstance(g, genlet): if g is None: raise RuntimeError('yield outside a genlet') g = g.parent g.parent.switch(value) def generator(func): class Generator(genlet): fn = (func,) return Generator # ____________________________________________________________ class GeneratorTests(TestCase): def test_generator(self): seen = [] def g(n): for i in range(n): seen.append(i) Yield(i) g = generator(g) for _ in range(3): for j in g(5): seen.append(j) self.assertEqual(seen, 3 * [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) greenlet-3.1.0/src/greenlet/tests/test_generator_nested.py000066400000000000000000000072061467007427000240330ustar00rootroot00000000000000 from greenlet import greenlet from . import TestCase from .leakcheck import fails_leakcheck class genlet(greenlet): parent = None def __init__(self, *args, **kwds): self.args = args self.kwds = kwds self.child = None def run(self): # Note the function is packed in a tuple # to avoid creating a bound method for it. fn, = self.fn fn(*self.args, **self.kwds) def __iter__(self): return self def set_child(self, child): self.child = child def __next__(self): if self.child: child = self.child while child.child: tmp = child child = child.child tmp.child = None result = child.switch() else: self.parent = greenlet.getcurrent() result = self.switch() if self: return result raise StopIteration next = __next__ def Yield(value, level=1): g = greenlet.getcurrent() while level != 0: if not isinstance(g, genlet): raise RuntimeError('yield outside a genlet') if level > 1: g.parent.set_child(g) g = g.parent level -= 1 g.switch(value) def Genlet(func): class TheGenlet(genlet): fn = (func,) return TheGenlet # ____________________________________________________________ def g1(n, seen): for i in range(n): seen.append(i + 1) yield i def g2(n, seen): for i in range(n): seen.append(i + 1) Yield(i) g2 = Genlet(g2) def nested(i): Yield(i) def g3(n, seen): for i in range(n): seen.append(i + 1) nested(i) g3 = Genlet(g3) def a(n): if n == 0: return for ii in ax(n - 1): Yield(ii) Yield(n) ax = Genlet(a) def perms(l): if len(l) > 1: for e in l: # No syntactical sugar for generator expressions x = [Yield([e] + p) for p in perms([x for x in l if x != e])] assert x else: Yield(l) perms = Genlet(perms) def gr1(n): for ii in range(1, n): Yield(ii) Yield(ii * ii, 2) gr1 = Genlet(gr1) def gr2(n, seen): for ii in gr1(n): seen.append(ii) gr2 = Genlet(gr2) class NestedGeneratorTests(TestCase): def test_layered_genlets(self): seen = [] for ii in gr2(5, seen): seen.append(ii) self.assertEqual(seen, [1, 1, 2, 4, 3, 9, 4, 16]) @fails_leakcheck def test_permutations(self): gen_perms = perms(list(range(4))) permutations = list(gen_perms) self.assertEqual(len(permutations), 4 * 3 * 2 * 1) self.assertIn([0, 1, 2, 3], permutations) self.assertIn([3, 2, 1, 0], permutations) res = [] for ii in zip(perms(list(range(4))), perms(list(range(3)))): res.append(ii) self.assertEqual( res, [([0, 1, 2, 3], [0, 1, 2]), ([0, 1, 3, 2], [0, 2, 1]), ([0, 2, 1, 3], [1, 0, 2]), ([0, 2, 3, 1], [1, 2, 0]), ([0, 3, 1, 2], [2, 0, 1]), ([0, 3, 2, 1], [2, 1, 0])]) # XXX Test to make sure we are working as a generator expression def test_genlet_simple(self): for g in g1, g2, g3: seen = [] for _ in range(3): for j in g(5, seen): seen.append(j) self.assertEqual(seen, 3 * [1, 0, 2, 1, 3, 2, 4, 3, 5, 4]) def test_genlet_bad(self): try: Yield(10) except RuntimeError: pass def test_nested_genlets(self): seen = [] for ii in ax(5): seen.append(ii) greenlet-3.1.0/src/greenlet/tests/test_greenlet.py000066400000000000000000001315331467007427000223110ustar00rootroot00000000000000import gc import sys import time import threading import unittest from abc import ABCMeta from abc import abstractmethod import greenlet from greenlet import greenlet as RawGreenlet from . import TestCase from . import RUNNING_ON_MANYLINUX from . import PY313 from .leakcheck import fails_leakcheck # 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 def fmain(seen): try: greenlet.getcurrent().parent.switch() except: seen.append(sys.exc_info()[0]) raise raise SomeError def send_exception(g, exc): # note: send_exception(g, exc) can be now done with g.throw(exc). # the purpose of this test is to explicitly check the propagation rules. def crasher(exc): raise exc g1 = RawGreenlet(crasher, parent=g) g1.switch(exc) class TestGreenlet(TestCase): def _do_simple_test(self): lst = [] def f(): lst.append(1) greenlet.getcurrent().parent.switch() lst.append(3) g = RawGreenlet(f) lst.append(0) g.switch() lst.append(2) g.switch() lst.append(4) self.assertEqual(lst, list(range(5))) def test_simple(self): self._do_simple_test() def test_switch_no_run_raises_AttributeError(self): g = RawGreenlet() with self.assertRaises(AttributeError) as exc: g.switch() self.assertIn("run", str(exc.exception)) def test_throw_no_run_raises_AttributeError(self): g = RawGreenlet() with self.assertRaises(AttributeError) as exc: g.throw(SomeError) self.assertIn("run", str(exc.exception)) def test_parent_equals_None(self): g = RawGreenlet(parent=None) self.assertIsNotNone(g) self.assertIs(g.parent, greenlet.getcurrent()) def test_run_equals_None(self): g = RawGreenlet(run=None) self.assertIsNotNone(g) self.assertIsNone(g.run) def test_two_children(self): lst = [] def f(): lst.append(1) greenlet.getcurrent().parent.switch() lst.extend([1, 1]) g = RawGreenlet(f) h = RawGreenlet(f) g.switch() self.assertEqual(len(lst), 1) h.switch() self.assertEqual(len(lst), 2) h.switch() self.assertEqual(len(lst), 4) self.assertEqual(h.dead, True) g.switch() self.assertEqual(len(lst), 6) self.assertEqual(g.dead, True) def test_two_recursive_children(self): lst = [] def f(): lst.append('b') greenlet.getcurrent().parent.switch() def g(): lst.append('a') g = RawGreenlet(f) g.switch() lst.append('c') g = RawGreenlet(g) self.assertEqual(sys.getrefcount(g), 2) g.switch() self.assertEqual(lst, ['a', 'b', 'c']) # Just the one in this frame, plus the one on the stack we pass to the function self.assertEqual(sys.getrefcount(g), 2) def test_threads(self): success = [] def f(): self._do_simple_test() success.append(True) ths = [threading.Thread(target=f) for i in range(10)] for th in ths: th.start() for th in ths: th.join(10) self.assertEqual(len(success), len(ths)) def test_exception(self): seen = [] g1 = RawGreenlet(fmain) g2 = RawGreenlet(fmain) g1.switch(seen) g2.switch(seen) g2.parent = g1 self.assertEqual(seen, []) #with self.assertRaises(SomeError): # p("***Switching back") # g2.switch() # Creating this as a bound method can reveal bugs that # are hidden on newer versions of Python that avoid creating # bound methods for direct expressions; IOW, don't use the `with` # form! self.assertRaises(SomeError, g2.switch) self.assertEqual(seen, [SomeError]) value = g2.switch() self.assertEqual(value, ()) self.assertEqual(seen, [SomeError]) value = g2.switch(25) self.assertEqual(value, 25) self.assertEqual(seen, [SomeError]) def test_send_exception(self): seen = [] g1 = RawGreenlet(fmain) g1.switch(seen) self.assertRaises(KeyError, send_exception, g1, KeyError) self.assertEqual(seen, [KeyError]) def test_dealloc(self): seen = [] g1 = RawGreenlet(fmain) g2 = RawGreenlet(fmain) g1.switch(seen) g2.switch(seen) self.assertEqual(seen, []) del g1 gc.collect() self.assertEqual(seen, [greenlet.GreenletExit]) del g2 gc.collect() self.assertEqual(seen, [greenlet.GreenletExit, greenlet.GreenletExit]) def test_dealloc_catches_GreenletExit_throws_other(self): def run(): try: greenlet.getcurrent().parent.switch() except greenlet.GreenletExit: raise SomeError from None g = RawGreenlet(run) g.switch() # Destroying the only reference to the greenlet causes it # to get GreenletExit; when it in turn raises, even though we're the parent # we don't get the exception, it just gets printed. # When we run on 3.8 only, we can use sys.unraisablehook oldstderr = sys.stderr from io import StringIO stderr = sys.stderr = StringIO() try: del g finally: sys.stderr = oldstderr v = stderr.getvalue() self.assertIn("Exception", v) self.assertIn('ignored', v) self.assertIn("SomeError", v) @unittest.skipIf( PY313 and RUNNING_ON_MANYLINUX, "Sometimes flaky (getting one GreenletExit in the second list)" # Probably due to funky timing interactions? # TODO: FIXME Make that work. ) def test_dealloc_other_thread(self): seen = [] someref = [] bg_glet_created_running_and_no_longer_ref_in_bg = threading.Event() fg_ref_released = threading.Event() bg_should_be_clear = threading.Event() ok_to_exit_bg_thread = threading.Event() def f(): g1 = RawGreenlet(fmain) g1.switch(seen) someref.append(g1) del g1 gc.collect() bg_glet_created_running_and_no_longer_ref_in_bg.set() fg_ref_released.wait(3) RawGreenlet() # trigger release bg_should_be_clear.set() ok_to_exit_bg_thread.wait(3) RawGreenlet() # One more time t = threading.Thread(target=f) t.start() bg_glet_created_running_and_no_longer_ref_in_bg.wait(10) self.assertEqual(seen, []) self.assertEqual(len(someref), 1) del someref[:] gc.collect() # g1 is not released immediately because it's from another thread self.assertEqual(seen, []) fg_ref_released.set() bg_should_be_clear.wait(3) try: self.assertEqual(seen, [greenlet.GreenletExit]) finally: ok_to_exit_bg_thread.set() t.join(10) del seen[:] del someref[:] def test_frame(self): def f1(): f = sys._getframe(0) # pylint:disable=protected-access self.assertEqual(f.f_back, None) greenlet.getcurrent().parent.switch(f) return "meaning of life" g = RawGreenlet(f1) frame = g.switch() self.assertTrue(frame is g.gr_frame) self.assertTrue(g) from_g = g.switch() self.assertFalse(g) self.assertEqual(from_g, 'meaning of life') self.assertEqual(g.gr_frame, None) def test_thread_bug(self): def runner(x): g = RawGreenlet(lambda: time.sleep(x)) g.switch() t1 = threading.Thread(target=runner, args=(0.2,)) t2 = threading.Thread(target=runner, args=(0.3,)) t1.start() t2.start() t1.join(10) t2.join(10) def test_switch_kwargs(self): def run(a, b): self.assertEqual(a, 4) self.assertEqual(b, 2) return 42 x = RawGreenlet(run).switch(a=4, b=2) self.assertEqual(x, 42) def test_switch_kwargs_to_parent(self): def run(x): greenlet.getcurrent().parent.switch(x=x) greenlet.getcurrent().parent.switch(2, x=3) return x, x ** 2 g = RawGreenlet(run) self.assertEqual({'x': 3}, g.switch(3)) self.assertEqual(((2,), {'x': 3}), g.switch()) self.assertEqual((3, 9), g.switch()) def test_switch_to_another_thread(self): data = {} created_event = threading.Event() done_event = threading.Event() def run(): data['g'] = RawGreenlet(lambda: None) created_event.set() done_event.wait(10) thread = threading.Thread(target=run) thread.start() created_event.wait(10) with self.assertRaises(greenlet.error): data['g'].switch() done_event.set() thread.join(10) # XXX: Should handle this automatically data.clear() def test_exc_state(self): def f(): try: raise ValueError('fun') except: # pylint:disable=bare-except exc_info = sys.exc_info() RawGreenlet(h).switch() self.assertEqual(exc_info, sys.exc_info()) def h(): self.assertEqual(sys.exc_info(), (None, None, None)) RawGreenlet(f).switch() def test_instance_dict(self): def f(): greenlet.getcurrent().test = 42 def deldict(g): del g.__dict__ def setdict(g, value): g.__dict__ = value g = RawGreenlet(f) self.assertEqual(g.__dict__, {}) g.switch() self.assertEqual(g.test, 42) self.assertEqual(g.__dict__, {'test': 42}) g.__dict__ = g.__dict__ self.assertEqual(g.__dict__, {'test': 42}) self.assertRaises(TypeError, deldict, g) self.assertRaises(TypeError, setdict, g, 42) def test_running_greenlet_has_no_run(self): has_run = [] def func(): has_run.append( hasattr(greenlet.getcurrent(), 'run') ) g = RawGreenlet(func) g.switch() self.assertEqual(has_run, [False]) def test_deepcopy(self): import copy self.assertRaises(TypeError, copy.copy, RawGreenlet()) self.assertRaises(TypeError, copy.deepcopy, RawGreenlet()) def test_parent_restored_on_kill(self): hub = RawGreenlet(lambda: None) main = greenlet.getcurrent() result = [] def worker(): try: # Wait to be killed by going back to the test. main.switch() except greenlet.GreenletExit: # Resurrect and switch to parent result.append(greenlet.getcurrent().parent) result.append(greenlet.getcurrent()) hub.switch() g = RawGreenlet(worker, parent=hub) g.switch() # delete the only reference, thereby raising GreenletExit del g self.assertTrue(result) self.assertIs(result[0], main) self.assertIs(result[1].parent, hub) # Delete them, thereby breaking the cycle between the greenlet # and the frame, which otherwise would never be collectable # XXX: We should be able to automatically fix this. del result[:] hub = None main = None def test_parent_return_failure(self): # No run causes AttributeError on switch g1 = RawGreenlet() # Greenlet that implicitly switches to parent g2 = RawGreenlet(lambda: None, parent=g1) # AttributeError should propagate to us, no fatal errors with self.assertRaises(AttributeError): g2.switch() def test_throw_exception_not_lost(self): class mygreenlet(RawGreenlet): def __getattribute__(self, name): try: raise Exception # pylint:disable=broad-exception-raised except: # pylint:disable=bare-except pass return RawGreenlet.__getattribute__(self, name) g = mygreenlet(lambda: None) self.assertRaises(SomeError, g.throw, SomeError()) @fails_leakcheck def _do_test_throw_to_dead_thread_doesnt_crash(self, wait_for_cleanup=False): result = [] def worker(): greenlet.getcurrent().parent.switch() def creator(): g = RawGreenlet(worker) g.switch() result.append(g) if wait_for_cleanup: # Let this greenlet eventually be cleaned up. g.switch() greenlet.getcurrent() t = threading.Thread(target=creator) t.start() t.join(10) del t # But, depending on the operating system, the thread # deallocator may not actually have run yet! So we can't be # sure about the error message unless we wait. if wait_for_cleanup: self.wait_for_pending_cleanups() with self.assertRaises(greenlet.error) as exc: result[0].throw(SomeError) if not wait_for_cleanup: self.assertIn( str(exc.exception), [ "cannot switch to a different thread (which happens to have exited)", "cannot switch to a different thread" ] ) else: self.assertEqual( str(exc.exception), "cannot switch to a different thread (which happens to have exited)", ) if hasattr(result[0].gr_frame, 'clear'): # The frame is actually executing (it thinks), we can't clear it. with self.assertRaises(RuntimeError): result[0].gr_frame.clear() # Unfortunately, this doesn't actually clear the references, they're in the # fast local array. if not wait_for_cleanup: # f_locals has no clear method in Python 3.13 if hasattr(result[0].gr_frame.f_locals, 'clear'): result[0].gr_frame.f_locals.clear() else: self.assertIsNone(result[0].gr_frame) del creator worker = None del result[:] # XXX: we ought to be able to automatically fix this. # See issue 252 self.expect_greenlet_leak = True # direct us not to wait for it to go away @fails_leakcheck def test_throw_to_dead_thread_doesnt_crash(self): self._do_test_throw_to_dead_thread_doesnt_crash() def test_throw_to_dead_thread_doesnt_crash_wait(self): self._do_test_throw_to_dead_thread_doesnt_crash(True) @fails_leakcheck def test_recursive_startup(self): class convoluted(RawGreenlet): def __init__(self): RawGreenlet.__init__(self) self.count = 0 def __getattribute__(self, name): if name == 'run' and self.count == 0: self.count = 1 self.switch(43) return RawGreenlet.__getattribute__(self, name) def run(self, value): while True: self.parent.switch(value) g = convoluted() self.assertEqual(g.switch(42), 43) # Exits the running greenlet, otherwise it leaks # XXX: We should be able to automatically fix this #g.throw(greenlet.GreenletExit) #del g self.expect_greenlet_leak = True def test_threaded_updatecurrent(self): # released when main thread should execute lock1 = threading.Lock() lock1.acquire() # released when another thread should execute lock2 = threading.Lock() lock2.acquire() class finalized(object): def __del__(self): # happens while in green_updatecurrent() in main greenlet # should be very careful not to accidentally call it again # at the same time we must make sure another thread executes lock2.release() lock1.acquire() # now ts_current belongs to another thread def deallocator(): greenlet.getcurrent().parent.switch() def fthread(): lock2.acquire() greenlet.getcurrent() del g[0] lock1.release() lock2.acquire() greenlet.getcurrent() lock1.release() main = greenlet.getcurrent() g = [RawGreenlet(deallocator)] g[0].bomb = finalized() g[0].switch() t = threading.Thread(target=fthread) t.start() # let another thread grab ts_current and deallocate g[0] lock2.release() lock1.acquire() # this is the corner stone # getcurrent() will notice that ts_current belongs to another thread # and start the update process, which would notice that g[0] should # be deallocated, and that will execute an object's finalizer. Now, # that object will let another thread run so it can grab ts_current # again, which would likely crash the interpreter if there's no # check for this case at the end of green_updatecurrent(). This test # passes if getcurrent() returns correct result, but it's likely # to randomly crash if it's not anyway. self.assertEqual(greenlet.getcurrent(), main) # wait for another thread to complete, just in case t.join(10) def test_dealloc_switch_args_not_lost(self): seen = [] def worker(): # wait for the value value = greenlet.getcurrent().parent.switch() # delete all references to ourself del worker[0] initiator.parent = greenlet.getcurrent().parent # switch to main with the value, but because # ts_current is the last reference to us we # return here immediately, where we resurrect ourself. try: greenlet.getcurrent().parent.switch(value) finally: seen.append(greenlet.getcurrent()) def initiator(): return 42 # implicitly falls thru to parent worker = [RawGreenlet(worker)] worker[0].switch() # prime worker initiator = RawGreenlet(initiator, worker[0]) value = initiator.switch() self.assertTrue(seen) self.assertEqual(value, 42) def test_tuple_subclass(self): # The point of this test is to see what happens when a custom # tuple subclass is used as an object passed directly to the C # function ``green_switch``; part of ``green_switch`` checks # the ``len()`` of the ``args`` tuple, and that can call back # into Python. Here, when it calls back into Python, we # recursively enter ``green_switch`` again. # This test is really only relevant on Python 2. The builtin # `apply` function directly passes the given args tuple object # to the underlying function, whereas the Python 3 version # unpacks and repacks into an actual tuple. This could still # happen using the C API on Python 3 though. We should write a # builtin version of apply() ourself. def _apply(func, a, k): func(*a, **k) class mytuple(tuple): def __len__(self): greenlet.getcurrent().switch() return tuple.__len__(self) args = mytuple() kwargs = dict(a=42) def switchapply(): _apply(greenlet.getcurrent().parent.switch, args, kwargs) g = RawGreenlet(switchapply) self.assertEqual(g.switch(), kwargs) def test_abstract_subclasses(self): AbstractSubclass = ABCMeta( 'AbstractSubclass', (RawGreenlet,), {'run': abstractmethod(lambda self: None)}) class BadSubclass(AbstractSubclass): pass class GoodSubclass(AbstractSubclass): def run(self): pass GoodSubclass() # should not raise self.assertRaises(TypeError, BadSubclass) def test_implicit_parent_with_threads(self): if not gc.isenabled(): return # cannot test with disabled gc N = gc.get_threshold()[0] if N < 50: return # cannot test with such a small N def attempt(): lock1 = threading.Lock() lock1.acquire() lock2 = threading.Lock() lock2.acquire() recycled = [False] def another_thread(): lock1.acquire() # wait for gc greenlet.getcurrent() # update ts_current lock2.release() # release gc t = threading.Thread(target=another_thread) t.start() class gc_callback(object): def __del__(self): lock1.release() lock2.acquire() recycled[0] = True class garbage(object): def __init__(self): self.cycle = self self.callback = gc_callback() l = [] x = range(N*2) current = greenlet.getcurrent() g = garbage() for _ in x: g = None # lose reference to garbage if recycled[0]: # gc callback called prematurely t.join(10) return False last = RawGreenlet() if recycled[0]: break # yes! gc called in green_new l.append(last) # increase allocation counter else: # gc callback not called when expected gc.collect() if recycled[0]: t.join(10) return False self.assertEqual(last.parent, current) for g in l: self.assertEqual(g.parent, current) return True for _ in range(5): if attempt(): break def test_issue_245_reference_counting_subclass_no_threads(self): # https://github.com/python-greenlet/greenlet/issues/245 # Before the fix, this crashed pretty reliably on # Python 3.10, at least on macOS; but much less reliably on other # interpreters (memory layout must have changed). # The threaded test crashed more reliably on more interpreters. from greenlet import getcurrent from greenlet import GreenletExit class Greenlet(RawGreenlet): pass initial_refs = sys.getrefcount(Greenlet) # This has to be an instance variable because # Python 2 raises a SyntaxError if we delete a local # variable referenced in an inner scope. self.glets = [] # pylint:disable=attribute-defined-outside-init def greenlet_main(): try: getcurrent().parent.switch() except GreenletExit: self.glets.append(getcurrent()) # Before the for _ in range(10): Greenlet(greenlet_main).switch() del self.glets self.assertEqual(sys.getrefcount(Greenlet), initial_refs) @unittest.skipIf( PY313 and RUNNING_ON_MANYLINUX, "The manylinux images appear to hang on this test on 3.13rc2" # Or perhaps I just got tired of waiting for the 450s timeout. # Still, it shouldn't take anywhere near that long. Does not reproduce in # Ubuntu images, on macOS or Windows. ) def test_issue_245_reference_counting_subclass_threads(self): # https://github.com/python-greenlet/greenlet/issues/245 from threading import Thread from threading import Event from greenlet import getcurrent class MyGreenlet(RawGreenlet): pass glets = [] ref_cleared = Event() def greenlet_main(): getcurrent().parent.switch() def thread_main(greenlet_running_event): mine = MyGreenlet(greenlet_main) glets.append(mine) # The greenlets being deleted must be active mine.switch() # Don't keep any reference to it in this thread del mine # Let main know we published our greenlet. greenlet_running_event.set() # Wait for main to let us know the references are # gone and the greenlet objects no longer reachable ref_cleared.wait(10) # The creating thread must call getcurrent() (or a few other # greenlet APIs) because that's when the thread-local list of dead # greenlets gets cleared. getcurrent() # We start with 3 references to the subclass: # - This module # - Its __mro__ # - The __subclassess__ attribute of greenlet # - (If we call gc.get_referents(), we find four entries, including # some other tuple ``(greenlet)`` that I'm not sure about but must be part # of the machinery.) # # On Python 3.10 it's often enough to just run 3 threads; on Python 2.7, # more threads are needed, and the results are still # non-deterministic. Presumably the memory layouts are different initial_refs = sys.getrefcount(MyGreenlet) thread_ready_events = [] for _ in range( initial_refs + 45 ): event = Event() thread = Thread(target=thread_main, args=(event,)) thread_ready_events.append(event) thread.start() for done_event in thread_ready_events: done_event.wait(10) del glets[:] ref_cleared.set() # Let any other thread run; it will crash the interpreter # if not fixed (or silently corrupt memory and we possibly crash # later). self.wait_for_pending_cleanups() self.assertEqual(sys.getrefcount(MyGreenlet), initial_refs) def test_falling_off_end_switches_to_unstarted_parent_raises_error(self): def no_args(): return 13 parent_never_started = RawGreenlet(no_args) def leaf(): return 42 child = RawGreenlet(leaf, parent_never_started) # Because the run function takes to arguments with self.assertRaises(TypeError): child.switch() def test_falling_off_end_switches_to_unstarted_parent_works(self): def one_arg(x): return (x, 24) parent_never_started = RawGreenlet(one_arg) def leaf(): return 42 child = RawGreenlet(leaf, parent_never_started) result = child.switch() self.assertEqual(result, (42, 24)) def test_switch_to_dead_greenlet_with_unstarted_perverse_parent(self): class Parent(RawGreenlet): def __getattribute__(self, name): if name == 'run': raise SomeError parent_never_started = Parent() seen = [] child = RawGreenlet(lambda: seen.append(42), parent_never_started) # Because we automatically start the parent when the child is # finished with self.assertRaises(SomeError): child.switch() self.assertEqual(seen, [42]) with self.assertRaises(SomeError): child.switch() self.assertEqual(seen, [42]) def test_switch_to_dead_greenlet_reparent(self): seen = [] parent_never_started = RawGreenlet(lambda: seen.append(24)) child = RawGreenlet(lambda: seen.append(42)) child.switch() self.assertEqual(seen, [42]) child.parent = parent_never_started # This actually is the same as switching to the parent. result = child.switch() self.assertIsNone(result) self.assertEqual(seen, [42, 24]) def test_can_access_f_back_of_suspended_greenlet(self): # 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 outer(): inner() 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.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): def test_threaded_reparent(self): data = {} created_event = threading.Event() done_event = threading.Event() def run(): data['g'] = RawGreenlet(lambda: None) created_event.set() done_event.wait(10) def blank(): greenlet.getcurrent().parent.switch() thread = threading.Thread(target=run) thread.start() created_event.wait(10) g = RawGreenlet(blank) g.switch() with self.assertRaises(ValueError) as exc: g.parent = data['g'] done_event.set() thread.join(10) self.assertEqual(str(exc.exception), "parent cannot be on a different thread") def test_unexpected_reparenting(self): another = [] def worker(): g = RawGreenlet(lambda: None) another.append(g) g.switch() t = threading.Thread(target=worker) t.start() t.join(10) # The first time we switch (running g_initialstub(), which is # when we look up the run attribute) we attempt to change the # parent to one from another thread (which also happens to be # dead). ``g_initialstub()`` should detect this and raise a # greenlet error. # # EXCEPT: With the fix for #252, this is actually detected # sooner, when setting the parent itself. Prior to that fix, # the main greenlet from the background thread kept a valid # value for ``run_info``, and appeared to be a valid parent # until we actually started the greenlet. But now that it's # cleared, this test is catching whether ``green_setparent`` # can detect the dead thread. # # Further refactoring once again changes this back to a greenlet.error # # We need to wait for the cleanup to happen, but we're # deliberately leaking a main greenlet here. self.wait_for_pending_cleanups(initial_main_greenlets=self.main_greenlets_before_test + 1) class convoluted(RawGreenlet): def __getattribute__(self, name): if name == 'run': self.parent = another[0] # pylint:disable=attribute-defined-outside-init return RawGreenlet.__getattribute__(self, name) g = convoluted(lambda: None) with self.assertRaises(greenlet.error) as exc: g.switch() self.assertEqual(str(exc.exception), "cannot switch to a different thread (which happens to have exited)") del another[:] def test_unexpected_reparenting_thread_running(self): # Like ``test_unexpected_reparenting``, except the background thread is # actually still alive. another = [] switched_to_greenlet = threading.Event() keep_main_alive = threading.Event() def worker(): g = RawGreenlet(lambda: None) another.append(g) g.switch() switched_to_greenlet.set() keep_main_alive.wait(10) class convoluted(RawGreenlet): def __getattribute__(self, name): if name == 'run': self.parent = another[0] # pylint:disable=attribute-defined-outside-init return RawGreenlet.__getattribute__(self, name) t = threading.Thread(target=worker) t.start() switched_to_greenlet.wait(10) try: g = convoluted(lambda: None) with self.assertRaises(greenlet.error) as exc: g.switch() self.assertEqual(str(exc.exception), "cannot switch to a different thread") finally: keep_main_alive.set() t.join(10) # XXX: Should handle this automatically. del another[:] def test_cannot_delete_parent(self): worker = RawGreenlet(lambda: None) self.assertIs(worker.parent, greenlet.getcurrent()) with self.assertRaises(AttributeError) as exc: del worker.parent self.assertEqual(str(exc.exception), "can't delete attribute") def test_cannot_delete_parent_of_main(self): with self.assertRaises(AttributeError) as exc: del greenlet.getcurrent().parent self.assertEqual(str(exc.exception), "can't delete attribute") def test_main_greenlet_parent_is_none(self): # assuming we're in a main greenlet here. self.assertIsNone(greenlet.getcurrent().parent) def test_set_parent_wrong_types(self): def bg(): # Go back to main. greenlet.getcurrent().parent.switch() def check(glet): for p in None, 1, self, "42": with self.assertRaises(TypeError) as exc: glet.parent = p self.assertEqual( str(exc.exception), "GreenletChecker: Expected any type of greenlet, not " + type(p).__name__) # First, not running g = RawGreenlet(bg) self.assertFalse(g) check(g) # Then when running. g.switch() self.assertTrue(g) check(g) # Let it finish g.switch() def test_trivial_cycle(self): glet = RawGreenlet(lambda: None) with self.assertRaises(ValueError) as exc: glet.parent = glet self.assertEqual(str(exc.exception), "cyclic parent chain") def test_trivial_cycle_main(self): # This used to produce a ValueError, but we catch it earlier than that now. with self.assertRaises(AttributeError) as exc: greenlet.getcurrent().parent = greenlet.getcurrent() self.assertEqual(str(exc.exception), "cannot set the parent of a main greenlet") def test_deeper_cycle(self): g1 = RawGreenlet(lambda: None) g2 = RawGreenlet(lambda: None) g3 = RawGreenlet(lambda: None) g1.parent = g2 g2.parent = g3 with self.assertRaises(ValueError) as exc: g3.parent = g1 self.assertEqual(str(exc.exception), "cyclic parent chain") class TestRepr(TestCase): def assertEndsWith(self, got, suffix): self.assertTrue(got.endswith(suffix), (got, suffix)) def test_main_while_running(self): r = repr(greenlet.getcurrent()) self.assertEndsWith(r, " current active started main>") def test_main_in_background(self): main = greenlet.getcurrent() def run(): return repr(main) g = RawGreenlet(run) r = g.switch() self.assertEndsWith(r, ' suspended active started main>') def test_initial(self): r = repr(RawGreenlet()) self.assertEndsWith(r, ' pending>') def test_main_from_other_thread(self): main = greenlet.getcurrent() class T(threading.Thread): original_main = thread_main = None main_glet = None def run(self): self.original_main = repr(main) self.main_glet = greenlet.getcurrent() self.thread_main = repr(self.main_glet) t = T() t.start() t.join(10) self.assertEndsWith(t.original_main, ' suspended active started main>') self.assertEndsWith(t.thread_main, ' current active started main>') # give the machinery time to notice the death of the thread, # and clean it up. Note that we don't use # ``expect_greenlet_leak`` or wait_for_pending_cleanups, # because at this point we know we have an extra greenlet # still reachable. for _ in range(3): time.sleep(0.001) # In the past, main greenlets, even from dead threads, never # really appear dead. We have fixed that, and we also report # that the thread is dead in the repr. (Do this multiple times # to make sure that we don't self-modify and forget our state # in the C++ code). for _ in range(3): self.assertTrue(t.main_glet.dead) r = repr(t.main_glet) self.assertEndsWith(r, ' (thread exited) dead>') def test_dead(self): g = RawGreenlet(lambda: None) g.switch() self.assertEndsWith(repr(g), ' dead>') self.assertNotIn('suspended', repr(g)) self.assertNotIn('started', repr(g)) self.assertNotIn('active', repr(g)) def test_formatting_produces_native_str(self): # https://github.com/python-greenlet/greenlet/issues/218 # %s formatting on Python 2 was producing unicode, not str. g_dead = RawGreenlet(lambda: None) g_not_started = RawGreenlet(lambda: None) g_cur = greenlet.getcurrent() for g in g_dead, g_not_started, g_cur: self.assertIsInstance( '%s' % (g,), str ) self.assertIsInstance( '%r' % (g,), str, ) class TestMainGreenlet(TestCase): # Tests some implementation details, and relies on some # implementation details. def _check_current_is_main(self): # implementation detail assert 'main' in repr(greenlet.getcurrent()) t = type(greenlet.getcurrent()) assert 'main' not in repr(t) return t def test_main_greenlet_type_can_be_subclassed(self): main_type = self._check_current_is_main() subclass = type('subclass', (main_type,), {}) self.assertIsNotNone(subclass) def test_main_greenlet_is_greenlet(self): self._check_current_is_main() self.assertIsInstance(greenlet.getcurrent(), RawGreenlet) class TestBrokenGreenlets(TestCase): # Tests for things that used to, or still do, terminate the interpreter. # This often means doing unsavory things. def test_failed_to_initialstub(self): def func(): raise AssertionError("Never get here") g = greenlet._greenlet.UnswitchableGreenlet(func) g.force_switch_error = True with self.assertRaisesRegex(SystemError, "Failed to switch stacks into a greenlet for the first time."): g.switch() def test_failed_to_switch_into_running(self): runs = [] def func(): runs.append(1) greenlet.getcurrent().parent.switch() runs.append(2) greenlet.getcurrent().parent.switch() runs.append(3) # pragma: no cover g = greenlet._greenlet.UnswitchableGreenlet(func) g.switch() self.assertEqual(runs, [1]) g.switch() self.assertEqual(runs, [1, 2]) g.force_switch_error = True with self.assertRaisesRegex(SystemError, "Failed to switch stacks into a running greenlet."): g.switch() # If we stopped here, we would fail the leakcheck, because we've left # the ``inner_bootstrap()`` C frame and its descendents hanging around, # which have a bunch of Python references. They'll never get cleaned up # if we don't let the greenlet finish. g.force_switch_error = False g.switch() self.assertEqual(runs, [1, 2, 3]) def test_failed_to_slp_switch_into_running(self): ex = self.assertScriptRaises('fail_slp_switch.py') self.assertIn('fail_slp_switch is running', ex.output) self.assertIn(ex.returncode, self.get_expected_returncodes_for_aborted_process()) def test_reentrant_switch_two_greenlets(self): # Before we started capturing the arguments in g_switch_finish, this could crash. output = self.run_script('fail_switch_two_greenlets.py') self.assertIn('In g1_run', output) self.assertIn('TRACE', output) self.assertIn('LEAVE TRACE', output) self.assertIn('Falling off end of main', output) self.assertIn('Falling off end of g1_run', output) self.assertIn('Falling off end of g2', output) def test_reentrant_switch_three_greenlets(self): # On debug builds of greenlet, this used to crash with an assertion error; # on non-debug versions, it ran fine (which it should not do!). # Now it always crashes correctly with a TypeError ex = self.assertScriptRaises('fail_switch_three_greenlets.py', exitcodes=(1,)) self.assertIn('TypeError', ex.output) self.assertIn('positional arguments', ex.output) def test_reentrant_switch_three_greenlets2(self): # This actually passed on debug and non-debug builds. It # should probably have been triggering some debug assertions # but it didn't. # # I think the fixes for the above test also kicked in here. output = self.run_script('fail_switch_three_greenlets2.py') self.assertIn( "RESULTS: [('trace', 'switch'), " "('trace', 'switch'), ('g2 arg', 'g2 from tracefunc'), " "('trace', 'switch'), ('main g1', 'from g2_run'), ('trace', 'switch'), " "('g1 arg', 'g1 from main'), ('trace', 'switch'), ('main g2', 'from g1_run'), " "('trace', 'switch'), ('g1 from parent', 'g1 from main 2'), ('trace', 'switch'), " "('main g1.2', 'g1 done'), ('trace', 'switch'), ('g2 from parent', ()), " "('trace', 'switch'), ('main g2.2', 'g2 done')]", output ) def test_reentrant_switch_GreenletAlreadyStartedInPython(self): output = self.run_script('fail_initialstub_already_started.py') self.assertIn( "RESULTS: ['Begin C', 'Switch to b from B.__getattribute__ in C', " "('Begin B', ()), '_B_run switching to main', ('main from c', 'From B'), " "'B.__getattribute__ back from main in C', ('Begin A', (None,)), " "('A dead?', True, 'B dead?', True, 'C dead?', False), " "'C done', ('main from c.2', None)]", output ) def test_reentrant_switch_run_callable_has_del(self): output = self.run_script('fail_clearing_run_switches.py') self.assertIn( "RESULTS [" "('G.__getattribute__', 'run'), ('RunCallable', '__del__'), " "('main: g.switch()', 'from RunCallable'), ('run_func', 'enter')" "]", output ) if __name__ == '__main__': unittest.main() greenlet-3.1.0/src/greenlet/tests/test_greenlet_trash.py000066400000000000000000000174131467007427000235120ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Tests for greenlets interacting with the CPython trash can API. The CPython trash can API is not designed to be re-entered from a single thread. But this can happen using greenlets, if something during the object deallocation process switches greenlets, and this second greenlet then causes the trash can to get entered again. Here, we do this very explicitly, but in other cases (like gevent) it could be arbitrarily more complicated: for example, a weakref callback might try to acquire a lock that's already held by another greenlet; that would allow a greenlet switch to occur. See https://github.com/gevent/gevent/issues/1909 This test is fragile and relies on details of the CPython implementation (like most of the rest of this package): - We enter the trashcan and deferred deallocation after ``_PyTrash_UNWIND_LEVEL`` calls. This constant, defined in CPython's object.c, is generally 50. That's basically how many objects are required to get us into the deferred deallocation situation. - The test fails by hitting an ``assert()`` in object.c; if the build didn't enable assert, then we don't catch this. - If the test fails in that way, the interpreter crashes. """ from __future__ import print_function, absolute_import, division import unittest class TestTrashCanReEnter(unittest.TestCase): def test_it(self): try: # pylint:disable-next=no-name-in-module from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=unused-import except ImportError: import sys # Python 3.13 has not "trash delete nesting" anymore (but "delete later") assert sys.version_info[:2] >= (3, 13) self.skipTest("get_tstate_trash_delete_nesting is not available.") # Try several times to trigger it, because it isn't 100% # reliable. for _ in range(10): self.check_it() def check_it(self): # pylint:disable=too-many-statements import greenlet from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=no-name-in-module main = greenlet.getcurrent() assert get_tstate_trash_delete_nesting() == 0 # We expect to be in deferred deallocation after this many # deallocations have occurred. TODO: I wish we had a better way to do # this --- that was before get_tstate_trash_delete_nesting; perhaps # we can use that API to do better? TRASH_UNWIND_LEVEL = 50 # How many objects to put in a container; it's the container that # queues objects for deferred deallocation. OBJECTS_PER_CONTAINER = 500 class Dealloc: # define the class here because we alter class variables each time we run. """ An object with a ``__del__`` method. When it starts getting deallocated from a deferred trash can run, it switches greenlets, allocates more objects which then also go in the trash can. If we don't save state appropriately, nesting gets out of order and we can crash the interpreter. """ #: Has our deallocation actually run and switched greenlets? #: When it does, this will be set to the current greenlet. This should #: be happening in the main greenlet, so we check that down below. SPAWNED = False #: Has the background greenlet run? BG_RAN = False BG_GLET = None #: How many of these things have ever been allocated. CREATED = 0 #: How many of these things have ever been deallocated. DESTROYED = 0 #: How many were destroyed not in the main greenlet. There should always #: be some. #: If the test is broken or things change in the trashcan implementation, #: this may not be correct. DESTROYED_BG = 0 def __init__(self, sequence_number): """ :param sequence_number: The ordinal of this object during one particular creation run. This is used to detect (guess, really) when we have entered the trash can's deferred deallocation. """ self.i = sequence_number Dealloc.CREATED += 1 def __del__(self): if self.i == TRASH_UNWIND_LEVEL and not self.SPAWNED: Dealloc.SPAWNED = greenlet.getcurrent() other = Dealloc.BG_GLET = greenlet.greenlet(background_greenlet) x = other.switch() assert x == 42 # It's important that we don't switch back to the greenlet, # we leave it hanging there in an incomplete state. But we don't let it # get collected, either. If we complete it now, while we're still # in the scope of the initial trash can, things work out and we # don't see the problem. We need this greenlet to complete # at some point in the future, after we've exited this trash can invocation. del other elif self.i == 40 and greenlet.getcurrent() is not main: Dealloc.BG_RAN = True try: main.switch(42) except greenlet.GreenletExit as ex: # We expect this; all references to us go away # while we're still running, and we need to finish deleting # ourself. Dealloc.BG_RAN = type(ex) del ex # Record the fact that we're dead last of all. This ensures that # we actually get returned too. Dealloc.DESTROYED += 1 if greenlet.getcurrent() is not main: Dealloc.DESTROYED_BG += 1 def background_greenlet(): # We direct through a second function, instead of # directly calling ``make_some()``, so that we have complete # control over when these objects are destroyed: we need them # to be destroyed in the context of the background greenlet t = make_some() del t # Triggere deletion. def make_some(): t = () i = OBJECTS_PER_CONTAINER while i: # Nest the tuples; it's the recursion that gets us # into trash. t = (Dealloc(i), t) i -= 1 return t some = make_some() self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER) self.assertEqual(Dealloc.DESTROYED, 0) # If we're going to crash, it should be on the following line. # We only crash if ``assert()`` is enabled, of course. del some # For non-debug builds of CPython, we won't crash. The best we can do is check # the nesting level explicitly. self.assertEqual(0, get_tstate_trash_delete_nesting()) # Discard this, raising GreenletExit into where it is waiting. Dealloc.BG_GLET = None # The same nesting level maintains. self.assertEqual(0, get_tstate_trash_delete_nesting()) # We definitely cleaned some up in the background self.assertGreater(Dealloc.DESTROYED_BG, 0) # Make sure all the cleanups happened. self.assertIs(Dealloc.SPAWNED, main) self.assertTrue(Dealloc.BG_RAN) self.assertEqual(Dealloc.BG_RAN, greenlet.GreenletExit) self.assertEqual(Dealloc.CREATED, Dealloc.DESTROYED ) self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER * 2) import gc gc.collect() if __name__ == '__main__': unittest.main() greenlet-3.1.0/src/greenlet/tests/test_leaks.py000066400000000000000000000420711467007427000216010ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Testing scenarios that may have leaked. """ from __future__ import print_function, absolute_import, division import sys import gc import time import weakref import threading import greenlet from . import TestCase from .leakcheck import fails_leakcheck from .leakcheck import ignores_leakcheck from .leakcheck import RUNNING_ON_MANYLINUX # pylint:disable=protected-access assert greenlet.GREENLET_USE_GC # Option to disable this was removed in 1.0 class HasFinalizerTracksInstances(object): EXTANT_INSTANCES = set() def __init__(self, msg): self.msg = sys.intern(msg) self.EXTANT_INSTANCES.add(id(self)) def __del__(self): self.EXTANT_INSTANCES.remove(id(self)) def __repr__(self): return "" % ( id(self), self.msg ) @classmethod def reset(cls): cls.EXTANT_INSTANCES.clear() class TestLeaks(TestCase): def test_arg_refs(self): args = ('a', 'b', 'c') refcount_before = sys.getrefcount(args) # pylint:disable=unnecessary-lambda g = greenlet.greenlet( lambda *args: greenlet.getcurrent().parent.switch(*args)) for _ in range(100): g.switch(*args) self.assertEqual(sys.getrefcount(args), refcount_before) def test_kwarg_refs(self): kwargs = {} # pylint:disable=unnecessary-lambda g = greenlet.greenlet( lambda **kwargs: greenlet.getcurrent().parent.switch(**kwargs)) for _ in range(100): g.switch(**kwargs) self.assertEqual(sys.getrefcount(kwargs), 2) @staticmethod def __recycle_threads(): # By introducing a thread that does sleep we allow other threads, # that have triggered their __block condition, but did not have a # chance to deallocate their thread state yet, to finally do so. # The way it works is by requiring a GIL switch (different thread), # which does a GIL release (sleep), which might do a GIL switch # to finished threads and allow them to clean up. def worker(): time.sleep(0.001) t = threading.Thread(target=worker) t.start() time.sleep(0.001) t.join(10) def test_threaded_leak(self): gg = [] def worker(): # only main greenlet present gg.append(weakref.ref(greenlet.getcurrent())) for _ in range(2): t = threading.Thread(target=worker) t.start() t.join(10) del t greenlet.getcurrent() # update ts_current self.__recycle_threads() greenlet.getcurrent() # update ts_current gc.collect() greenlet.getcurrent() # update ts_current for g in gg: self.assertIsNone(g()) def test_threaded_adv_leak(self): gg = [] def worker(): # main and additional *finished* greenlets ll = greenlet.getcurrent().ll = [] def additional(): ll.append(greenlet.getcurrent()) for _ in range(2): greenlet.greenlet(additional).switch() gg.append(weakref.ref(greenlet.getcurrent())) for _ in range(2): t = threading.Thread(target=worker) t.start() t.join(10) del t greenlet.getcurrent() # update ts_current self.__recycle_threads() greenlet.getcurrent() # update ts_current gc.collect() greenlet.getcurrent() # update ts_current for g in gg: self.assertIsNone(g()) def assertClocksUsed(self): used = greenlet._greenlet.get_clocks_used_doing_optional_cleanup() self.assertGreaterEqual(used, 0) # we don't lose the value greenlet._greenlet.enable_optional_cleanup(True) used2 = greenlet._greenlet.get_clocks_used_doing_optional_cleanup() self.assertEqual(used, used2) self.assertGreater(greenlet._greenlet.CLOCKS_PER_SEC, 1) def _check_issue251(self, manually_collect_background=True, explicit_reference_to_switch=False): # See https://github.com/python-greenlet/greenlet/issues/251 # Killing a greenlet (probably not the main one) # in one thread from another thread would # result in leaking a list (the ts_delkey list). # We no longer use lists to hold that stuff, though. # For the test to be valid, even empty lists have to be tracked by the # GC assert gc.is_tracked([]) HasFinalizerTracksInstances.reset() greenlet.getcurrent() greenlets_before = self.count_objects(greenlet.greenlet, exact_kind=False) background_glet_running = threading.Event() background_glet_killed = threading.Event() background_greenlets = [] # XXX: Switching this to a greenlet subclass that overrides # run results in all callers failing the leaktest; that # greenlet instance is leaked. There's a bound method for # run() living on the stack of the greenlet in g_initialstub, # and since we don't manually switch back to the background # greenlet to let it "fall off the end" and exit the # g_initialstub function, it never gets cleaned up. Making the # garbage collector aware of this bound method (making it an # attribute of the greenlet structure and traversing into it) # doesn't help, for some reason. def background_greenlet(): # Throw control back to the main greenlet. jd = HasFinalizerTracksInstances("DELETING STACK OBJECT") greenlet._greenlet.set_thread_local( 'test_leaks_key', HasFinalizerTracksInstances("DELETING THREAD STATE")) # Explicitly keeping 'switch' in a local variable # breaks this test in all versions if explicit_reference_to_switch: s = greenlet.getcurrent().parent.switch s([jd]) else: greenlet.getcurrent().parent.switch([jd]) bg_main_wrefs = [] def background_thread(): glet = greenlet.greenlet(background_greenlet) bg_main_wrefs.append(weakref.ref(glet.parent)) background_greenlets.append(glet) glet.switch() # Be sure it's active. # Control is ours again. del glet # Delete one reference from the thread it runs in. background_glet_running.set() background_glet_killed.wait(10) # To trigger the background collection of the dead # greenlet, thus clearing out the contents of the list, we # need to run some APIs. See issue 252. if manually_collect_background: greenlet.getcurrent() t = threading.Thread(target=background_thread) t.start() background_glet_running.wait(10) greenlet.getcurrent() lists_before = self.count_objects(list, exact_kind=True) assert len(background_greenlets) == 1 self.assertFalse(background_greenlets[0].dead) # Delete the last reference to the background greenlet # from a different thread. This puts it in the background thread's # ts_delkey list. del background_greenlets[:] background_glet_killed.set() # Now wait for the background thread to die. t.join(10) del t # As part of the fix for 252, we need to cycle the ceval.c # interpreter loop to be sure it has had a chance to process # the pending call. self.wait_for_pending_cleanups() lists_after = self.count_objects(list, exact_kind=True) greenlets_after = self.count_objects(greenlet.greenlet, exact_kind=False) # On 2.7, we observe that lists_after is smaller than # lists_before. No idea what lists got cleaned up. All the # Python 3 versions match exactly. self.assertLessEqual(lists_after, lists_before) # On versions after 3.6, we've successfully cleaned up the # greenlet references thanks to the internal "vectorcall" # protocol; prior to that, there is a reference path through # the ``greenlet.switch`` method still on the stack that we # can't reach to clean up. The C code goes through terrific # lengths to clean that up. if not explicit_reference_to_switch \ and greenlet._greenlet.get_clocks_used_doing_optional_cleanup() is not None: # If cleanup was disabled, though, we may not find it. self.assertEqual(greenlets_after, greenlets_before) if manually_collect_background: # TODO: Figure out how to make this work! # The one on the stack is still leaking somehow # in the non-manually-collect state. self.assertEqual(HasFinalizerTracksInstances.EXTANT_INSTANCES, set()) else: # The explicit reference prevents us from collecting it # and it isn't always found by the GC either for some # reason. The entire frame is leaked somehow, on some # platforms (e.g., MacPorts builds of Python (all # versions!)), but not on other platforms (the linux and # windows builds on GitHub actions and Appveyor). So we'd # like to write a test that proves that the main greenlet # sticks around, and we can on my machine (macOS 11.6, # MacPorts builds of everything) but we can't write that # same test on other platforms. However, hopefully iteration # done by leakcheck will find it. pass if greenlet._greenlet.get_clocks_used_doing_optional_cleanup() is not None: self.assertClocksUsed() def test_issue251_killing_cross_thread_leaks_list(self): self._check_issue251() def test_issue251_with_cleanup_disabled(self): greenlet._greenlet.enable_optional_cleanup(False) try: self._check_issue251() finally: greenlet._greenlet.enable_optional_cleanup(True) @fails_leakcheck def test_issue251_issue252_need_to_collect_in_background(self): # Between greenlet 1.1.2 and the next version, this was still # failing because the leak of the list still exists when we # don't call a greenlet API before exiting the thread. The # proximate cause is that neither of the two greenlets from # the background thread are actually being destroyed, even # though the GC is in fact visiting both objects. It's not # clear where that leak is? For some reason the thread-local # dict holding it isn't being cleaned up. # # The leak, I think, is in the CPYthon internal function that # calls into green_switch(). The argument tuple is still on # the C stack somewhere and can't be reached? That doesn't # make sense, because the tuple should be collectable when # this object goes away. # # Note that this test sometimes spuriously passes on Linux, # for some reason, but I've never seen it pass on macOS. self._check_issue251(manually_collect_background=False) @fails_leakcheck def test_issue251_issue252_need_to_collect_in_background_cleanup_disabled(self): self.expect_greenlet_leak = True greenlet._greenlet.enable_optional_cleanup(False) try: self._check_issue251(manually_collect_background=False) finally: greenlet._greenlet.enable_optional_cleanup(True) @fails_leakcheck def test_issue251_issue252_explicit_reference_not_collectable(self): self._check_issue251( manually_collect_background=False, explicit_reference_to_switch=True) UNTRACK_ATTEMPTS = 100 def _only_test_some_versions(self): # We're only looking for this problem specifically on 3.11, # and this set of tests is relatively fragile, depending on # OS and memory management details. So we want to run it on 3.11+ # (obviously) but not every older 3.x version in order to reduce # false negatives. At the moment, those false results seem to have # resolved, so we are actually running this on 3.8+ assert sys.version_info[0] >= 3 if sys.version_info[:2] < (3, 8): self.skipTest('Only observed on 3.11') if RUNNING_ON_MANYLINUX: self.skipTest("Slow and not worth repeating here") @ignores_leakcheck # Because we're just trying to track raw memory, not objects, and running # the leakcheck makes an already slow test slower. def test_untracked_memory_doesnt_increase(self): # See https://github.com/gevent/gevent/issues/1924 # and https://github.com/python-greenlet/greenlet/issues/328 self._only_test_some_versions() def f(): return 1 ITER = 10000 def run_it(): for _ in range(ITER): greenlet.greenlet(f).switch() # Establish baseline for _ in range(3): run_it() # uss: (Linux, macOS, Windows): aka "Unique Set Size", this is # the memory which is unique to a process and which would be # freed if the process was terminated right now. uss_before = self.get_process_uss() for count in range(self.UNTRACK_ATTEMPTS): uss_before = max(uss_before, self.get_process_uss()) run_it() uss_after = self.get_process_uss() if uss_after <= uss_before and count > 1: break self.assertLessEqual(uss_after, uss_before) def _check_untracked_memory_thread(self, deallocate_in_thread=True): self._only_test_some_versions() # Like the above test, but what if there are a bunch of # unfinished greenlets in a thread that dies? # Does it matter if we deallocate in the thread or not? EXIT_COUNT = [0] def f(): try: greenlet.getcurrent().parent.switch() except greenlet.GreenletExit: EXIT_COUNT[0] += 1 raise return 1 ITER = 10000 def run_it(): glets = [] for _ in range(ITER): # Greenlet starts, switches back to us. # We keep a strong reference to the greenlet though so it doesn't # get a GreenletExit exception. g = greenlet.greenlet(f) glets.append(g) g.switch() return glets test = self class ThreadFunc: uss_before = uss_after = 0 glets = () ITER = 2 def __call__(self): self.uss_before = test.get_process_uss() for _ in range(self.ITER): self.glets += tuple(run_it()) for g in self.glets: test.assertIn('suspended active', str(g)) # Drop them. if deallocate_in_thread: self.glets = () self.uss_after = test.get_process_uss() # Establish baseline uss_before = uss_after = None for count in range(self.UNTRACK_ATTEMPTS): EXIT_COUNT[0] = 0 thread_func = ThreadFunc() t = threading.Thread(target=thread_func) t.start() t.join(30) self.assertFalse(t.is_alive()) if uss_before is None: uss_before = thread_func.uss_before uss_before = max(uss_before, thread_func.uss_before) if deallocate_in_thread: self.assertEqual(thread_func.glets, ()) self.assertEqual(EXIT_COUNT[0], ITER * thread_func.ITER) del thread_func # Deallocate the greenlets; but this won't raise into them del t if not deallocate_in_thread: self.assertEqual(EXIT_COUNT[0], 0) if deallocate_in_thread: self.wait_for_pending_cleanups() uss_after = self.get_process_uss() # See if we achieve a non-growth state at some point. Break when we do. if uss_after <= uss_before and count > 1: break self.wait_for_pending_cleanups() uss_after = self.get_process_uss() self.assertLessEqual(uss_after, uss_before, "after attempts %d" % (count,)) @ignores_leakcheck # Because we're just trying to track raw memory, not objects, and running # the leakcheck makes an already slow test slower. def test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_thread(self): self._check_untracked_memory_thread(deallocate_in_thread=True) @ignores_leakcheck # Because the main greenlets from the background threads do not exit in a timely fashion, # we fail the object-based leakchecks. def test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main(self): self._check_untracked_memory_thread(deallocate_in_thread=False) if __name__ == '__main__': __import__('unittest').main() greenlet-3.1.0/src/greenlet/tests/test_stack_saved.py000066400000000000000000000006761467007427000227760ustar00rootroot00000000000000import greenlet from . import TestCase class Test(TestCase): def test_stack_saved(self): main = greenlet.getcurrent() self.assertEqual(main._stack_saved, 0) def func(): main.switch(main._stack_saved) g = greenlet.greenlet(func) x = g.switch() self.assertGreater(x, 0) self.assertGreater(g._stack_saved, 0) g.switch() self.assertEqual(g._stack_saved, 0) greenlet-3.1.0/src/greenlet/tests/test_throw.py000066400000000000000000000072001467007427000216400ustar00rootroot00000000000000import sys from greenlet import greenlet from . import TestCase def switch(*args): return greenlet.getcurrent().parent.switch(*args) class ThrowTests(TestCase): def test_class(self): def f(): try: switch("ok") except RuntimeError: switch("ok") return switch("fail") g = greenlet(f) res = g.switch() self.assertEqual(res, "ok") res = g.throw(RuntimeError) self.assertEqual(res, "ok") def test_val(self): def f(): try: switch("ok") except RuntimeError: val = sys.exc_info()[1] if str(val) == "ciao": switch("ok") return switch("fail") g = greenlet(f) res = g.switch() self.assertEqual(res, "ok") res = g.throw(RuntimeError("ciao")) self.assertEqual(res, "ok") g = greenlet(f) res = g.switch() self.assertEqual(res, "ok") res = g.throw(RuntimeError, "ciao") self.assertEqual(res, "ok") def test_kill(self): def f(): switch("ok") switch("fail") g = greenlet(f) res = g.switch() self.assertEqual(res, "ok") res = g.throw() self.assertTrue(isinstance(res, greenlet.GreenletExit)) self.assertTrue(g.dead) res = g.throw() # immediately eaten by the already-dead greenlet self.assertTrue(isinstance(res, greenlet.GreenletExit)) def test_throw_goes_to_original_parent(self): main = greenlet.getcurrent() def f1(): try: main.switch("f1 ready to catch") except IndexError: return "caught" return "normal exit" def f2(): main.switch("from f2") g1 = greenlet(f1) g2 = greenlet(f2, parent=g1) with self.assertRaises(IndexError): g2.throw(IndexError) self.assertTrue(g2.dead) self.assertTrue(g1.dead) g1 = greenlet(f1) g2 = greenlet(f2, parent=g1) res = g1.switch() self.assertEqual(res, "f1 ready to catch") res = g2.throw(IndexError) self.assertEqual(res, "caught") self.assertTrue(g2.dead) self.assertTrue(g1.dead) g1 = greenlet(f1) g2 = greenlet(f2, parent=g1) res = g1.switch() self.assertEqual(res, "f1 ready to catch") res = g2.switch() self.assertEqual(res, "from f2") res = g2.throw(IndexError) self.assertEqual(res, "caught") self.assertTrue(g2.dead) self.assertTrue(g1.dead) def test_non_traceback_param(self): with self.assertRaises(TypeError) as exc: greenlet.getcurrent().throw( Exception, Exception(), self ) self.assertEqual(str(exc.exception), "throw() third argument must be a traceback object") def test_instance_of_wrong_type(self): with self.assertRaises(TypeError) as exc: greenlet.getcurrent().throw( Exception(), BaseException() ) self.assertEqual(str(exc.exception), "instance exception may not have a separate value") def test_not_throwable(self): with self.assertRaises(TypeError) as exc: greenlet.getcurrent().throw( "abc" ) self.assertEqual(str(exc.exception), "exceptions must be classes, or instances, not str") greenlet-3.1.0/src/greenlet/tests/test_tracing.py000066400000000000000000000200721467007427000221260ustar00rootroot00000000000000from __future__ import print_function import sys import greenlet import unittest from . import TestCase from . import PY312 # https://discuss.python.org/t/cpython-3-12-greenlet-and-tracing-profiling-how-to-not-crash-and-get-correct-results/33144/2 DEBUG_BUILD_PY312 = ( PY312 and hasattr(sys, 'gettotalrefcount'), "Broken on debug builds of Python 3.12" ) class SomeError(Exception): pass class GreenletTracer(object): oldtrace = None def __init__(self, error_on_trace=False): self.actions = [] self.error_on_trace = error_on_trace def __call__(self, *args): self.actions.append(args) if self.error_on_trace: raise SomeError def __enter__(self): self.oldtrace = greenlet.settrace(self) return self.actions def __exit__(self, *args): greenlet.settrace(self.oldtrace) class TestGreenletTracing(TestCase): """ Tests of ``greenlet.settrace()`` """ def test_a_greenlet_tracing(self): main = greenlet.getcurrent() def dummy(): pass def dummyexc(): raise SomeError() with GreenletTracer() as actions: g1 = greenlet.greenlet(dummy) g1.switch() g2 = greenlet.greenlet(dummyexc) self.assertRaises(SomeError, g2.switch) self.assertEqual(actions, [ ('switch', (main, g1)), ('switch', (g1, main)), ('switch', (main, g2)), ('throw', (g2, main)), ]) def test_b_exception_disables_tracing(self): main = greenlet.getcurrent() def dummy(): main.switch() g = greenlet.greenlet(dummy) g.switch() with GreenletTracer(error_on_trace=True) as actions: self.assertRaises(SomeError, g.switch) self.assertEqual(greenlet.gettrace(), None) self.assertEqual(actions, [ ('switch', (main, g)), ]) def test_set_same_tracer_twice(self): # https://github.com/python-greenlet/greenlet/issues/332 # Our logic in asserting that the tracefunction should # gain a reference was incorrect if the same tracefunction was set # twice. tracer = GreenletTracer() with tracer: greenlet.settrace(tracer) class PythonTracer(object): oldtrace = None def __init__(self): self.actions = [] def __call__(self, frame, event, arg): # Record the co_name so we have an idea what function we're in. self.actions.append((event, frame.f_code.co_name)) def __enter__(self): self.oldtrace = sys.setprofile(self) return self.actions def __exit__(self, *args): sys.setprofile(self.oldtrace) def tpt_callback(): return 42 class TestPythonTracing(TestCase): """ Tests of the interaction of ``sys.settrace()`` with greenlet facilities. NOTE: Most of this is probably CPython specific. """ maxDiff = None def test_trace_events_trivial(self): with PythonTracer() as actions: tpt_callback() # If we use the sys.settrace instead of setprofile, we get # this: # self.assertEqual(actions, [ # ('call', 'tpt_callback'), # ('call', '__exit__'), # ]) self.assertEqual(actions, [ ('return', '__enter__'), ('call', 'tpt_callback'), ('return', 'tpt_callback'), ('call', '__exit__'), ('c_call', '__exit__'), ]) def _trace_switch(self, glet): with PythonTracer() as actions: glet.switch() return actions def _check_trace_events_func_already_set(self, glet): actions = self._trace_switch(glet) self.assertEqual(actions, [ ('return', '__enter__'), ('c_call', '_trace_switch'), ('call', 'run'), ('call', 'tpt_callback'), ('return', 'tpt_callback'), ('return', 'run'), ('c_return', '_trace_switch'), ('call', '__exit__'), ('c_call', '__exit__'), ]) def test_trace_events_into_greenlet_func_already_set(self): def run(): return tpt_callback() self._check_trace_events_func_already_set(greenlet.greenlet(run)) def test_trace_events_into_greenlet_subclass_already_set(self): class X(greenlet.greenlet): def run(self): return tpt_callback() self._check_trace_events_func_already_set(X()) def _check_trace_events_from_greenlet_sets_profiler(self, g, tracer): g.switch() tpt_callback() tracer.__exit__() self.assertEqual(tracer.actions, [ ('return', '__enter__'), ('call', 'tpt_callback'), ('return', 'tpt_callback'), ('return', 'run'), ('call', 'tpt_callback'), ('return', 'tpt_callback'), ('call', '__exit__'), ('c_call', '__exit__'), ]) def test_trace_events_from_greenlet_func_sets_profiler(self): tracer = PythonTracer() def run(): tracer.__enter__() return tpt_callback() self._check_trace_events_from_greenlet_sets_profiler(greenlet.greenlet(run), tracer) def test_trace_events_from_greenlet_subclass_sets_profiler(self): tracer = PythonTracer() class X(greenlet.greenlet): def run(self): tracer.__enter__() return tpt_callback() self._check_trace_events_from_greenlet_sets_profiler(X(), tracer) @unittest.skipIf(*DEBUG_BUILD_PY312) def test_trace_events_multiple_greenlets_switching(self): tracer = PythonTracer() g1 = None g2 = None def g1_run(): tracer.__enter__() tpt_callback() g2.switch() tpt_callback() return 42 def g2_run(): tpt_callback() tracer.__exit__() tpt_callback() g1.switch() g1 = greenlet.greenlet(g1_run) g2 = greenlet.greenlet(g2_run) x = g1.switch() self.assertEqual(x, 42) tpt_callback() # ensure not in the trace self.assertEqual(tracer.actions, [ ('return', '__enter__'), ('call', 'tpt_callback'), ('return', 'tpt_callback'), ('c_call', 'g1_run'), ('call', 'g2_run'), ('call', 'tpt_callback'), ('return', 'tpt_callback'), ('call', '__exit__'), ('c_call', '__exit__'), ]) @unittest.skipIf(*DEBUG_BUILD_PY312) def test_trace_events_multiple_greenlets_switching_siblings(self): # Like the first version, but get both greenlets running first # as "siblings" and then establish the tracing. tracer = PythonTracer() g1 = None g2 = None def g1_run(): greenlet.getcurrent().parent.switch() tracer.__enter__() tpt_callback() g2.switch() tpt_callback() return 42 def g2_run(): greenlet.getcurrent().parent.switch() tpt_callback() tracer.__exit__() tpt_callback() g1.switch() g1 = greenlet.greenlet(g1_run) g2 = greenlet.greenlet(g2_run) # Start g1 g1.switch() # And it immediately returns control to us. # Start g2 g2.switch() # Which also returns. Now kick of the real part of the # test. x = g1.switch() self.assertEqual(x, 42) tpt_callback() # ensure not in the trace self.assertEqual(tracer.actions, [ ('return', '__enter__'), ('call', 'tpt_callback'), ('return', 'tpt_callback'), ('c_call', 'g1_run'), ('call', 'tpt_callback'), ('return', 'tpt_callback'), ('call', '__exit__'), ('c_call', '__exit__'), ]) if __name__ == '__main__': unittest.main() greenlet-3.1.0/src/greenlet/tests/test_version.py000077500000000000000000000024731467007427000221740ustar00rootroot00000000000000#! /usr/bin/env python from __future__ import absolute_import from __future__ import print_function import sys import os from unittest import TestCase as NonLeakingTestCase import greenlet # No reason to run this multiple times under leakchecks, # it doesn't do anything. class VersionTests(NonLeakingTestCase): def test_version(self): def find_dominating_file(name): if os.path.exists(name): return name tried = [] here = os.path.abspath(os.path.dirname(__file__)) for i in range(10): up = ['..'] * i path = [here] + up + [name] fname = os.path.join(*path) fname = os.path.abspath(fname) tried.append(fname) if os.path.exists(fname): return fname raise AssertionError("Could not find file " + name + "; checked " + str(tried)) try: setup_py = find_dominating_file('setup.py') except AssertionError as e: self.skipTest("Unable to find setup.py; must be out of tree. " + str(e)) invoke_setup = "%s %s --version" % (sys.executable, setup_py) with os.popen(invoke_setup) as f: sversion = f.read().strip() self.assertEqual(sversion, greenlet.__version__) greenlet-3.1.0/src/greenlet/tests/test_weakref.py000066400000000000000000000015631467007427000221270ustar00rootroot00000000000000import gc import weakref import greenlet from . import TestCase class WeakRefTests(TestCase): def test_dead_weakref(self): def _dead_greenlet(): g = greenlet.greenlet(lambda: None) g.switch() return g o = weakref.ref(_dead_greenlet()) gc.collect() self.assertEqual(o(), None) def test_inactive_weakref(self): o = weakref.ref(greenlet.greenlet()) gc.collect() self.assertEqual(o(), None) def test_dealloc_weakref(self): seen = [] def worker(): try: greenlet.getcurrent().parent.switch() finally: seen.append(g()) g = greenlet.greenlet(worker) g.switch() g2 = greenlet.greenlet(lambda: None, g) g = weakref.ref(g2) g2 = None self.assertEqual(seen, [None]) greenlet-3.1.0/tox.ini000066400000000000000000000011761467007427000146470ustar00rootroot00000000000000[tox] envlist = py{37,38,39,310,311,312,313},py{310,311,312,313}-ns,docs [testenv] commands = python -c 'import greenlet._greenlet as G; assert G.GREENLET_USE_STANDARD_THREADING' python -m unittest discover -v greenlet.tests sphinx-build -b doctest -d docs/_build/doctrees-{envname} docs docs/_build/doctest-{envname} sitepackages = False extras = test docs [testenv:docs] # usedevelop to save rebuilding the extension usedevelop = true commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest extras = docs