templates, sh to start
							parent
							
								
									65226c2be2
								
							
						
					
					
						commit
						769fa3c625
					
				| @ -1,5 +1,6 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| scp ~/Documents/Code/lazywiki/bflw/lazy_wiki.sqlite3 vps: | ||||
| scp ~/Documents/Code/lazywiki/lazy_wiki.sqlite3 vps: | ||||
| (templates, sh to start) | ||||
| ssh vps docker cp lazy_wiki.sqlite3 lazywiki:/db/lazy_wiki.sqlite3 | ||||
| ssh vps docker restart lazywiki | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| from . import web | ||||
| import sys | ||||
| 
 | ||||
| def main(): | ||||
| 
 | ||||
|     web.app.run(host = '0.0.0.0', port = 8080) | ||||
|     web.app.run(host = '0.0.0.0', port = int(sys.argv[2])) | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     print("444444") | ||||
|     main() | ||||
|  | ||||
| @ -0,0 +1,6 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| python3 -m venv venv/ | ||||
| source venv/bin/activate | ||||
| pip install ./lazywiki | ||||
| lazywiki ~/Documents/Code/lazywiki/ 8080 | ||||
| @ -0,0 +1,247 @@ | ||||
| <# | ||||
| .Synopsis | ||||
| Activate a Python virtual environment for the current PowerShell session. | ||||
| 
 | ||||
| .Description | ||||
| Pushes the python executable for a virtual environment to the front of the | ||||
| $Env:PATH environment variable and sets the prompt to signify that you are | ||||
| in a Python virtual environment. Makes use of the command line switches as | ||||
| well as the `pyvenv.cfg` file values present in the virtual environment. | ||||
| 
 | ||||
| .Parameter VenvDir | ||||
| Path to the directory that contains the virtual environment to activate. The | ||||
| default value for this is the parent of the directory that the Activate.ps1 | ||||
| script is located within. | ||||
| 
 | ||||
| .Parameter Prompt | ||||
| The prompt prefix to display when this virtual environment is activated. By | ||||
| default, this prompt is the name of the virtual environment folder (VenvDir) | ||||
| surrounded by parentheses and followed by a single space (ie. '(.venv) '). | ||||
| 
 | ||||
| .Example | ||||
| Activate.ps1 | ||||
| Activates the Python virtual environment that contains the Activate.ps1 script. | ||||
| 
 | ||||
| .Example | ||||
| Activate.ps1 -Verbose | ||||
| Activates the Python virtual environment that contains the Activate.ps1 script, | ||||
| and shows extra information about the activation as it executes. | ||||
| 
 | ||||
| .Example | ||||
| Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv | ||||
| Activates the Python virtual environment located in the specified location. | ||||
| 
 | ||||
| .Example | ||||
| Activate.ps1 -Prompt "MyPython" | ||||
| Activates the Python virtual environment that contains the Activate.ps1 script, | ||||
| and prefixes the current prompt with the specified string (surrounded in | ||||
| parentheses) while the virtual environment is active. | ||||
| 
 | ||||
| .Notes | ||||
| On Windows, it may be required to enable this Activate.ps1 script by setting the | ||||
| execution policy for the user. You can do this by issuing the following PowerShell | ||||
| command: | ||||
| 
 | ||||
| PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser | ||||
| 
 | ||||
| For more information on Execution Policies:  | ||||
| https://go.microsoft.com/fwlink/?LinkID=135170 | ||||
| 
 | ||||
| #> | ||||
| Param( | ||||
|     [Parameter(Mandatory = $false)] | ||||
|     [String] | ||||
|     $VenvDir, | ||||
|     [Parameter(Mandatory = $false)] | ||||
|     [String] | ||||
|     $Prompt | ||||
| ) | ||||
| 
 | ||||
| <# Function declarations --------------------------------------------------- #> | ||||
| 
 | ||||
| <# | ||||
| .Synopsis | ||||
| Remove all shell session elements added by the Activate script, including the | ||||
| addition of the virtual environment's Python executable from the beginning of | ||||
| the PATH variable. | ||||
| 
 | ||||
| .Parameter NonDestructive | ||||
| If present, do not remove this function from the global namespace for the | ||||
| session. | ||||
| 
 | ||||
| #> | ||||
| function global:deactivate ([switch]$NonDestructive) { | ||||
|     # Revert to original values | ||||
| 
 | ||||
|     # The prior prompt: | ||||
|     if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { | ||||
|         Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt | ||||
|         Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT | ||||
|     } | ||||
| 
 | ||||
|     # The prior PYTHONHOME: | ||||
|     if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { | ||||
|         Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME | ||||
|         Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME | ||||
|     } | ||||
| 
 | ||||
|     # The prior PATH: | ||||
|     if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { | ||||
|         Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH | ||||
|         Remove-Item -Path Env:_OLD_VIRTUAL_PATH | ||||
|     } | ||||
| 
 | ||||
|     # Just remove the VIRTUAL_ENV altogether: | ||||
|     if (Test-Path -Path Env:VIRTUAL_ENV) { | ||||
|         Remove-Item -Path env:VIRTUAL_ENV | ||||
|     } | ||||
| 
 | ||||
|     # Just remove VIRTUAL_ENV_PROMPT altogether. | ||||
|     if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { | ||||
|         Remove-Item -Path env:VIRTUAL_ENV_PROMPT | ||||
|     } | ||||
| 
 | ||||
|     # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: | ||||
|     if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { | ||||
|         Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force | ||||
|     } | ||||
| 
 | ||||
|     # Leave deactivate function in the global namespace if requested: | ||||
|     if (-not $NonDestructive) { | ||||
|         Remove-Item -Path function:deactivate | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| <# | ||||
| .Description | ||||
| Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the | ||||
| given folder, and returns them in a map. | ||||
| 
 | ||||
| For each line in the pyvenv.cfg file, if that line can be parsed into exactly | ||||
| two strings separated by `=` (with any amount of whitespace surrounding the =) | ||||
| then it is considered a `key = value` line. The left hand string is the key, | ||||
| the right hand is the value. | ||||
| 
 | ||||
| If the value starts with a `'` or a `"` then the first and last character is | ||||
| stripped from the value before being captured. | ||||
| 
 | ||||
| .Parameter ConfigDir | ||||
| Path to the directory that contains the `pyvenv.cfg` file. | ||||
| #> | ||||
| function Get-PyVenvConfig( | ||||
|     [String] | ||||
|     $ConfigDir | ||||
| ) { | ||||
|     Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" | ||||
| 
 | ||||
|     # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). | ||||
|     $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue | ||||
| 
 | ||||
|     # An empty map will be returned if no config file is found. | ||||
|     $pyvenvConfig = @{ } | ||||
| 
 | ||||
|     if ($pyvenvConfigPath) { | ||||
| 
 | ||||
|         Write-Verbose "File exists, parse `key = value` lines" | ||||
|         $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath | ||||
| 
 | ||||
|         $pyvenvConfigContent | ForEach-Object { | ||||
|             $keyval = $PSItem -split "\s*=\s*", 2 | ||||
|             if ($keyval[0] -and $keyval[1]) { | ||||
|                 $val = $keyval[1] | ||||
| 
 | ||||
|                 # Remove extraneous quotations around a string value. | ||||
|                 if ("'""".Contains($val.Substring(0, 1))) { | ||||
|                     $val = $val.Substring(1, $val.Length - 2) | ||||
|                 } | ||||
| 
 | ||||
|                 $pyvenvConfig[$keyval[0]] = $val | ||||
|                 Write-Verbose "Adding Key: '$($keyval[0])'='$val'" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return $pyvenvConfig | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| <# Begin Activate script --------------------------------------------------- #> | ||||
| 
 | ||||
| # Determine the containing directory of this script | ||||
| $VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition | ||||
| $VenvExecDir = Get-Item -Path $VenvExecPath | ||||
| 
 | ||||
| Write-Verbose "Activation script is located in path: '$VenvExecPath'" | ||||
| Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" | ||||
| Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" | ||||
| 
 | ||||
| # Set values required in priority: CmdLine, ConfigFile, Default | ||||
| # First, get the location of the virtual environment, it might not be | ||||
| # VenvExecDir if specified on the command line. | ||||
| if ($VenvDir) { | ||||
|     Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" | ||||
| } | ||||
| else { | ||||
|     Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." | ||||
|     $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") | ||||
|     Write-Verbose "VenvDir=$VenvDir" | ||||
| } | ||||
| 
 | ||||
| # Next, read the `pyvenv.cfg` file to determine any required value such | ||||
| # as `prompt`. | ||||
| $pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir | ||||
| 
 | ||||
| # Next, set the prompt from the command line, or the config file, or | ||||
| # just use the name of the virtual environment folder. | ||||
| if ($Prompt) { | ||||
|     Write-Verbose "Prompt specified as argument, using '$Prompt'" | ||||
| } | ||||
| else { | ||||
|     Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" | ||||
|     if ($pyvenvCfg -and $pyvenvCfg['prompt']) { | ||||
|         Write-Verbose "  Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" | ||||
|         $Prompt = $pyvenvCfg['prompt']; | ||||
|     } | ||||
|     else { | ||||
|         Write-Verbose "  Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" | ||||
|         Write-Verbose "  Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" | ||||
|         $Prompt = Split-Path -Path $venvDir -Leaf | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Write-Verbose "Prompt = '$Prompt'" | ||||
| Write-Verbose "VenvDir='$VenvDir'" | ||||
| 
 | ||||
| # Deactivate any currently active virtual environment, but leave the | ||||
| # deactivate function in place. | ||||
| deactivate -nondestructive | ||||
| 
 | ||||
| # Now set the environment variable VIRTUAL_ENV, used by many tools to determine | ||||
| # that there is an activated venv. | ||||
| $env:VIRTUAL_ENV = $VenvDir | ||||
| 
 | ||||
| if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { | ||||
| 
 | ||||
|     Write-Verbose "Setting prompt to '$Prompt'" | ||||
| 
 | ||||
|     # Set the prompt to include the env name | ||||
|     # Make sure _OLD_VIRTUAL_PROMPT is global | ||||
|     function global:_OLD_VIRTUAL_PROMPT { "" } | ||||
|     Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT | ||||
|     New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt | ||||
| 
 | ||||
|     function global:prompt { | ||||
|         Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " | ||||
|         _OLD_VIRTUAL_PROMPT | ||||
|     } | ||||
|     $env:VIRTUAL_ENV_PROMPT = $Prompt | ||||
| } | ||||
| 
 | ||||
| # Clear PYTHONHOME | ||||
| if (Test-Path -Path Env:PYTHONHOME) { | ||||
|     Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME | ||||
|     Remove-Item -Path Env:PYTHONHOME | ||||
| } | ||||
| 
 | ||||
| # Add the venv to the PATH | ||||
| Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH | ||||
| $Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" | ||||
											
												Binary file not shown.
											
										
									
								| @ -0,0 +1,69 @@ | ||||
| # This file must be used with "source bin/activate" *from bash* | ||||
| # you cannot run it directly | ||||
| 
 | ||||
| deactivate () { | ||||
|     # reset old environment variables | ||||
|     if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then | ||||
|         PATH="${_OLD_VIRTUAL_PATH:-}" | ||||
|         export PATH | ||||
|         unset _OLD_VIRTUAL_PATH | ||||
|     fi | ||||
|     if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then | ||||
|         PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" | ||||
|         export PYTHONHOME | ||||
|         unset _OLD_VIRTUAL_PYTHONHOME | ||||
|     fi | ||||
| 
 | ||||
|     # This should detect bash and zsh, which have a hash command that must | ||||
|     # be called to get it to forget past commands.  Without forgetting | ||||
|     # past commands the $PATH changes we made may not be respected | ||||
|     if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then | ||||
|         hash -r 2> /dev/null | ||||
|     fi | ||||
| 
 | ||||
|     if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then | ||||
|         PS1="${_OLD_VIRTUAL_PS1:-}" | ||||
|         export PS1 | ||||
|         unset _OLD_VIRTUAL_PS1 | ||||
|     fi | ||||
| 
 | ||||
|     unset VIRTUAL_ENV | ||||
|     unset VIRTUAL_ENV_PROMPT | ||||
|     if [ ! "${1:-}" = "nondestructive" ] ; then | ||||
|     # Self destruct! | ||||
|         unset -f deactivate | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| # unset irrelevant variables | ||||
| deactivate nondestructive | ||||
| 
 | ||||
| VIRTUAL_ENV="/home/grid/Documents/Code/lazywiki/venv" | ||||
| export VIRTUAL_ENV | ||||
| 
 | ||||
| _OLD_VIRTUAL_PATH="$PATH" | ||||
| PATH="$VIRTUAL_ENV/bin:$PATH" | ||||
| export PATH | ||||
| 
 | ||||
| # unset PYTHONHOME if set | ||||
| # this will fail if PYTHONHOME is set to the empty string (which is bad anyway) | ||||
| # could use `if (set -u; : $PYTHONHOME) ;` in bash | ||||
| if [ -n "${PYTHONHOME:-}" ] ; then | ||||
|     _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" | ||||
|     unset PYTHONHOME | ||||
| fi | ||||
| 
 | ||||
| if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then | ||||
|     _OLD_VIRTUAL_PS1="${PS1:-}" | ||||
|     PS1="(venv) ${PS1:-}" | ||||
|     export PS1 | ||||
|     VIRTUAL_ENV_PROMPT="(venv) " | ||||
|     export VIRTUAL_ENV_PROMPT | ||||
| fi | ||||
| 
 | ||||
| # This should detect bash and zsh, which have a hash command that must | ||||
| # be called to get it to forget past commands.  Without forgetting | ||||
| # past commands the $PATH changes we made may not be respected | ||||
| if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then | ||||
|     hash -r 2> /dev/null | ||||
| fi | ||||
| @ -0,0 +1,26 @@ | ||||
| # This file must be used with "source bin/activate.csh" *from csh*. | ||||
| # You cannot run it directly. | ||||
| # Created by Davide Di Blasi <davidedb@gmail.com>. | ||||
| # Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com> | ||||
| 
 | ||||
| alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' | ||||
| 
 | ||||
| # Unset irrelevant variables. | ||||
| deactivate nondestructive | ||||
| 
 | ||||
| setenv VIRTUAL_ENV "/home/grid/Documents/Code/lazywiki/venv" | ||||
| 
 | ||||
| set _OLD_VIRTUAL_PATH="$PATH" | ||||
| setenv PATH "$VIRTUAL_ENV/bin:$PATH" | ||||
| 
 | ||||
| 
 | ||||
| set _OLD_VIRTUAL_PROMPT="$prompt" | ||||
| 
 | ||||
| if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then | ||||
|     set prompt = "(venv) $prompt" | ||||
|     setenv VIRTUAL_ENV_PROMPT "(venv) " | ||||
| endif | ||||
| 
 | ||||
| alias pydoc python -m pydoc | ||||
| 
 | ||||
| rehash | ||||
| @ -0,0 +1,69 @@ | ||||
| # This file must be used with "source <venv>/bin/activate.fish" *from fish* | ||||
| # (https://fishshell.com/); you cannot run it directly. | ||||
| 
 | ||||
| function deactivate  -d "Exit virtual environment and return to normal shell environment" | ||||
|     # reset old environment variables | ||||
|     if test -n "$_OLD_VIRTUAL_PATH" | ||||
|         set -gx PATH $_OLD_VIRTUAL_PATH | ||||
|         set -e _OLD_VIRTUAL_PATH | ||||
|     end | ||||
|     if test -n "$_OLD_VIRTUAL_PYTHONHOME" | ||||
|         set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME | ||||
|         set -e _OLD_VIRTUAL_PYTHONHOME | ||||
|     end | ||||
| 
 | ||||
|     if test -n "$_OLD_FISH_PROMPT_OVERRIDE" | ||||
|         set -e _OLD_FISH_PROMPT_OVERRIDE | ||||
|         # prevents error when using nested fish instances (Issue #93858) | ||||
|         if functions -q _old_fish_prompt | ||||
|             functions -e fish_prompt | ||||
|             functions -c _old_fish_prompt fish_prompt | ||||
|             functions -e _old_fish_prompt | ||||
|         end | ||||
|     end | ||||
| 
 | ||||
|     set -e VIRTUAL_ENV | ||||
|     set -e VIRTUAL_ENV_PROMPT | ||||
|     if test "$argv[1]" != "nondestructive" | ||||
|         # Self-destruct! | ||||
|         functions -e deactivate | ||||
|     end | ||||
| end | ||||
| 
 | ||||
| # Unset irrelevant variables. | ||||
| deactivate nondestructive | ||||
| 
 | ||||
| set -gx VIRTUAL_ENV "/home/grid/Documents/Code/lazywiki/venv" | ||||
| 
 | ||||
| set -gx _OLD_VIRTUAL_PATH $PATH | ||||
| set -gx PATH "$VIRTUAL_ENV/bin" $PATH | ||||
| 
 | ||||
| # Unset PYTHONHOME if set. | ||||
| if set -q PYTHONHOME | ||||
|     set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME | ||||
|     set -e PYTHONHOME | ||||
| end | ||||
| 
 | ||||
| if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" | ||||
|     # fish uses a function instead of an env var to generate the prompt. | ||||
| 
 | ||||
|     # Save the current fish_prompt function as the function _old_fish_prompt. | ||||
|     functions -c fish_prompt _old_fish_prompt | ||||
| 
 | ||||
|     # With the original prompt function renamed, we can override with our own. | ||||
|     function fish_prompt | ||||
|         # Save the return status of the last command. | ||||
|         set -l old_status $status | ||||
| 
 | ||||
|         # Output the venv prompt; color taken from the blue of the Python logo. | ||||
|         printf "%s%s%s" (set_color 4B8BBE) "(venv) " (set_color normal) | ||||
| 
 | ||||
|         # Restore the return status of the previous command. | ||||
|         echo "exit $old_status" | . | ||||
|         # Output the original/"old" prompt. | ||||
|         _old_fish_prompt | ||||
|     end | ||||
| 
 | ||||
|     set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" | ||||
|     set -gx VIRTUAL_ENV_PROMPT "(venv) " | ||||
| end | ||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| @ -0,0 +1,33 @@ | ||||
| #!/home/grid/Documents/Code/lazywiki/venv/bin/python3 | ||||
| # EASY-INSTALL-ENTRY-SCRIPT: 'Lazy-Wiki==0.0.2','console_scripts','lazywiki' | ||||
| import re | ||||
| import sys | ||||
| 
 | ||||
| # for compatibility with easy_install; see #2198 | ||||
| __requires__ = 'Lazy-Wiki==0.0.2' | ||||
| 
 | ||||
| try: | ||||
|     from importlib.metadata import distribution | ||||
| except ImportError: | ||||
|     try: | ||||
|         from importlib_metadata import distribution | ||||
|     except ImportError: | ||||
|         from pkg_resources import load_entry_point | ||||
| 
 | ||||
| 
 | ||||
| def importlib_load_entry_point(spec, group, name): | ||||
|     dist_name, _, _ = spec.partition('==') | ||||
|     matches = ( | ||||
|         entry_point | ||||
|         for entry_point in distribution(dist_name).entry_points | ||||
|         if entry_point.group == group and entry_point.name == name | ||||
|     ) | ||||
|     return next(matches).load() | ||||
| 
 | ||||
| 
 | ||||
| globals().setdefault('load_entry_point', importlib_load_entry_point) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | ||||
|     sys.exit(load_entry_point('Lazy-Wiki==0.0.2', 'console_scripts', 'lazywiki')()) | ||||
| @ -0,0 +1,8 @@ | ||||
| #!/home/grid/Documents/Code/lazywiki/venv/bin/python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| import re | ||||
| import sys | ||||
| from markdown.__main__ import run | ||||
| if __name__ == '__main__': | ||||
|     sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) | ||||
|     sys.exit(run()) | ||||
| @ -0,0 +1,8 @@ | ||||
| #!/home/grid/Documents/Code/lazywiki/venv/bin/python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| import re | ||||
| import sys | ||||
| from pip._internal.cli.main import main | ||||
| if __name__ == '__main__': | ||||
|     sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) | ||||
|     sys.exit(main()) | ||||
| @ -0,0 +1,8 @@ | ||||
| #!/home/grid/Documents/Code/lazywiki/venv/bin/python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| import re | ||||
| import sys | ||||
| from pip._internal.cli.main import main | ||||
| if __name__ == '__main__': | ||||
|     sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) | ||||
|     sys.exit(main()) | ||||
| @ -0,0 +1,8 @@ | ||||
| #!/home/grid/Documents/Code/lazywiki/venv/bin/python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| import re | ||||
| import sys | ||||
| from pip._internal.cli.main import main | ||||
| if __name__ == '__main__': | ||||
|     sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) | ||||
|     sys.exit(main()) | ||||
| @ -0,0 +1 @@ | ||||
| python3 | ||||
| @ -0,0 +1 @@ | ||||
| /usr/bin/python3 | ||||
| @ -0,0 +1 @@ | ||||
| python3 | ||||
| @ -0,0 +1,164 @@ | ||||
| /* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ | ||||
| 
 | ||||
| /* Greenlet object interface */ | ||||
| 
 | ||||
| #ifndef Py_GREENLETOBJECT_H | ||||
| #define Py_GREENLETOBJECT_H | ||||
| 
 | ||||
| 
 | ||||
| #include <Python.h> | ||||
| 
 | ||||
| #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 */ | ||||
| @ -0,0 +1,3 @@ | ||||
| Metadata-Version: 2.1 | ||||
| Name: Lazy-Wiki | ||||
| Version: 0.0.2 | ||||
| @ -0,0 +1,17 @@ | ||||
| MANIFEST.in | ||||
| setup.py | ||||
| Lazy_Wiki.egg-info/PKG-INFO | ||||
| Lazy_Wiki.egg-info/SOURCES.txt | ||||
| Lazy_Wiki.egg-info/dependency_links.txt | ||||
| Lazy_Wiki.egg-info/entry_points.txt | ||||
| Lazy_Wiki.egg-info/requires.txt | ||||
| Lazy_Wiki.egg-info/top_level.txt | ||||
| lazy_wiki/__init__.py | ||||
| lazy_wiki/__main__.py | ||||
| lazy_wiki/db.py | ||||
| lazy_wiki/schema.py | ||||
| lazy_wiki/web.py | ||||
| lazy_wiki/views/delete.tpl | ||||
| lazy_wiki/views/edit.tpl | ||||
| lazy_wiki/views/head.tpl | ||||
| lazy_wiki/views/view.tpl | ||||
| @ -0,0 +1 @@ | ||||
| 
 | ||||
| @ -0,0 +1,2 @@ | ||||
| [console_scripts] | ||||
| lazywiki = lazy_wiki.__main__:main | ||||
| @ -0,0 +1,21 @@ | ||||
| ../../../../bin/lazywiki | ||||
| ../lazy_wiki/__init__.py | ||||
| ../lazy_wiki/__main__.py | ||||
| ../lazy_wiki/__pycache__/__init__.cpython-311.pyc | ||||
| ../lazy_wiki/__pycache__/__main__.cpython-311.pyc | ||||
| ../lazy_wiki/__pycache__/db.cpython-311.pyc | ||||
| ../lazy_wiki/__pycache__/schema.cpython-311.pyc | ||||
| ../lazy_wiki/__pycache__/web.cpython-311.pyc | ||||
| ../lazy_wiki/db.py | ||||
| ../lazy_wiki/schema.py | ||||
| ../lazy_wiki/views/delete.tpl | ||||
| ../lazy_wiki/views/edit.tpl | ||||
| ../lazy_wiki/views/head.tpl | ||||
| ../lazy_wiki/views/view.tpl | ||||
| ../lazy_wiki/web.py | ||||
| PKG-INFO | ||||
| SOURCES.txt | ||||
| dependency_links.txt | ||||
| entry_points.txt | ||||
| requires.txt | ||||
| top_level.txt | ||||
| @ -0,0 +1,3 @@ | ||||
| bottle | ||||
| markdown | ||||
| sqlalchemy==1.4.42 | ||||
| @ -0,0 +1 @@ | ||||
| lazy_wiki | ||||
| @ -0,0 +1 @@ | ||||
| pip | ||||
| @ -0,0 +1,30 @@ | ||||
| BSD 3-Clause License | ||||
| 
 | ||||
| Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)   | ||||
| Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)   | ||||
| Copyright 2004 Manfred Stienstra (the original version) | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
| 
 | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
|    list of conditions and the following disclaimer. | ||||
| 
 | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|    this list of conditions and the following disclaimer in the documentation | ||||
|    and/or other materials provided with the distribution. | ||||
| 
 | ||||
| 3. Neither the name of the copyright holder nor the names of its | ||||
|    contributors may be used to endorse or promote products derived from | ||||
|    this software without specific prior written permission. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| @ -0,0 +1,146 @@ | ||||
| Metadata-Version: 2.1 | ||||
| Name: Markdown | ||||
| Version: 3.6 | ||||
| Summary: Python implementation of John Gruber's Markdown. | ||||
| Author: Manfred Stienstra, Yuri Takhteyev | ||||
| Author-email: Waylan limberg <python.markdown@gmail.com> | ||||
| Maintainer: Isaac Muse | ||||
| Maintainer-email: Waylan Limberg <python.markdown@gmail.com> | ||||
| License: BSD 3-Clause License | ||||
|          | ||||
|         Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)   | ||||
|         Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)   | ||||
|         Copyright 2004 Manfred Stienstra (the original version) | ||||
|          | ||||
|         Redistribution and use in source and binary forms, with or without | ||||
|         modification, are permitted provided that the following conditions are met: | ||||
|          | ||||
|         1. Redistributions of source code must retain the above copyright notice, this | ||||
|            list of conditions and the following disclaimer. | ||||
|          | ||||
|         2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|            this list of conditions and the following disclaimer in the documentation | ||||
|            and/or other materials provided with the distribution. | ||||
|          | ||||
|         3. Neither the name of the copyright holder nor the names of its | ||||
|            contributors may be used to endorse or promote products derived from | ||||
|            this software without specific prior written permission. | ||||
|          | ||||
|         THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|         AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|         IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|         DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|         FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|         DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|         SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|         CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|         OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|         OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|          | ||||
| Project-URL: Homepage, https://Python-Markdown.github.io/ | ||||
| Project-URL: Documentation, https://Python-Markdown.github.io/ | ||||
| Project-URL: Repository, https://github.com/Python-Markdown/markdown | ||||
| Project-URL: Issue Tracker, https://github.com/Python-Markdown/markdown/issues | ||||
| Project-URL: Changelog, https://python-markdown.github.io/changelog/ | ||||
| Keywords: markdown,markdown-parser,python-markdown,markdown-to-html | ||||
| Classifier: Development Status :: 5 - Production/Stable | ||||
| Classifier: License :: OSI Approved :: BSD License | ||||
| Classifier: Operating System :: OS Independent | ||||
| Classifier: Programming Language :: Python | ||||
| Classifier: Programming Language :: Python :: 3 | ||||
| Classifier: Programming Language :: Python :: 3.8 | ||||
| Classifier: Programming Language :: Python :: 3.9 | ||||
| Classifier: Programming Language :: Python :: 3.10 | ||||
| Classifier: Programming Language :: Python :: 3.11 | ||||
| Classifier: Programming Language :: Python :: 3.12 | ||||
| Classifier: Programming Language :: Python :: 3 :: Only | ||||
| Classifier: Programming Language :: Python :: Implementation :: CPython | ||||
| Classifier: Programming Language :: Python :: Implementation :: PyPy | ||||
| Classifier: Topic :: Communications :: Email :: Filters | ||||
| Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries | ||||
| Classifier: Topic :: Internet :: WWW/HTTP :: Site Management | ||||
| Classifier: Topic :: Software Development :: Documentation | ||||
| Classifier: Topic :: Software Development :: Libraries :: Python Modules | ||||
| Classifier: Topic :: Text Processing :: Filters | ||||
| Classifier: Topic :: Text Processing :: Markup :: HTML | ||||
| Classifier: Topic :: Text Processing :: Markup :: Markdown | ||||
| Requires-Python: >=3.8 | ||||
| Description-Content-Type: text/markdown | ||||
| License-File: LICENSE.md | ||||
| Requires-Dist: importlib-metadata >=4.4 ; python_version < "3.10" | ||||
| Provides-Extra: docs | ||||
| Requires-Dist: mkdocs >=1.5 ; extra == 'docs' | ||||
| Requires-Dist: mkdocs-nature >=0.6 ; extra == 'docs' | ||||
| Requires-Dist: mdx-gh-links >=0.2 ; extra == 'docs' | ||||
| Requires-Dist: mkdocstrings[python] ; extra == 'docs' | ||||
| Requires-Dist: mkdocs-gen-files ; extra == 'docs' | ||||
| Requires-Dist: mkdocs-section-index ; extra == 'docs' | ||||
| Requires-Dist: mkdocs-literate-nav ; extra == 'docs' | ||||
| Provides-Extra: testing | ||||
| Requires-Dist: coverage ; extra == 'testing' | ||||
| Requires-Dist: pyyaml ; extra == 'testing' | ||||
| 
 | ||||
| [Python-Markdown][] | ||||
| =================== | ||||
| 
 | ||||
| [![Build Status][build-button]][build] | ||||
| [![Coverage Status][codecov-button]][codecov] | ||||
| [![Latest Version][mdversion-button]][md-pypi] | ||||
| [![Python Versions][pyversion-button]][md-pypi] | ||||
| [![BSD License][bsdlicense-button]][bsdlicense] | ||||
| [![Code of Conduct][codeofconduct-button]][Code of Conduct] | ||||
| 
 | ||||
| [build-button]: https://github.com/Python-Markdown/markdown/workflows/CI/badge.svg?event=push | ||||
| [build]: https://github.com/Python-Markdown/markdown/actions?query=workflow%3ACI+event%3Apush | ||||
| [codecov-button]: https://codecov.io/gh/Python-Markdown/markdown/branch/master/graph/badge.svg | ||||
| [codecov]: https://codecov.io/gh/Python-Markdown/markdown | ||||
| [mdversion-button]: https://img.shields.io/pypi/v/Markdown.svg | ||||
| [md-pypi]: https://pypi.org/project/Markdown/ | ||||
| [pyversion-button]: https://img.shields.io/pypi/pyversions/Markdown.svg | ||||
| [bsdlicense-button]: https://img.shields.io/badge/license-BSD-yellow.svg | ||||
| [bsdlicense]: https://opensource.org/licenses/BSD-3-Clause | ||||
| [codeofconduct-button]: https://img.shields.io/badge/code%20of%20conduct-contributor%20covenant-green.svg?style=flat-square | ||||
| [Code of Conduct]: https://github.com/Python-Markdown/markdown/blob/master/CODE_OF_CONDUCT.md | ||||
| 
 | ||||
| This is a Python implementation of John Gruber's [Markdown][]. | ||||
| It is almost completely compliant with the reference implementation, | ||||
| though there are a few known issues. See [Features][] for information | ||||
| on what exactly is supported and what is not. Additional features are | ||||
| supported by the [Available Extensions][]. | ||||
| 
 | ||||
| [Python-Markdown]: https://Python-Markdown.github.io/ | ||||
| [Markdown]: https://daringfireball.net/projects/markdown/ | ||||
| [Features]: https://Python-Markdown.github.io#Features | ||||
| [Available Extensions]: https://Python-Markdown.github.io/extensions | ||||
| 
 | ||||
| Documentation | ||||
| ------------- | ||||
| 
 | ||||
| ```bash | ||||
| pip install markdown | ||||
| ``` | ||||
| ```python | ||||
| import markdown | ||||
| html = markdown.markdown(your_text_string) | ||||
| ``` | ||||
| 
 | ||||
| For more advanced [installation] and [usage] documentation, see the `docs/` directory | ||||
| of the distribution or the project website at <https://Python-Markdown.github.io/>. | ||||
| 
 | ||||
| [installation]: https://python-markdown.github.io/install/ | ||||
| [usage]: https://python-markdown.github.io/reference/ | ||||
| 
 | ||||
| See the change log at <https://python-markdown.github.io/changelog/>. | ||||
| 
 | ||||
| Support | ||||
| ------- | ||||
| 
 | ||||
| You may report bugs, ask for help, and discuss various other issues on the [bug tracker][]. | ||||
| 
 | ||||
| [bug tracker]: https://github.com/Python-Markdown/markdown/issues | ||||
| 
 | ||||
| Code of Conduct | ||||
| --------------- | ||||
| 
 | ||||
| Everyone interacting in the Python-Markdown project's code bases, issue trackers, | ||||
| and mailing lists is expected to follow the [Code of Conduct]. | ||||
| @ -0,0 +1,74 @@ | ||||
| ../../../bin/markdown_py,sha256=H_j-tI6aArIE37IVvg5JrI2CpQCOcF2g8qIgeiXC1VQ,249 | ||||
| Markdown-3.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | ||||
| Markdown-3.6.dist-info/LICENSE.md,sha256=e6TrbRCzKy0R3OE4ITQDUc27swuozMZ4Qdsv_Ybnmso,1650 | ||||
| Markdown-3.6.dist-info/METADATA,sha256=8_ETqzTxcOemQXj7ujUabMFcDBDGtsRrccFDr1-XWvc,7040 | ||||
| Markdown-3.6.dist-info/RECORD,, | ||||
| Markdown-3.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92 | ||||
| Markdown-3.6.dist-info/entry_points.txt,sha256=lMEyiiA_ZZyfPCBlDviBl-SiU0cfoeuEKpwxw361sKQ,1102 | ||||
| Markdown-3.6.dist-info/top_level.txt,sha256=IAxs8x618RXoH1uCqeLLxXsDefJvE_mIibr_M4sOlyk,9 | ||||
| markdown/__init__.py,sha256=dfzwwdpG9L8QLEPBpLFPIHx_BN056aZXp9xZifTxYIU,1777 | ||||
| markdown/__main__.py,sha256=innFBxRqwPBNxG1zhKktJji4bnRKtVyYYd30ID13Tcw,5859 | ||||
| markdown/__meta__.py,sha256=DqtqnYYLznrkvI1G4JalBc4WpgOp48naNoG9zlMWZas,1712 | ||||
| markdown/__pycache__/__init__.cpython-311.pyc,, | ||||
| markdown/__pycache__/__main__.cpython-311.pyc,, | ||||
| markdown/__pycache__/__meta__.cpython-311.pyc,, | ||||
| markdown/__pycache__/blockparser.cpython-311.pyc,, | ||||
| markdown/__pycache__/blockprocessors.cpython-311.pyc,, | ||||
| markdown/__pycache__/core.cpython-311.pyc,, | ||||
| markdown/__pycache__/htmlparser.cpython-311.pyc,, | ||||
| markdown/__pycache__/inlinepatterns.cpython-311.pyc,, | ||||
| markdown/__pycache__/postprocessors.cpython-311.pyc,, | ||||
| markdown/__pycache__/preprocessors.cpython-311.pyc,, | ||||
| markdown/__pycache__/serializers.cpython-311.pyc,, | ||||
| markdown/__pycache__/test_tools.cpython-311.pyc,, | ||||
| markdown/__pycache__/treeprocessors.cpython-311.pyc,, | ||||
| markdown/__pycache__/util.cpython-311.pyc,, | ||||
| markdown/blockparser.py,sha256=j4CQImVpiq7g9pz8wCxvzT61X_T2iSAjXupHJk8P3eA,5728 | ||||
| markdown/blockprocessors.py,sha256=koY5rq8DixzBCHcquvZJp6x2JYyBGjrwxMWNZhd6D2U,27013 | ||||
| markdown/core.py,sha256=DyyzDsmd-KcuEp8ZWUKJAeUCt7B7G3J3NeqZqp3LphI,21335 | ||||
| markdown/extensions/__init__.py,sha256=9z1khsdKCVrmrJ_2GfxtPAdjD3FyMe5vhC7wmM4O9m0,4822 | ||||
| markdown/extensions/__pycache__/__init__.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/abbr.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/admonition.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/attr_list.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/codehilite.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/def_list.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/extra.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/fenced_code.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/footnotes.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/legacy_attrs.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/legacy_em.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/md_in_html.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/meta.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/nl2br.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/sane_lists.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/smarty.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/tables.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/toc.cpython-311.pyc,, | ||||
| markdown/extensions/__pycache__/wikilinks.cpython-311.pyc,, | ||||
| markdown/extensions/abbr.py,sha256=JqFOfU7JlhIFY06-nZnSU0wDqneFKKWMe95eXB-iLtc,3250 | ||||
| markdown/extensions/admonition.py,sha256=Hqcn3I8JG0i-OPWdoqI189TmlQRgH6bs5PmpCANyLlg,6547 | ||||
| markdown/extensions/attr_list.py,sha256=t3PrgAr5Ebldnq3nJNbteBt79bN0ccXS5RemmQfUZ9g,7820 | ||||
| markdown/extensions/codehilite.py,sha256=ChlmpM6S--j-UK7t82859UpYjm8EftdiLqmgDnknyes,13503 | ||||
| markdown/extensions/def_list.py,sha256=J3NVa6CllfZPsboJCEycPyRhtjBHnOn8ET6omEvVlDo,4029 | ||||
| markdown/extensions/extra.py,sha256=1vleT284kued4HQBtF83IjSumJVo0q3ng6MjTkVNfNQ,2163 | ||||
| markdown/extensions/fenced_code.py,sha256=-fYSmRZ9DTYQ8HO9b_78i47kVyVu6mcVJlqVTMdzvo4,8300 | ||||
| markdown/extensions/footnotes.py,sha256=bRFlmIBOKDI5efG1jZfDkMoV2osfqWip1rN1j2P-mMg,16710 | ||||
| markdown/extensions/legacy_attrs.py,sha256=oWcyNrfP0F6zsBoBOaD5NiwrJyy4kCpgQLl12HA7JGU,2788 | ||||
| markdown/extensions/legacy_em.py,sha256=-Z_w4PEGSS-Xg-2-BtGAnXwwy5g5GDgv2tngASnPgxg,1693 | ||||
| markdown/extensions/md_in_html.py,sha256=y4HEWEnkvfih22fojcaJeAmjx1AtF8N-a_jb6IDFfts,16546 | ||||
| markdown/extensions/meta.py,sha256=v_4Uq7nbcQ76V1YAvqVPiNLbRLIQHJsnfsk-tN70RmY,2600 | ||||
| markdown/extensions/nl2br.py,sha256=9KKcrPs62c3ENNnmOJZs0rrXXqUtTCfd43j1_OPpmgU,1090 | ||||
| markdown/extensions/sane_lists.py,sha256=ogAKcm7gEpcXV7fSTf8JZH5YdKAssPCEOUzdGM3C9Tw,2150 | ||||
| markdown/extensions/smarty.py,sha256=yqT0OiE2AqYrqqZtcUFFmp2eJsQHomiKzgyG2JFb9rI,11048 | ||||
| markdown/extensions/tables.py,sha256=oTDvGD1qp9xjVWPGYNgDBWe9NqsX5gS6UU5wUsQ1bC8,8741 | ||||
| markdown/extensions/toc.py,sha256=PGg-EqbBubm3n0b633r8Xa9kc6JIdbo20HGAOZ6GEl8,18322 | ||||
| markdown/extensions/wikilinks.py,sha256=j7D2sozica6sqXOUa_GuAXqIzxp-7Hi60bfXymiuma8,3285 | ||||
| markdown/htmlparser.py,sha256=dEr6IE7i9b6Tc1gdCLZGeWw6g6-E-jK1Z4KPj8yGk8Q,14332 | ||||
| markdown/inlinepatterns.py,sha256=7_HF5nTOyQag_CyBgU4wwmuI6aMjtadvGadyS9IP21w,38256 | ||||
| markdown/postprocessors.py,sha256=eYi6eW0mGudmWpmsW45hduLwX66Zr8Bf44WyU9vKp-I,4807 | ||||
| markdown/preprocessors.py,sha256=pq5NnHKkOSVQeIo-ajC-Yt44kvyMV97D04FBOQXctJM,3224 | ||||
| markdown/serializers.py,sha256=YtAFYQoOdp_TAmYGow6nBo0eB6I-Sl4PTLdLDfQJHwQ,7174 | ||||
| markdown/test_tools.py,sha256=MtN4cf3ZPDtb83wXLTol-3q3aIGRIkJ2zWr6fd-RgVE,8662 | ||||
| markdown/treeprocessors.py,sha256=o4dnoZZsIeVV8qR45Njr8XgwKleWYDS5pv8dKQhJvv8,17651 | ||||
| markdown/util.py,sha256=vJ1E0xjMzDAlTqLUSJWgdEvxdQfLXDEYUssOQMw9kPQ,13929 | ||||
| @ -0,0 +1,5 @@ | ||||
| Wheel-Version: 1.0 | ||||
| Generator: bdist_wheel (0.43.0) | ||||
| Root-Is-Purelib: true | ||||
| Tag: py3-none-any | ||||
| 
 | ||||
| @ -0,0 +1,22 @@ | ||||
| [console_scripts] | ||||
| markdown_py = markdown.__main__:run | ||||
| 
 | ||||
| [markdown.extensions] | ||||
| abbr = markdown.extensions.abbr:AbbrExtension | ||||
| admonition = markdown.extensions.admonition:AdmonitionExtension | ||||
| attr_list = markdown.extensions.attr_list:AttrListExtension | ||||
| codehilite = markdown.extensions.codehilite:CodeHiliteExtension | ||||
| def_list = markdown.extensions.def_list:DefListExtension | ||||
| extra = markdown.extensions.extra:ExtraExtension | ||||
| fenced_code = markdown.extensions.fenced_code:FencedCodeExtension | ||||
| footnotes = markdown.extensions.footnotes:FootnoteExtension | ||||
| legacy_attrs = markdown.extensions.legacy_attrs:LegacyAttrExtension | ||||
| legacy_em = markdown.extensions.legacy_em:LegacyEmExtension | ||||
| md_in_html = markdown.extensions.md_in_html:MarkdownInHtmlExtension | ||||
| meta = markdown.extensions.meta:MetaExtension | ||||
| nl2br = markdown.extensions.nl2br:Nl2BrExtension | ||||
| sane_lists = markdown.extensions.sane_lists:SaneListExtension | ||||
| smarty = markdown.extensions.smarty:SmartyExtension | ||||
| tables = markdown.extensions.tables:TableExtension | ||||
| toc = markdown.extensions.toc:TocExtension | ||||
| wikilinks = markdown.extensions.wikilinks:WikiLinkExtension | ||||
| @ -0,0 +1 @@ | ||||
| markdown | ||||
| @ -0,0 +1 @@ | ||||
| pip | ||||
| @ -0,0 +1,19 @@ | ||||
| Copyright 2005-2022 SQLAlchemy authors and contributors <see AUTHORS file>. | ||||
| 
 | ||||
| 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. | ||||
| @ -0,0 +1,238 @@ | ||||
| Metadata-Version: 2.1 | ||||
| Name: SQLAlchemy | ||||
| Version: 1.4.42 | ||||
| Summary: Database Abstraction Library | ||||
| Home-page: https://www.sqlalchemy.org | ||||
| Author: Mike Bayer | ||||
| Author-email: mike_mp@zzzcomputing.com | ||||
| License: MIT | ||||
| Project-URL: Documentation, https://docs.sqlalchemy.org | ||||
| Project-URL: Issue Tracker, https://github.com/sqlalchemy/sqlalchemy/ | ||||
| Classifier: Development Status :: 5 - Production/Stable | ||||
| Classifier: Intended Audience :: Developers | ||||
| Classifier: License :: OSI Approved :: MIT License | ||||
| Classifier: Operating System :: OS Independent | ||||
| Classifier: Programming Language :: Python | ||||
| Classifier: Programming Language :: Python :: 2 | ||||
| Classifier: Programming Language :: Python :: 2.7 | ||||
| Classifier: Programming Language :: Python :: 3 | ||||
| Classifier: Programming Language :: Python :: 3.6 | ||||
| Classifier: Programming Language :: Python :: 3.7 | ||||
| Classifier: Programming Language :: Python :: 3.8 | ||||
| Classifier: Programming Language :: Python :: 3.9 | ||||
| Classifier: Programming Language :: Python :: 3.10 | ||||
| Classifier: Programming Language :: Python :: 3.11 | ||||
| Classifier: Programming Language :: Python :: Implementation :: CPython | ||||
| Classifier: Programming Language :: Python :: Implementation :: PyPy | ||||
| Classifier: Topic :: Database :: Front-Ends | ||||
| Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7 | ||||
| Description-Content-Type: text/x-rst | ||||
| License-File: LICENSE | ||||
| Requires-Dist: importlib-metadata ; python_version < "3.8" | ||||
| Requires-Dist: greenlet (!=0.4.17) ; python_version >= "3" and (platform_machine == "aarch64" or (platform_machine == "ppc64le" or (platform_machine == "x86_64" or (platform_machine == "amd64" or (platform_machine == "AMD64" or (platform_machine == "win32" or platform_machine == "WIN32")))))) | ||||
| Provides-Extra: aiomysql | ||||
| Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'aiomysql' | ||||
| Requires-Dist: aiomysql ; (python_version >= "3") and extra == 'aiomysql' | ||||
| Provides-Extra: aiosqlite | ||||
| Requires-Dist: typing-extensions (!=3.10.0.1) ; extra == 'aiosqlite' | ||||
| Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'aiosqlite' | ||||
| Requires-Dist: aiosqlite ; (python_version >= "3") and extra == 'aiosqlite' | ||||
| Provides-Extra: asyncio | ||||
| Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'asyncio' | ||||
| Provides-Extra: asyncmy | ||||
| Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'asyncmy' | ||||
| Requires-Dist: asyncmy (!=0.2.4,>=0.2.3) ; (python_version >= "3") and extra == 'asyncmy' | ||||
| Provides-Extra: mariadb_connector | ||||
| Requires-Dist: mariadb (!=1.1.2,>=1.0.1) ; (python_version >= "3") and extra == 'mariadb_connector' | ||||
| Provides-Extra: mssql | ||||
| Requires-Dist: pyodbc ; extra == 'mssql' | ||||
| Provides-Extra: mssql_pymssql | ||||
| Requires-Dist: pymssql ; extra == 'mssql_pymssql' | ||||
| Provides-Extra: mssql_pyodbc | ||||
| Requires-Dist: pyodbc ; extra == 'mssql_pyodbc' | ||||
| Provides-Extra: mypy | ||||
| Requires-Dist: sqlalchemy2-stubs ; extra == 'mypy' | ||||
| Requires-Dist: mypy (>=0.910) ; (python_version >= "3") and extra == 'mypy' | ||||
| Provides-Extra: mysql | ||||
| Requires-Dist: mysqlclient (<2,>=1.4.0) ; (python_version < "3") and extra == 'mysql' | ||||
| Requires-Dist: mysqlclient (>=1.4.0) ; (python_version >= "3") and extra == 'mysql' | ||||
| Provides-Extra: mysql_connector | ||||
| Requires-Dist: mysql-connector-python ; extra == 'mysql_connector' | ||||
| Provides-Extra: oracle | ||||
| Requires-Dist: cx-oracle (<8,>=7) ; (python_version < "3") and extra == 'oracle' | ||||
| Requires-Dist: cx-oracle (>=7) ; (python_version >= "3") and extra == 'oracle' | ||||
| Provides-Extra: postgresql | ||||
| Requires-Dist: psycopg2 (>=2.7) ; extra == 'postgresql' | ||||
| Provides-Extra: postgresql_asyncpg | ||||
| Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'postgresql_asyncpg' | ||||
| Requires-Dist: asyncpg ; (python_version >= "3") and extra == 'postgresql_asyncpg' | ||||
| Provides-Extra: postgresql_pg8000 | ||||
| Requires-Dist: pg8000 (!=1.29.0,>=1.16.6) ; extra == 'postgresql_pg8000' | ||||
| Provides-Extra: postgresql_psycopg2binary | ||||
| Requires-Dist: psycopg2-binary ; extra == 'postgresql_psycopg2binary' | ||||
| Provides-Extra: postgresql_psycopg2cffi | ||||
| Requires-Dist: psycopg2cffi ; extra == 'postgresql_psycopg2cffi' | ||||
| Provides-Extra: pymysql | ||||
| Requires-Dist: pymysql (<1) ; (python_version < "3") and extra == 'pymysql' | ||||
| Requires-Dist: pymysql ; (python_version >= "3") and extra == 'pymysql' | ||||
| Provides-Extra: sqlcipher | ||||
| Requires-Dist: sqlcipher3-binary ; (python_version >= "3") and extra == 'sqlcipher' | ||||
| 
 | ||||
| SQLAlchemy | ||||
| ========== | ||||
| 
 | ||||
| |PyPI| |Python| |Downloads| | ||||
| 
 | ||||
| .. |PyPI| image:: https://img.shields.io/pypi/v/sqlalchemy | ||||
|     :target: https://pypi.org/project/sqlalchemy | ||||
|     :alt: PyPI | ||||
| 
 | ||||
| .. |Python| image:: https://img.shields.io/pypi/pyversions/sqlalchemy | ||||
|     :target: https://pypi.org/project/sqlalchemy | ||||
|     :alt: PyPI - Python Version | ||||
| 
 | ||||
| .. |Downloads| image:: https://img.shields.io/pypi/dm/sqlalchemy | ||||
|     :target: https://pypi.org/project/sqlalchemy | ||||
|     :alt: PyPI - Downloads | ||||
| 
 | ||||
| 
 | ||||
| The Python SQL Toolkit and Object Relational Mapper | ||||
| 
 | ||||
| Introduction | ||||
| ------------- | ||||
| 
 | ||||
| SQLAlchemy is the Python SQL toolkit and Object Relational Mapper | ||||
| that gives application developers the full power and | ||||
| flexibility of SQL. SQLAlchemy provides a full suite | ||||
| of well known enterprise-level persistence patterns, | ||||
| designed for efficient and high-performing database | ||||
| access, adapted into a simple and Pythonic domain | ||||
| language. | ||||
| 
 | ||||
| Major SQLAlchemy features include: | ||||
| 
 | ||||
| * An industrial strength ORM, built | ||||
|   from the core on the identity map, unit of work, | ||||
|   and data mapper patterns.   These patterns | ||||
|   allow transparent persistence of objects | ||||
|   using a declarative configuration system. | ||||
|   Domain models | ||||
|   can be constructed and manipulated naturally, | ||||
|   and changes are synchronized with the | ||||
|   current transaction automatically. | ||||
| * A relationally-oriented query system, exposing | ||||
|   the full range of SQL's capabilities | ||||
|   explicitly, including joins, subqueries, | ||||
|   correlation, and most everything else, | ||||
|   in terms of the object model. | ||||
|   Writing queries with the ORM uses the same | ||||
|   techniques of relational composition you use | ||||
|   when writing SQL.  While you can drop into | ||||
|   literal SQL at any time, it's virtually never | ||||
|   needed. | ||||
| * A comprehensive and flexible system | ||||
|   of eager loading for related collections and objects. | ||||
|   Collections are cached within a session, | ||||
|   and can be loaded on individual access, all | ||||
|   at once using joins, or by query per collection | ||||
|   across the full result set. | ||||
| * A Core SQL construction system and DBAPI | ||||
|   interaction layer.  The SQLAlchemy Core is | ||||
|   separate from the ORM and is a full database | ||||
|   abstraction layer in its own right, and includes | ||||
|   an extensible Python-based SQL expression | ||||
|   language, schema metadata, connection pooling, | ||||
|   type coercion, and custom types. | ||||
| * All primary and foreign key constraints are | ||||
|   assumed to be composite and natural.  Surrogate | ||||
|   integer primary keys are of course still the | ||||
|   norm, but SQLAlchemy never assumes or hardcodes | ||||
|   to this model. | ||||
| * Database introspection and generation.  Database | ||||
|   schemas can be "reflected" in one step into | ||||
|   Python structures representing database metadata; | ||||
|   those same structures can then generate | ||||
|   CREATE statements right back out - all within | ||||
|   the Core, independent of the ORM. | ||||
| 
 | ||||
| SQLAlchemy's philosophy: | ||||
| 
 | ||||
| * SQL databases behave less and less like object | ||||
|   collections the more size and performance start to | ||||
|   matter; object collections behave less and less like | ||||
|   tables and rows the more abstraction starts to matter. | ||||
|   SQLAlchemy aims to accommodate both of these | ||||
|   principles. | ||||
| * An ORM doesn't need to hide the "R".   A relational | ||||
|   database provides rich, set-based functionality | ||||
|   that should be fully exposed.   SQLAlchemy's | ||||
|   ORM provides an open-ended set of patterns | ||||
|   that allow a developer to construct a custom | ||||
|   mediation layer between a domain model and | ||||
|   a relational schema, turning the so-called | ||||
|   "object relational impedance" issue into | ||||
|   a distant memory. | ||||
| * The developer, in all cases, makes all decisions | ||||
|   regarding the design, structure, and naming conventions | ||||
|   of both the object model as well as the relational | ||||
|   schema.   SQLAlchemy only provides the means | ||||
|   to automate the execution of these decisions. | ||||
| * With SQLAlchemy, there's no such thing as | ||||
|   "the ORM generated a bad query" - you | ||||
|   retain full control over the structure of | ||||
|   queries, including how joins are organized, | ||||
|   how subqueries and correlation is used, what | ||||
|   columns are requested.  Everything SQLAlchemy | ||||
|   does is ultimately the result of a developer- | ||||
|   initiated decision. | ||||
| * Don't use an ORM if the problem doesn't need one. | ||||
|   SQLAlchemy consists of a Core and separate ORM | ||||
|   component.   The Core offers a full SQL expression | ||||
|   language that allows Pythonic construction | ||||
|   of SQL constructs that render directly to SQL | ||||
|   strings for a target database, returning | ||||
|   result sets that are essentially enhanced DBAPI | ||||
|   cursors. | ||||
| * Transactions should be the norm.  With SQLAlchemy's | ||||
|   ORM, nothing goes to permanent storage until | ||||
|   commit() is called.  SQLAlchemy encourages applications | ||||
|   to create a consistent means of delineating | ||||
|   the start and end of a series of operations. | ||||
| * Never render a literal value in a SQL statement. | ||||
|   Bound parameters are used to the greatest degree | ||||
|   possible, allowing query optimizers to cache | ||||
|   query plans effectively and making SQL injection | ||||
|   attacks a non-issue. | ||||
| 
 | ||||
| Documentation | ||||
| ------------- | ||||
| 
 | ||||
| Latest documentation is at: | ||||
| 
 | ||||
| https://www.sqlalchemy.org/docs/ | ||||
| 
 | ||||
| Installation / Requirements | ||||
| --------------------------- | ||||
| 
 | ||||
| Full documentation for installation is at | ||||
| `Installation <https://www.sqlalchemy.org/docs/intro.html#installation>`_. | ||||
| 
 | ||||
| Getting Help / Development / Bug reporting | ||||
| ------------------------------------------ | ||||
| 
 | ||||
| Please refer to the `SQLAlchemy Community Guide <https://www.sqlalchemy.org/support.html>`_. | ||||
| 
 | ||||
| Code of Conduct | ||||
| --------------- | ||||
| 
 | ||||
| Above all, SQLAlchemy places great emphasis on polite, thoughtful, and | ||||
| constructive communication between users and developers. | ||||
| Please see our current Code of Conduct at | ||||
| `Code of Conduct <https://www.sqlalchemy.org/codeofconduct.html>`_. | ||||
| 
 | ||||
| License | ||||
| ------- | ||||
| 
 | ||||
| SQLAlchemy is distributed under the `MIT license | ||||
| <https://www.opensource.org/licenses/mit-license.php>`_. | ||||
| 
 | ||||
| @ -0,0 +1,485 @@ | ||||
| SQLAlchemy-1.4.42.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | ||||
| SQLAlchemy-1.4.42.dist-info/LICENSE,sha256=hZ3tJdo0wetz5uc230xfjOPtLtUpBmMXbwbncg2cmiA,1100 | ||||
| SQLAlchemy-1.4.42.dist-info/METADATA,sha256=kfixyeTkluIm-oaTTIeytK0bb1dOSaXlwK3d9XXym_o,10023 | ||||
| SQLAlchemy-1.4.42.dist-info/RECORD,, | ||||
| SQLAlchemy-1.4.42.dist-info/WHEEL,sha256=lVPXYH8LMHYHuLy0p0zNneWNEw-dpoJ5k5Tb3b38QMM,225 | ||||
| SQLAlchemy-1.4.42.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11 | ||||
| sqlalchemy/__init__.py,sha256=syDTdTFdJQFS7HqzjGJ4l8CLd8LWzyvG5F3MkJD0cCk,4114 | ||||
| sqlalchemy/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/__pycache__/events.cpython-311.pyc,, | ||||
| sqlalchemy/__pycache__/exc.cpython-311.pyc,, | ||||
| sqlalchemy/__pycache__/inspection.cpython-311.pyc,, | ||||
| sqlalchemy/__pycache__/log.cpython-311.pyc,, | ||||
| sqlalchemy/__pycache__/processors.cpython-311.pyc,, | ||||
| sqlalchemy/__pycache__/schema.cpython-311.pyc,, | ||||
| sqlalchemy/__pycache__/types.cpython-311.pyc,, | ||||
| sqlalchemy/cimmutabledict.cpython-311-x86_64-linux-gnu.so,sha256=_YcUdFdTaUvjX_BjfmVZRvPXyd42e0upDP6LVH2eMpI,49104 | ||||
| sqlalchemy/connectors/__init__.py,sha256=2m_LPZFkNExkoaTw14fRActQCcyFl7W81WeYj2O10lM,279 | ||||
| sqlalchemy/connectors/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/connectors/__pycache__/mxodbc.cpython-311.pyc,, | ||||
| sqlalchemy/connectors/__pycache__/pyodbc.cpython-311.pyc,, | ||||
| sqlalchemy/connectors/mxodbc.py,sha256=CApFVkPEL8amXL5HKcG83jU9RbbVg0EQSyxceLWh260,5784 | ||||
| sqlalchemy/connectors/pyodbc.py,sha256=003bqMmK-Hpy-kZYa4vy2CNRz73Fvvj2zUsyhFQnkUc,6855 | ||||
| sqlalchemy/cprocessors.cpython-311-x86_64-linux-gnu.so,sha256=-yT-JdHigN_GzZmvHh2HyzRlys3GlnIqEPjZI8y3tQc,52616 | ||||
| sqlalchemy/cresultproxy.cpython-311-x86_64-linux-gnu.so,sha256=EUTYgyblNDrJzGnsnn__7IE2nr0N9_SQd-aF-R3vNQA,97328 | ||||
| sqlalchemy/databases/__init__.py,sha256=LAm4NHQgjg4sdCED02wUiZj9_0fKBEkStYtqvLWHArk,1010 | ||||
| sqlalchemy/databases/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/__init__.py,sha256=52RcDU2JGS1nW2OHx2nIJ1B_IBI4puWFx09th8Hg-D0,2085 | ||||
| sqlalchemy/dialects/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/firebird/__init__.py,sha256=iZH9WTMjUcsAf6Rl6-64CkcoLOixitP45TSZVSBQYL4,1153 | ||||
| sqlalchemy/dialects/firebird/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/firebird/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/firebird/__pycache__/fdb.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/firebird/__pycache__/kinterbasdb.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/firebird/base.py,sha256=P0ycKcsMKJyglm6uikAVDSc_7UV0NPSIU7hL58HQaog,31171 | ||||
| sqlalchemy/dialects/firebird/fdb.py,sha256=lQhO8S1P8PjUeEW3NXCC1vqNp1DGzBQIUN2eIi-fCC0,4116 | ||||
| sqlalchemy/dialects/firebird/kinterbasdb.py,sha256=2_RZGXSw12FCEeZW0cXxbaR2Bl7GfMd7gGg5pgUiFzg,6479 | ||||
| sqlalchemy/dialects/mssql/__init__.py,sha256=fvIR7jRTPH_4HellLg2kjwYIA3HM_jpNWSw9De0JciE,1788 | ||||
| sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mssql/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mssql/__pycache__/json.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mssql/__pycache__/mxodbc.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mssql/__pycache__/provision.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mssql/base.py,sha256=P6CsAKmEjdENyLoeWPnhRpKXEHXW5oxpVF9-GqT2MIc,116347 | ||||
| sqlalchemy/dialects/mssql/information_schema.py,sha256=R0xpK7xppti2ToGahDksb9jHy9R9MyHTwCfgeNvw3BQ,7584 | ||||
| sqlalchemy/dialects/mssql/json.py,sha256=K1RqVl5bslYyVMtk5CWGjRV_I4K1sszXjx2F_nbCVWI,4558 | ||||
| sqlalchemy/dialects/mssql/mxodbc.py,sha256=HPIxqFtSUY9Ugz-ebNb2T_sLoLp4rQi7qrmezsIYIsM,4808 | ||||
| sqlalchemy/dialects/mssql/provision.py,sha256=m7ofLZYZinDS91Vgs42fK7dhJNnH-J_Bw2x_tP59tCc,4255 | ||||
| sqlalchemy/dialects/mssql/pymssql.py,sha256=Zo4lyJQD77NKCg_RG5hCmaPVgjrZLMjk-zZbYVYRDR8,3863 | ||||
| sqlalchemy/dialects/mssql/pyodbc.py,sha256=T__b7XXLrPAp0eo80ykgelUZQvncF9GcxccPDz_zOgw,24432 | ||||
| sqlalchemy/dialects/mysql/__init__.py,sha256=4C8GY2nAGQOrdGj3CseZqF4NR-CkhVZ_CgXFoskGAJs,2190 | ||||
| sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/oursql.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/mysql/aiomysql.py,sha256=Xqfr0SjvUu-qQZgrDLBnxo4dRQF9ZrI6tpc4HgiXENE,9609 | ||||
| sqlalchemy/dialects/mysql/asyncmy.py,sha256=D8slHiFP3hOvwxf8zMY_-72V1owEhnpO0LmQdkz4n4M,9885 | ||||
| sqlalchemy/dialects/mysql/base.py,sha256=cdE1wUMRADc0vxHpFCEB7lusbOIW7ihXPBOmRY6tpn0,115204 | ||||
| sqlalchemy/dialects/mysql/cymysql.py,sha256=zaVxpSLTg8rvIrI6BtlK0815BCLuLKp2ILHLs57thVA,2271 | ||||
| sqlalchemy/dialects/mysql/dml.py,sha256=EXTHGjiXeNxGyt-jbRH5ZNIkRjTja25gQXAthTCCw8g,6226 | ||||
| sqlalchemy/dialects/mysql/enumerated.py,sha256=Dv5BAF8DxCqfVXIkXt5kzGG-BxNygpdnXrZjyyzKyqM,9364 | ||||
| sqlalchemy/dialects/mysql/expression.py,sha256=HJ4IO3LPJk4cQYIL-O-jN2vLWxVGCqem_K3h8kKNWzE,3741 | ||||
| sqlalchemy/dialects/mysql/json.py,sha256=DMQnyo3PQ_XSPvDl8jt26Ya-fyMEaIJDXQBdLVmsdjE,2313 | ||||
| sqlalchemy/dialects/mysql/mariadb.py,sha256=OBwN9RMQLP-xqLbNMAe5uoz7PEtqa68ln2HwwA6KUn8,585 | ||||
| sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=vLhoFmC9OFh30bHGRFBwWHv3ou3wTZ8WPZOamgmUuWs,7563 | ||||
| sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=CT4bFb2WaFHwBDfRSqK3ieltrkulTYwsX0kgbWPrRao,7690 | ||||
| sqlalchemy/dialects/mysql/mysqldb.py,sha256=qvea9Iuf6SUqb4QSHeCEcbUf3c3FSckjT4jfQSTMlyw,10437 | ||||
| sqlalchemy/dialects/mysql/oursql.py,sha256=fWWMyvhNZ6ywBGvvwJ8DqtBec8cUtziiIjYopBn2WVg,8523 | ||||
| sqlalchemy/dialects/mysql/provision.py,sha256=P5ma4Xy5eSOFIcMjIe_zAwu_6ncSXSLVZYYSMS5Io9c,2649 | ||||
| sqlalchemy/dialects/mysql/pymysql.py,sha256=D106c8jEME1O0wOMV7ZgSuwin7Pv61kKLWYFEEKPpUY,2770 | ||||
| sqlalchemy/dialects/mysql/pyodbc.py,sha256=31587UnRrSQhep_NXt7ii0-3xkAVDJgCGQXSDCpDDuY,4290 | ||||
| sqlalchemy/dialects/mysql/reflection.py,sha256=ZyCxf4PlVqLgpHO8AZbXEadmvqInEwthaNJRiMziMoU,18710 | ||||
| sqlalchemy/dialects/mysql/reserved_words.py,sha256=vvAyUvobiAB46Lpd7DhyWPgp3cWdFaVu9_5P39TEXMM,9104 | ||||
| sqlalchemy/dialects/mysql/types.py,sha256=MrMLGeFo-zJJfGMn39smAfxy5fPvQrgXv49cIrm6Img,24589 | ||||
| sqlalchemy/dialects/oracle/__init__.py,sha256=POVn6bB3yD-b4ZT7CSYQlmNpxDRIRpfuJ8CTTYgphPM,1229 | ||||
| sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/oracle/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/oracle/__pycache__/provision.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/oracle/base.py,sha256=8jixA3aDMW-cyclxBOFIGnpFCVJuixy1raBhmkoaau4,87563 | ||||
| sqlalchemy/dialects/oracle/cx_oracle.py,sha256=78Igd2RmfFXNGSMllfhMPRu-AUbBVGKZ3_VI6a9ouh4,53202 | ||||
| sqlalchemy/dialects/oracle/provision.py,sha256=GtHrw1rtW0bzPSa9dUE-IjDFGaElyJyw4rwHAK3QDVY,5806 | ||||
| sqlalchemy/dialects/postgresql/__init__.py,sha256=thvDDu6Vp68lXdF78wagnnOTq7sFBCDwT5X9x8Mygn8,2509 | ||||
| sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/array.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/json.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/pygresql.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/pypostgresql.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/postgresql/array.py,sha256=oYxNNsFs09isqbdym5WGRPQkUVSF4UqeRDeudis-1bI,14197 | ||||
| sqlalchemy/dialects/postgresql/asyncpg.py,sha256=-vHGMkxXBg7gWRunr-WlmzYgJDfS4RJotNeyzbwRF8Q,35445 | ||||
| sqlalchemy/dialects/postgresql/base.py,sha256=MyxOUhYQFvOiKfX207ZlKR5ap5TbWUrwcGyE_IJF1T0,159101 | ||||
| sqlalchemy/dialects/postgresql/dml.py,sha256=O7GBPR4liaOBBJWGlEU86vrfuzLMy3d3LIbeRZ-nSvc,9582 | ||||
| sqlalchemy/dialects/postgresql/ext.py,sha256=xk8e0iT5L7bPwjpSlw5eI3lwli_LwmdugB7GhnDOtMo,8652 | ||||
| sqlalchemy/dialects/postgresql/hstore.py,sha256=8V6JhPYHtwctKlD3PA_FrGNejxz_YUCVhwYUkaSj0WA,12873 | ||||
| sqlalchemy/dialects/postgresql/json.py,sha256=cIABYehcW9j7ctBCAYXhZFGFQeHgLkisVQB1k2ftnT4,10556 | ||||
| sqlalchemy/dialects/postgresql/pg8000.py,sha256=_UztntjUclGLtty8nvVwlcNtCEFz_9lsQrf-HR7EpLE,17044 | ||||
| sqlalchemy/dialects/postgresql/provision.py,sha256=ZDFEIOvtpBIgCnj1Q1R3-WDWx7lFnE6kdEGNTDFpzAw,4319 | ||||
| sqlalchemy/dialects/postgresql/psycopg2.py,sha256=yUbR7QwBtu46n1TssONOtcF7ci6W2YERDZlyIRzVckI,40340 | ||||
| sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=pBRHxI6KgVePwPO_FAFaE7Nces43qPIviDwbtchi8f8,1691 | ||||
| sqlalchemy/dialects/postgresql/pygresql.py,sha256=oZ847ZkhqqzPeo1BiQnIP7slX7SIbXdoo1OyC5ehChY,8585 | ||||
| sqlalchemy/dialects/postgresql/pypostgresql.py,sha256=_Kw2eXUEAefflJVA1dZJ7aCGt2Lown3PW3i2ab2Eat0,3693 | ||||
| sqlalchemy/dialects/postgresql/ranges.py,sha256=AP3ODSZoH9Yf9CeAPy_GpVVLMtK-4rdebmHWYjgKFug,4763 | ||||
| sqlalchemy/dialects/sqlite/__init__.py,sha256=GwL23FcaoQOso1Sa1RlaF3i5SezqEVjfijvbp8hzRg0,1198 | ||||
| sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sqlite/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sqlite/__pycache__/json.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sqlite/aiosqlite.py,sha256=VY9IAHargDb13k5QKtrodhJcXQ8nErgl4fEj7o39o0Y,10223 | ||||
| sqlalchemy/dialects/sqlite/base.py,sha256=UZrriowzuSoAbQagvqKyC9HTCV0UjWuqIxB0SBmO07E,88435 | ||||
| sqlalchemy/dialects/sqlite/dml.py,sha256=hFloxZoqsrew4tlzS0DSMyzdKJ9-HU0z-dLKWVgR5ns,6865 | ||||
| sqlalchemy/dialects/sqlite/json.py,sha256=oFw4Rt8xw-tkD3IMlm3TDEGe1RqrTyvIuqjABsxn8EI,2518 | ||||
| sqlalchemy/dialects/sqlite/provision.py,sha256=AQILXN5PBUSM05c-SFSFFhPdFqcQDwdoKtUnvLDac14,4676 | ||||
| sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=1MmhAlAaUTnzm7guppjDzGXQ6_OxFtuGzchSiJ0PeRA,5605 | ||||
| sqlalchemy/dialects/sqlite/pysqlite.py,sha256=hIvCxLaxe-HSYmLBnvwmzayqxo2OMJMr35mlFGxeNd8,24453 | ||||
| sqlalchemy/dialects/sybase/__init__.py,sha256=STn2xh97yskErTEYZAyrptb5vYOqPamvb9-QnYd3aG4,1364 | ||||
| sqlalchemy/dialects/sybase/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sybase/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sybase/__pycache__/mxodbc.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sybase/__pycache__/pyodbc.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sybase/__pycache__/pysybase.cpython-311.pyc,, | ||||
| sqlalchemy/dialects/sybase/base.py,sha256=rOfZ2sN3BEtwIDo9nvIWe5VpgxVvjjLt4gSxFb9VyC0,32421 | ||||
| sqlalchemy/dialects/sybase/mxodbc.py,sha256=7U4-Y4mf_o6qzFQraQ7XklDTB0PDddF8u6hFIpuAsCE,939 | ||||
| sqlalchemy/dialects/sybase/pyodbc.py,sha256=bTbAjgvx2LRlhY94DYl_NXRkbVJAd71_LbIvRCtDPX0,2230 | ||||
| sqlalchemy/dialects/sybase/pysybase.py,sha256=-i6vGx7UIVX2arQE9_9GM_YcqeiRCawqxcXnngjvRAY,3370 | ||||
| sqlalchemy/engine/__init__.py,sha256=T44Oyjf2yPp77vDWs8g54h9XVt3FbGRZagKxGxu9XwU,2108 | ||||
| sqlalchemy/engine/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/characteristics.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/create.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/cursor.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/default.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/events.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/interfaces.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/mock.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/reflection.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/result.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/row.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/strategies.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/url.cpython-311.pyc,, | ||||
| sqlalchemy/engine/__pycache__/util.cpython-311.pyc,, | ||||
| sqlalchemy/engine/base.py,sha256=Iv9_Fcju-spBWw_E-KAwaPzNXhFM5EE8XOnBUKLqHt4,124586 | ||||
| sqlalchemy/engine/characteristics.py,sha256=qvd3T8HW470kIxN-x6OzycfjCFdnmbzcaFQeds7KHOw,1817 | ||||
| sqlalchemy/engine/create.py,sha256=q47BzZWgZVxWAaex60SIbFxkfvDFHkDUH5RU0_WnwdA,30797 | ||||
| sqlalchemy/engine/cursor.py,sha256=VSuEZzk6G6NrXjtt0wxMAQwiLz3fPsfOyj1dwJHY0jM,68765 | ||||
| sqlalchemy/engine/default.py,sha256=ndcnktOnj3Tmu-PUnaPtVfL1xpykzJrQmDhGj-5Ox0U,67023 | ||||
| sqlalchemy/engine/events.py,sha256=_qeDo_mMNXXnpQBSAnaRkE1gg6c-r7P5IT78r0aBUuc,33422 | ||||
| sqlalchemy/engine/interfaces.py,sha256=hXZVQUVaXkwsE3oI_1f4xJ9dMGKmO_s3dCFwlaMC7A4,58972 | ||||
| sqlalchemy/engine/mock.py,sha256=wJIFZbvkHwAoi7jCupeyZzuE-J9lqyzhJ6VdrAyMNkw,3626 | ||||
| sqlalchemy/engine/reflection.py,sha256=w0ix23go8S41ye3kM-UOLGVs-UiLUnS8oJqrWI-z9ow,38930 | ||||
| sqlalchemy/engine/result.py,sha256=HwRxVtgpu62MdUxOdlv79HbZx4UKJJoN_uqoe1dQ2WA,58992 | ||||
| sqlalchemy/engine/row.py,sha256=eFw7PtgqNkRSNwMTZPFxKNOBbwZ4V6_eOP8YpYAwRPE,18690 | ||||
| sqlalchemy/engine/strategies.py,sha256=RzejZkLGzWq6QWWJ6a6fyYDdQac4VWCmORCTYEOZwCM,414 | ||||
| sqlalchemy/engine/url.py,sha256=nUMnXWrRX98_1WYH39JObBqw8WvUTke5fzyRI9cO9Ek,26686 | ||||
| sqlalchemy/engine/util.py,sha256=drzyg95MX5NzC10bSQsqQ-dc3k4N4p009JhQuLUS8r0,8442 | ||||
| sqlalchemy/event/__init__.py,sha256=I3Y3cjTy0wC_f-pJRX7B-9UizYje3nh3lIHOlL0Xf00,517 | ||||
| sqlalchemy/event/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/event/__pycache__/api.cpython-311.pyc,, | ||||
| sqlalchemy/event/__pycache__/attr.cpython-311.pyc,, | ||||
| sqlalchemy/event/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/event/__pycache__/legacy.cpython-311.pyc,, | ||||
| sqlalchemy/event/__pycache__/registry.cpython-311.pyc,, | ||||
| sqlalchemy/event/api.py,sha256=yTMDO4cZp-CioTgeDfYGR0O4_zxfFZ-EFdNqM-dOw8E,8043 | ||||
| sqlalchemy/event/attr.py,sha256=j_JWiTWNGvnb3fVrYvUfoiFpn8wB-gWjiF0wyAenOxw,14109 | ||||
| sqlalchemy/event/base.py,sha256=FCifBVGLxkNkpr4mN608ZRcAraML8bcS5IU8_vAJjRQ,10936 | ||||
| sqlalchemy/event/legacy.py,sha256=C09AtrcACXF2gL5c8adk2nLUo1oBfnhFHDkBpv3znUg,6270 | ||||
| sqlalchemy/event/registry.py,sha256=5FuO494J1n2dUYImM9Yz1kl7C8NmO4c4GtKbk_l-S6k,8486 | ||||
| sqlalchemy/events.py,sha256=VrZuUXHgwyx4kMKEielctzyTWqDlm2gvzMcc38jedoE,467 | ||||
| sqlalchemy/exc.py,sha256=x9Z-nIkMQ1r3dqdNmVK5cHQq0zVFrdI6oKkXMw_QB3s,21116 | ||||
| sqlalchemy/ext/__init__.py,sha256=4-X49d1TiOPC-T8JSpaFiMMVNP8JL9bDoBW19wBmXRY,322 | ||||
| sqlalchemy/ext/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/associationproxy.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/automap.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/baked.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/compiler.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/horizontal_shard.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/hybrid.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/indexable.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/instrumentation.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/mutable.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/orderinglist.cpython-311.pyc,, | ||||
| sqlalchemy/ext/__pycache__/serializer.cpython-311.pyc,, | ||||
| sqlalchemy/ext/associationproxy.py,sha256=-687A1ZZMgToO6emMUy8kDOQb-GE8OqfM01xNkh3QtQ,51139 | ||||
| sqlalchemy/ext/asyncio/__init__.py,sha256=XKCzBrSBP_LlqaCKpiMeSPUzwNdQFXUg9GL57EOM9-8,823 | ||||
| sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/ext/asyncio/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/ext/asyncio/__pycache__/engine.cpython-311.pyc,, | ||||
| sqlalchemy/ext/asyncio/__pycache__/events.cpython-311.pyc,, | ||||
| sqlalchemy/ext/asyncio/__pycache__/exc.cpython-311.pyc,, | ||||
| sqlalchemy/ext/asyncio/__pycache__/result.cpython-311.pyc,, | ||||
| sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-311.pyc,, | ||||
| sqlalchemy/ext/asyncio/__pycache__/session.cpython-311.pyc,, | ||||
| sqlalchemy/ext/asyncio/base.py,sha256=UM_GgnHC7VqT6TTgLXj1eQXtUQa1gVL8--wYcTTeotM,2520 | ||||
| sqlalchemy/ext/asyncio/engine.py,sha256=h6vGb79ZP4AF5OSsZggxcCHF-dU-zwXso5YCKyga8pk,26655 | ||||
| sqlalchemy/ext/asyncio/events.py,sha256=_rh2nSAD_6ZqoIRJihiCKUgzSMLBMxBuZ_gUWLpfbHg,1423 | ||||
| sqlalchemy/ext/asyncio/exc.py,sha256=3tcIXQPCJROB3P_TkoHmkzy6o_dIIuMcnnu4tJB__ck,639 | ||||
| sqlalchemy/ext/asyncio/result.py,sha256=OPsKEHnMNP80BJI8kLExY8OQovff_2Wj8Kvxd4t3Ht0,21238 | ||||
| sqlalchemy/ext/asyncio/scoping.py,sha256=fckFlTcwgGjgurVnp69En-4IFwWRqgUV6ukGgPklDJ4,2960 | ||||
| sqlalchemy/ext/asyncio/session.py,sha256=cbXZVkWO_aO0_r2uHC1GC092LMvF7QPAV0_WE9SCDVM,24140 | ||||
| sqlalchemy/ext/automap.py,sha256=-x_Ls5a-opmgYwpjDGjmtrR1hqSy7AvKfUthK5UHD2A,45782 | ||||
| sqlalchemy/ext/baked.py,sha256=DI4hcMk-poznDtAB6S38S7kvo5DXuvrt1CIAT8t5QPw,19969 | ||||
| sqlalchemy/ext/compiler.py,sha256=Q3Dkj-viLi_1_OFL1EUKsz3RJ8aQk6bYwIflx6tbZR0,22629 | ||||
| sqlalchemy/ext/declarative/__init__.py,sha256=NS6-oy4iI6AiMaGWGznzYSx4gnB1fOniOGtqPHxC0ms,1842 | ||||
| sqlalchemy/ext/declarative/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/ext/declarative/__pycache__/extensions.cpython-311.pyc,, | ||||
| sqlalchemy/ext/declarative/extensions.py,sha256=Bg_aQW5sJy5LDpy6AyMTCo5U_IpPJqwg5jQ1lV86Fwc,16541 | ||||
| sqlalchemy/ext/horizontal_shard.py,sha256=2NygP6u9SsOlOqCEqkzNbcSshdxtfxOI78XysnJw3S8,8922 | ||||
| sqlalchemy/ext/hybrid.py,sha256=OSy2ZB-4i46Ai5NYncBQ4VAd19clflN6esAUGAgKxJE,41939 | ||||
| sqlalchemy/ext/indexable.py,sha256=RZmG2074pMoM9-A3evs2ZKqMn3M9uTc3izAI1cN6HQc,11255 | ||||
| sqlalchemy/ext/instrumentation.py,sha256=ReSLFxqbHgwAKNwoQQmKHoqYvWCob_WuXlPAEUJk4pk,14386 | ||||
| sqlalchemy/ext/mutable.py,sha256=nQ0lVZVjJoRXrebrF_XUdxFcHmvN3ROKZzibzypDZN8,32492 | ||||
| sqlalchemy/ext/mypy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | ||||
| sqlalchemy/ext/mypy/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/ext/mypy/__pycache__/apply.cpython-311.pyc,, | ||||
| sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-311.pyc,, | ||||
| sqlalchemy/ext/mypy/__pycache__/infer.cpython-311.pyc,, | ||||
| sqlalchemy/ext/mypy/__pycache__/names.cpython-311.pyc,, | ||||
| sqlalchemy/ext/mypy/__pycache__/plugin.cpython-311.pyc,, | ||||
| sqlalchemy/ext/mypy/__pycache__/util.cpython-311.pyc,, | ||||
| sqlalchemy/ext/mypy/apply.py,sha256=9FIH7jxh6Rl1YDE_3tsacpfNb_8floNQkTuHaNgL7XU,9610 | ||||
| sqlalchemy/ext/mypy/decl_class.py,sha256=buWnXWGOR71CADPZ0_51S49imTXDo-LjTjWsWhhgee0,17343 | ||||
| sqlalchemy/ext/mypy/infer.py,sha256=otnyujWtI9x7IqsYMu-c21_AJigyAtsaHW6XmVXcaBk,18028 | ||||
| sqlalchemy/ext/mypy/names.py,sha256=exMWKhQ7ouSFXojttr0ZadmigT5O_wFQ1rmZ4r7Ks4g,7930 | ||||
| sqlalchemy/ext/mypy/plugin.py,sha256=6JnnsFCOJVwkF1o6FmXRhBYszq5gmli_lqLZJKMhALA,9245 | ||||
| sqlalchemy/ext/mypy/util.py,sha256=NuIWpY4W5CXES-3q3lviisWuQhwtaQmkAejOspfrGls,8242 | ||||
| sqlalchemy/ext/orderinglist.py,sha256=JtRiLDROBsDJnME4kZMDzr3FI6rheP-bd1M-C6zxDPU,13875 | ||||
| sqlalchemy/ext/serializer.py,sha256=RC0aOS6nlFdA0Agkw_-3iiw7Ah2bZnY7sZVZFGj7vHI,5956 | ||||
| sqlalchemy/future/__init__.py,sha256=tDG3ddqc3cRE61x7Q32ekTBQONsdy30drnW6KnIB92g,525 | ||||
| sqlalchemy/future/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/future/__pycache__/engine.cpython-311.pyc,, | ||||
| sqlalchemy/future/engine.py,sha256=Ly-M3NGamVrpnA9XOG_nVLra5f7OlmTMmg7dMb2tn4s,16184 | ||||
| sqlalchemy/future/orm/__init__.py,sha256=EKGpGVxFh3-ZA34c1Ujfy51Z_2oG05CFiSxk48pE1R8,289 | ||||
| sqlalchemy/future/orm/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/inspection.py,sha256=Bcoh4cUJMKjZHcGQP-_Nz-swGXLVVWidj36W2F35Trg,3051 | ||||
| sqlalchemy/log.py,sha256=0zxWZ9_FkRwYyjTvTaBGW9wMlRG0dSmbAb7SvW42EfY,7143 | ||||
| sqlalchemy/orm/__init__.py,sha256=ECAf9d5L7wG58S3ijtNRJaQrdgX3WxDJxTlVVPk0hvk,10964 | ||||
| sqlalchemy/orm/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/attributes.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/clsregistry.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/collections.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/context.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/decl_api.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/decl_base.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/dependency.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/descriptor_props.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/dynamic.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/evaluator.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/events.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/exc.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/identity.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/instrumentation.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/interfaces.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/loading.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/mapper.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/path_registry.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/persistence.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/properties.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/query.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/relationships.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/scoping.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/session.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/state.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/strategies.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/strategy_options.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/sync.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/unitofwork.cpython-311.pyc,, | ||||
| sqlalchemy/orm/__pycache__/util.cpython-311.pyc,, | ||||
| sqlalchemy/orm/attributes.py,sha256=u3tFz0hQdKyh_mCD53rWSKzaPnerquZiM9C71MHsOa4,77098 | ||||
| sqlalchemy/orm/base.py,sha256=HZu51CAOyCjJqGGPJbFqOgqbbA_yQ06Lucxpf-J1B54,15068 | ||||
| sqlalchemy/orm/clsregistry.py,sha256=i8-S8jCSsslTUlOXmfaxoDDkxy3nYGUiZVUeJlpDERA,13286 | ||||
| sqlalchemy/orm/collections.py,sha256=YXLS4MyQIGWVAV5S3sXLvJKdfVCFAQsKFymOgxzkSuU,54723 | ||||
| sqlalchemy/orm/context.py,sha256=HyOcHWsyDckCtuc9mVEXCjVkBNigXdReIp8EnGSZR4A,111259 | ||||
| sqlalchemy/orm/decl_api.py,sha256=rZSz1jys3n_V2woNUZuV8nciN0VgDZFMAiQdNbLkr10,35564 | ||||
| sqlalchemy/orm/decl_base.py,sha256=unKLbWcQZ3At3nbqh6wbK8YtyGpywuJoBoCB00KJse8,44746 | ||||
| sqlalchemy/orm/dependency.py,sha256=RsQ6UtF0Ryl-hgMqw9mm5tqNCZa5bbW56_X1prm6R-8,46987 | ||||
| sqlalchemy/orm/descriptor_props.py,sha256=mdVGbdKc4N8gCxV2RXDGMFZB3V2aWZARUUH9VOe0K1s,25987 | ||||
| sqlalchemy/orm/dynamic.py,sha256=heJsZBQSckDO1k2fYd1x1tap6qEDoS2yogx9VapzIY4,15957 | ||||
| sqlalchemy/orm/evaluator.py,sha256=DIHogWj0T5l0CNB7PfiPg-KlKhYKFJD53g7VHGZ92BY,7942 | ||||
| sqlalchemy/orm/events.py,sha256=81htesUMxJZ1u0DhneIxKYzq9SYBWAHdmj7xSeXYTds,112280 | ||||
| sqlalchemy/orm/exc.py,sha256=dCW9lmc-DpwTJaHo-q8TJac5dK2jWFc4Fes6V8Z_gUo,6532 | ||||
| sqlalchemy/orm/identity.py,sha256=_UnI-6Beolu3xWGGERUQfVg0dc9sb-3G22Xv8yzfKFg,7233 | ||||
| sqlalchemy/orm/instrumentation.py,sha256=L4pmTKaGvmRjd8UTThGF-QqScFIWWs9hx6AZA0-rOn0,20378 | ||||
| sqlalchemy/orm/interfaces.py,sha256=sHcREdNqRuHwX_hXv9UbfwbY5GD60b5vDODQOsdZW6Y,32344 | ||||
| sqlalchemy/orm/loading.py,sha256=5rAng8kIp5UOLtZd5nUjduDLIhUQ80Sodc9W-jSMc1E,49317 | ||||
| sqlalchemy/orm/mapper.py,sha256=rpizqQ7CrtuwLTalpEtQ_jXjt_HoGXdwK1jmeaSushw,143240 | ||||
| sqlalchemy/orm/path_registry.py,sha256=0Akeeayg-OM-pPOAxVCyggGINInYX8kXrQkYWOtesd0,16411 | ||||
| sqlalchemy/orm/persistence.py,sha256=KW7iYNJpEHjUMVFr_pQkkyvoSC1cfSpmzRvVv1H_sgs,84250 | ||||
| sqlalchemy/orm/properties.py,sha256=XmmjsU1XBTyIe1mX8DZ2EdavRutLWxO7QN1k2cJVJ4w,14665 | ||||
| sqlalchemy/orm/query.py,sha256=9aBTx4yskglMfirPKc9u_RwjmtXz2s3Be7dKHCmcEtY,125553 | ||||
| sqlalchemy/orm/relationships.py,sha256=1gF4dUPcvAqszVrwGXC1mp58A0kvWQswUh6DBOWCL08,143945 | ||||
| sqlalchemy/orm/scoping.py,sha256=K4sY8l969uQigmm9VV1GL4XmIA505r_x_1yeDZSRWMQ,7257 | ||||
| sqlalchemy/orm/session.py,sha256=hfwa5CPmkv33IFGvMUnFDEkGUIOy0e_I-dfOEJixmPc,162785 | ||||
| sqlalchemy/orm/state.py,sha256=dqtNddMpqipJTxdYT8YCgRBBnRLx1aYSv4thMtNESrs,33524 | ||||
| sqlalchemy/orm/strategies.py,sha256=GYe8eW9eH5zbmSuly-uS0dn33n0zxj71N3uYZj4nO-M,108082 | ||||
| sqlalchemy/orm/strategy_options.py,sha256=Qaa2h2Ukq3H7k4ytlMfuziFhctDCGnsyrW5b3KIonDk,67454 | ||||
| sqlalchemy/orm/sync.py,sha256=KRyKql_Pgjm_y8clsUOLe8jo5JzM1t6II2vCorbtRow,5824 | ||||
| sqlalchemy/orm/unitofwork.py,sha256=XEMx8PhX-KdP9tQpVgB0mcqnPlVbpSPG4bSKW6zIMRE,27090 | ||||
| sqlalchemy/orm/util.py,sha256=wSmgTqRDkBKuwuisftAqAnJ_18XLi2lRkPK8zXP3yBU,75314 | ||||
| sqlalchemy/pool/__init__.py,sha256=dTuz0I0lQ1aj_BHoMzoBk4FW1rI-4ssLZfXi7826ja8,1603 | ||||
| sqlalchemy/pool/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/pool/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/pool/__pycache__/dbapi_proxy.cpython-311.pyc,, | ||||
| sqlalchemy/pool/__pycache__/events.cpython-311.pyc,, | ||||
| sqlalchemy/pool/__pycache__/impl.cpython-311.pyc,, | ||||
| sqlalchemy/pool/base.py,sha256=CQaopoyk_9yAiLYr1vcgct1LAb6uA2t1AGYoCGKtJfU,39107 | ||||
| sqlalchemy/pool/dbapi_proxy.py,sha256=ZDa32bJzGunYw8OyS5g0GfLoRo-Qwrf7jcsGsA9StSg,4229 | ||||
| sqlalchemy/pool/events.py,sha256=nVQfjW55gD6-DEtTIDUCx-cNHZCKtt7C3gsdqf-PFWg,10299 | ||||
| sqlalchemy/pool/impl.py,sha256=m8kUBUGN3ZikSndBO8mcu2ym8kd_o8vEtLsDSycZXAI,15783 | ||||
| sqlalchemy/processors.py,sha256=LWwr9g-qDHiike9UKqD1yX8ghCxjpAWRdQk7Mh5NepA,5745 | ||||
| sqlalchemy/schema.py,sha256=FLG1OeHCucohyiShM_jvw4OJivdrWSAsI7MxPIX7Q1M,2413 | ||||
| sqlalchemy/sql/__init__.py,sha256=ojeq7QnyQrUcO1Ia7nogzumgOfTKXk6Oib7HuH_hz6Y,4661 | ||||
| sqlalchemy/sql/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/annotation.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/base.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/coercions.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/compiler.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/crud.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/ddl.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/default_comparator.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/dml.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/elements.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/events.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/expression.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/functions.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/lambdas.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/naming.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/operators.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/roles.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/schema.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/selectable.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/sqltypes.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/traversals.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/type_api.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/util.cpython-311.pyc,, | ||||
| sqlalchemy/sql/__pycache__/visitors.cpython-311.pyc,, | ||||
| sqlalchemy/sql/annotation.py,sha256=xGpbeieggvywgRlqerZxz6lYnuSob7C86rJQ87k6Va0,11502 | ||||
| sqlalchemy/sql/base.py,sha256=grJ02HrUj2yoDqlrhbNR_J4RHSahsyFilmvVgnCKb2g,55897 | ||||
| sqlalchemy/sql/coercions.py,sha256=r5bczqjtsm67jl6RiPxyY-ictLPqtPQO0OnhhSN2zCI,34530 | ||||
| sqlalchemy/sql/compiler.py,sha256=9D8kz0YBIz7ojkhsUZeU68pg-ecsKAtSef9ymFhI06A,188380 | ||||
| sqlalchemy/sql/crud.py,sha256=yMGTebDMvF2Hpdto3YSwK6GiRLPpSbRVcZby1zU3n4w,35967 | ||||
| sqlalchemy/sql/ddl.py,sha256=OV8dpPN3tW0nepwxitfz05W804mGJX6I3HHNJsI0mDo,44208 | ||||
| sqlalchemy/sql/default_comparator.py,sha256=GR_hgIHtrZWq6j6yTWpiOWTUjIts5gn-UBcE37JVvfk,11178 | ||||
| sqlalchemy/sql/dml.py,sha256=xAI5vzJFY_Y8_AEhJCo1Cxj-2M9tZzljVcpQ7-iUnpM,54663 | ||||
| sqlalchemy/sql/elements.py,sha256=Z8zavyqLnrAto9Z-JI28s6IR1w7B3No45JgDMjneLNE,181569 | ||||
| sqlalchemy/sql/events.py,sha256=7TLLn-aA-vgg8YbWK04RzXNJQ_gh9zmEHlFJu1947iA,13246 | ||||
| sqlalchemy/sql/expression.py,sha256=cyzp-pgHBfrgQ6_mRxo4T4zNSKIIzd40PlRLgwXI5aM,8828 | ||||
| sqlalchemy/sql/functions.py,sha256=qwzMoP1OIn0Fnw54iGdWNzFB67ABnu6gTi0D94pCPx4,48482 | ||||
| sqlalchemy/sql/lambdas.py,sha256=Jh4K1h_Vqp9bKlVGYrIFGfbFZ6WjhitVPyMtpEpeLZw,44913 | ||||
| sqlalchemy/sql/naming.py,sha256=bmjEtvUW0Ccrc5tzH0_PcoPeA5jAtDLPJ4QxtKaAwe8,6786 | ||||
| sqlalchemy/sql/operators.py,sha256=cJaehhLz2HWqEdFHtBQwasIqgpaUukegNmKJKrtVt84,48538 | ||||
| sqlalchemy/sql/roles.py,sha256=ZTgs4PY4rneDh2suTVbmn25yGZyW34sztNWX8cOUf3M,5638 | ||||
| sqlalchemy/sql/schema.py,sha256=pbLkR844wkM0uzIXTAyauACab3vor1IhmUhBreoqG94,195347 | ||||
| sqlalchemy/sql/selectable.py,sha256=jj2zQ1vZvhJ2jLeTdBH5YIomtZq8oN1Z1mOro2ozQYg,237390 | ||||
| sqlalchemy/sql/sqltypes.py,sha256=s1jHooEjUEIHj_-mmALSnLc-KmnwBNQ7h_4H5yWmdkA,114742 | ||||
| sqlalchemy/sql/traversals.py,sha256=P0GP8F8RlM-lpL5jm3gWj7-NnE8klIXEcDmHk5Dmc-c,52719 | ||||
| sqlalchemy/sql/type_api.py,sha256=IHOZMFl05LgcJ8FfqGGr703bEQEc8c56ru9vJdX-PEU,71036 | ||||
| sqlalchemy/sql/util.py,sha256=JI2eMLpaDzZQjG3Cd4AopUmIMfzQXFIQVUJj8TG8gWw,35856 | ||||
| sqlalchemy/sql/visitors.py,sha256=XLRAf08NKf5ndsNDIRY3wPJaaEBIIxl3DDI_dTKrh_s,27329 | ||||
| sqlalchemy/testing/__init__.py,sha256=TKwXQsqFFV4gjeO48VGaLhCE99qhIVSQNxFrKdP6uNk,2850 | ||||
| sqlalchemy/testing/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/assertions.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/assertsql.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/asyncio.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/config.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/engines.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/entities.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/exclusions.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/fixtures.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/mock.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/pickleable.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/profiling.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/provision.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/requirements.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/schema.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/util.cpython-311.pyc,, | ||||
| sqlalchemy/testing/__pycache__/warnings.cpython-311.pyc,, | ||||
| sqlalchemy/testing/assertions.py,sha256=fcCcIUk04m2XgpotqK2mRD5nKXsyOHXV8tchAAnfQyk,26502 | ||||
| sqlalchemy/testing/assertsql.py,sha256=OIt0QyHKlFJ4zxu6WrX8_ufmBD9KrMgFrjsXTGkU3ys,14964 | ||||
| sqlalchemy/testing/asyncio.py,sha256=B6ZqYcQpT6QtM8gR3o3AcZX32J6ZbWDqTTZGklVo5-I,3671 | ||||
| sqlalchemy/testing/config.py,sha256=XhmzFNkEN_djORr4r6owvoIl3G5zA6Eo5neUiEJXy0E,6543 | ||||
| sqlalchemy/testing/engines.py,sha256=s4h7bKB2Bqmu1rlquR2O88UktP03n6UVrrWkTNhqm3w,13392 | ||||
| sqlalchemy/testing/entities.py,sha256=sOd9BlmZFPQFrBdCUlkOR8lxGEQNExkJmS_V2U5WIOk,3253 | ||||
| sqlalchemy/testing/exclusions.py,sha256=zOthfVJs07z9wN2iAH0rGT39Q76Y_2cBuk5dPEW4wOA,13329 | ||||
| sqlalchemy/testing/fixtures.py,sha256=Rc2Pa9Ae6xtDPqCPGQhB3UFl7h2_5F41TvdKocL7jvE,30924 | ||||
| sqlalchemy/testing/mock.py,sha256=RUTHkpnxCQfsDlEZ_aQttL_3SXLATwxt4olgmSxAsJw,894 | ||||
| sqlalchemy/testing/pickleable.py,sha256=QlwC2Cr7vKkHlj86t2Wlq9eGteZFXkvPpGlWAl9_g7Y,2886 | ||||
| sqlalchemy/testing/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | ||||
| sqlalchemy/testing/plugin/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-311.pyc,, | ||||
| sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-311.pyc,, | ||||
| sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-311.pyc,, | ||||
| sqlalchemy/testing/plugin/__pycache__/reinvent_fixtures_py2k.cpython-311.pyc,, | ||||
| sqlalchemy/testing/plugin/bootstrap.py,sha256=038KOv89msOTFsWoDvCyPRb3ZTMv5eAOOKoGPHuZ7zs,1701 | ||||
| sqlalchemy/testing/plugin/plugin_base.py,sha256=9Bg56KOsZSGW1jLHh_7fle85yFocyV8AGGVlswO9XAU,21540 | ||||
| sqlalchemy/testing/plugin/pytestplugin.py,sha256=_NbB52E6sv6R9NJApMxMnwomH8y7iirfCYKnXvUH1g0,26133 | ||||
| sqlalchemy/testing/plugin/reinvent_fixtures_py2k.py,sha256=MdakbJzFh8N_7gUpX-nFbGPFs3AZRsmDAe-7zucf0ls,3288 | ||||
| sqlalchemy/testing/profiling.py,sha256=ullStV2c-R4jTQJMK1tMKZE5qtSZ-PB1LzHod_hA230,10566 | ||||
| sqlalchemy/testing/provision.py,sha256=IPpsZg4Pc42mXGScKdLri0SjeWJrURXbBF1S9m6ftY8,12070 | ||||
| sqlalchemy/testing/requirements.py,sha256=G-l-20BjZ6eMA7TIy3FO4Ck_T6acLz9XwBheQI4Dql0,43499 | ||||
| sqlalchemy/testing/schema.py,sha256=INOq15yhNyANmheylSQBUlm0IWRaAkEX22BpHSMqn08,6544 | ||||
| sqlalchemy/testing/suite/__init__.py,sha256=_firVc2uS3TMZ3vH2baQzNb17ubM78RHtb9kniSybmk,476 | ||||
| sqlalchemy/testing/suite/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_cte.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_insert.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_results.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_select.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_types.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-311.pyc,, | ||||
| sqlalchemy/testing/suite/test_cte.py,sha256=XuTuaWblSXyO1OOUTShBBmNch7fBdGnlMD84ooVTqFY,6183 | ||||
| sqlalchemy/testing/suite/test_ddl.py,sha256=UwbfljXHdWUen3muIcgnOPi-A4AO6F1QzSOiHf9lU-A,11762 | ||||
| sqlalchemy/testing/suite/test_deprecations.py,sha256=8oLDFUswey8KjPFKRUsqMyGT5sUMMoPQr7-XyIBMehw,5059 | ||||
| sqlalchemy/testing/suite/test_dialect.py,sha256=eR1VVOb2fm955zavpWkmMjipCva3QvEE177U0OG-0LY,10895 | ||||
| sqlalchemy/testing/suite/test_insert.py,sha256=oKtVjFuxqdSV5uKj5-OxdSABupLp0pECkWkSLd2U_QA,11134 | ||||
| sqlalchemy/testing/suite/test_reflection.py,sha256=p-m2BjuWh7jW2vXvY_LxYsfjW47HqGs9O9PUpfm1HIs,58130 | ||||
| sqlalchemy/testing/suite/test_results.py,sha256=xcoSl1ueaHo8LgKZp0Z1lJ44Mhjf2hxlWs_LjNLBNiE,13983 | ||||
| sqlalchemy/testing/suite/test_rowcount.py,sha256=GQQRXIWbb6SfD5hwtBC8qvkGAgi1rI5Pv3c59eoumck,4877 | ||||
| sqlalchemy/testing/suite/test_select.py,sha256=is3BbULeOWOJTRCoUwPnh6Crue15FXfkXKqAkxrFeGM,55464 | ||||
| sqlalchemy/testing/suite/test_sequence.py,sha256=eCyOQlynF8T0cLrIMz0PO6WuW8ktpFVYq_fQp5CQ298,8431 | ||||
| sqlalchemy/testing/suite/test_types.py,sha256=airX8OuJJdft4DU8okOLecJbcUhC15urr60Yu1U8Qe4,48044 | ||||
| sqlalchemy/testing/suite/test_unicode_ddl.py,sha256=CndeAtV3DWJXxLbOoumqf4_mOOYcW_yNOrbKQ4cwFhw,6737 | ||||
| sqlalchemy/testing/suite/test_update_delete.py,sha256=w9MMRqJCm7OW0Q5XaVjS6B8BGY_b_VvBeK3EWr7NKhU,1625 | ||||
| sqlalchemy/testing/util.py,sha256=bvCWcESEPEO8QUTH0CcOa4Xg65EYK--V8Q_XeFcfGfE,12503 | ||||
| sqlalchemy/testing/warnings.py,sha256=l9lI3heNOSbKreAhLcABpaA1e_6Ioi4l7q0mr5jY5OI,2270 | ||||
| sqlalchemy/types.py,sha256=x8YDIEypMHOzWb7dzp67tW2WfDF7xtdh72HVDxm-aaY,2995 | ||||
| sqlalchemy/util/__init__.py,sha256=75NADEtwE5GMCS27VcsEnTsTq1nSvXmJ2GY2aU3Q8hI,6373 | ||||
| sqlalchemy/util/__pycache__/__init__.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/_collections.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/_compat_py3k.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/_preloaded.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/compat.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/concurrency.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/deprecations.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/langhelpers.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/queue.cpython-311.pyc,, | ||||
| sqlalchemy/util/__pycache__/topological.cpython-311.pyc,, | ||||
| sqlalchemy/util/_collections.py,sha256=Nulmym_NZYGN4OyE9cMtIVSoTwOzk3eJpSJ20l8j-lU,29139 | ||||
| sqlalchemy/util/_compat_py3k.py,sha256=KibHVHAIlQfYdl8xs3ZhJQDlWEI6EhudTbOnMc2x9e4,2195 | ||||
| sqlalchemy/util/_concurrency_py3k.py,sha256=5fTahmOgokaam-u-z7Xv0DYKR7YnK4TNjQqbVRYhoKQ,6598 | ||||
| sqlalchemy/util/_preloaded.py,sha256=rx7QZ4T1zDZV5lktSvQlop3O0kdbCFVMmNDp5IOhpXQ,2396 | ||||
| sqlalchemy/util/compat.py,sha256=cRcYIpcBc6aV_yboUTsKpmX1ssICP7kloCJRqEMsRBs,18281 | ||||
| sqlalchemy/util/concurrency.py,sha256=LtozDo0PsiToyVmKzSDnu8qOMhRyGVjTNMsBiKro9d8,2278 | ||||
| sqlalchemy/util/deprecations.py,sha256=RXg5M_MQhaopn00uTB0WEcz5yTTmPu2OCFPNklw5Uv4,11774 | ||||
| sqlalchemy/util/langhelpers.py,sha256=RIlviqqBbBy1XhMnOwQHtmtAofNtMF79aCu3wa9Iycc,56288 | ||||
| sqlalchemy/util/queue.py,sha256=FW6DSeO_GadaW0UA2EXjrBtFPRHO-dNGEoRwqHTfkMA,9293 | ||||
| sqlalchemy/util/topological.py,sha256=MV1lkI2E0JdVIJVplggVo6iO_ZEVlUHRGvMW9AsXJRA,2859 | ||||
| @ -0,0 +1,8 @@ | ||||
| Wheel-Version: 1.0 | ||||
| Generator: bdist_wheel (0.37.1) | ||||
| Root-Is-Purelib: false | ||||
| Tag: cp311-cp311-manylinux_2_5_x86_64 | ||||
| Tag: cp311-cp311-manylinux1_x86_64 | ||||
| Tag: cp311-cp311-manylinux_2_17_x86_64 | ||||
| Tag: cp311-cp311-manylinux2014_x86_64 | ||||
| 
 | ||||
| @ -0,0 +1 @@ | ||||
| sqlalchemy | ||||
											
												Binary file not shown.
											
										
									
								| @ -0,0 +1,222 @@ | ||||
| # don't import any costly modules | ||||
| import sys | ||||
| import os | ||||
| 
 | ||||
| 
 | ||||
| is_pypy = '__pypy__' in sys.builtin_module_names | ||||
| 
 | ||||
| 
 | ||||
| def warn_distutils_present(): | ||||
|     if 'distutils' not in sys.modules: | ||||
|         return | ||||
|     if is_pypy and sys.version_info < (3, 7): | ||||
|         # PyPy for 3.6 unconditionally imports distutils, so bypass the warning | ||||
|         # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 | ||||
|         return | ||||
|     import warnings | ||||
| 
 | ||||
|     warnings.warn( | ||||
|         "Distutils was imported before Setuptools, but importing Setuptools " | ||||
|         "also replaces the `distutils` module in `sys.modules`. This may lead " | ||||
|         "to undesirable behaviors or errors. To avoid these issues, avoid " | ||||
|         "using distutils directly, ensure that setuptools is installed in the " | ||||
|         "traditional way (e.g. not an editable install), and/or make sure " | ||||
|         "that setuptools is always imported before distutils." | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def clear_distutils(): | ||||
|     if 'distutils' not in sys.modules: | ||||
|         return | ||||
|     import warnings | ||||
| 
 | ||||
|     warnings.warn("Setuptools is replacing distutils.") | ||||
|     mods = [ | ||||
|         name | ||||
|         for name in sys.modules | ||||
|         if name == "distutils" or name.startswith("distutils.") | ||||
|     ] | ||||
|     for name in mods: | ||||
|         del sys.modules[name] | ||||
| 
 | ||||
| 
 | ||||
| def enabled(): | ||||
|     """ | ||||
|     Allow selection of distutils by environment variable. | ||||
|     """ | ||||
|     which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local') | ||||
|     return which == 'local' | ||||
| 
 | ||||
| 
 | ||||
| def ensure_local_distutils(): | ||||
|     import importlib | ||||
| 
 | ||||
|     clear_distutils() | ||||
| 
 | ||||
|     # With the DistutilsMetaFinder in place, | ||||
|     # perform an import to cause distutils to be | ||||
|     # loaded from setuptools._distutils. Ref #2906. | ||||
|     with shim(): | ||||
|         importlib.import_module('distutils') | ||||
| 
 | ||||
|     # check that submodules load as expected | ||||
|     core = importlib.import_module('distutils.core') | ||||
|     assert '_distutils' in core.__file__, core.__file__ | ||||
|     assert 'setuptools._distutils.log' not in sys.modules | ||||
| 
 | ||||
| 
 | ||||
| def do_override(): | ||||
|     """ | ||||
|     Ensure that the local copy of distutils is preferred over stdlib. | ||||
| 
 | ||||
|     See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401 | ||||
|     for more motivation. | ||||
|     """ | ||||
|     if enabled(): | ||||
|         warn_distutils_present() | ||||
|         ensure_local_distutils() | ||||
| 
 | ||||
| 
 | ||||
| class _TrivialRe: | ||||
|     def __init__(self, *patterns): | ||||
|         self._patterns = patterns | ||||
| 
 | ||||
|     def match(self, string): | ||||
|         return all(pat in string for pat in self._patterns) | ||||
| 
 | ||||
| 
 | ||||
| class DistutilsMetaFinder: | ||||
|     def find_spec(self, fullname, path, target=None): | ||||
|         # optimization: only consider top level modules and those | ||||
|         # found in the CPython test suite. | ||||
|         if path is not None and not fullname.startswith('test.'): | ||||
|             return | ||||
| 
 | ||||
|         method_name = 'spec_for_{fullname}'.format(**locals()) | ||||
|         method = getattr(self, method_name, lambda: None) | ||||
|         return method() | ||||
| 
 | ||||
|     def spec_for_distutils(self): | ||||
|         if self.is_cpython(): | ||||
|             return | ||||
| 
 | ||||
|         import importlib | ||||
|         import importlib.abc | ||||
|         import importlib.util | ||||
| 
 | ||||
|         try: | ||||
|             mod = importlib.import_module('setuptools._distutils') | ||||
|         except Exception: | ||||
|             # There are a couple of cases where setuptools._distutils | ||||
|             # may not be present: | ||||
|             # - An older Setuptools without a local distutils is | ||||
|             #   taking precedence. Ref #2957. | ||||
|             # - Path manipulation during sitecustomize removes | ||||
|             #   setuptools from the path but only after the hook | ||||
|             #   has been loaded. Ref #2980. | ||||
|             # In either case, fall back to stdlib behavior. | ||||
|             return | ||||
| 
 | ||||
|         class DistutilsLoader(importlib.abc.Loader): | ||||
|             def create_module(self, spec): | ||||
|                 mod.__name__ = 'distutils' | ||||
|                 return mod | ||||
| 
 | ||||
|             def exec_module(self, module): | ||||
|                 pass | ||||
| 
 | ||||
|         return importlib.util.spec_from_loader( | ||||
|             'distutils', DistutilsLoader(), origin=mod.__file__ | ||||
|         ) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def is_cpython(): | ||||
|         """ | ||||
|         Suppress supplying distutils for CPython (build and tests). | ||||
|         Ref #2965 and #3007. | ||||
|         """ | ||||
|         return os.path.isfile('pybuilddir.txt') | ||||
| 
 | ||||
|     def spec_for_pip(self): | ||||
|         """ | ||||
|         Ensure stdlib distutils when running under pip. | ||||
|         See pypa/pip#8761 for rationale. | ||||
|         """ | ||||
|         if self.pip_imported_during_build(): | ||||
|             return | ||||
|         clear_distutils() | ||||
|         self.spec_for_distutils = lambda: None | ||||
| 
 | ||||
|     @classmethod | ||||
|     def pip_imported_during_build(cls): | ||||
|         """ | ||||
|         Detect if pip is being imported in a build script. Ref #2355. | ||||
|         """ | ||||
|         import traceback | ||||
| 
 | ||||
|         return any( | ||||
|             cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None) | ||||
|         ) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def frame_file_is_setup(frame): | ||||
|         """ | ||||
|         Return True if the indicated frame suggests a setup.py file. | ||||
|         """ | ||||
|         # some frames may not have __file__ (#2940) | ||||
|         return frame.f_globals.get('__file__', '').endswith('setup.py') | ||||
| 
 | ||||
|     def spec_for_sensitive_tests(self): | ||||
|         """ | ||||
|         Ensure stdlib distutils when running select tests under CPython. | ||||
| 
 | ||||
|         python/cpython#91169 | ||||
|         """ | ||||
|         clear_distutils() | ||||
|         self.spec_for_distutils = lambda: None | ||||
| 
 | ||||
|     sensitive_tests = ( | ||||
|         [ | ||||
|             'test.test_distutils', | ||||
|             'test.test_peg_generator', | ||||
|             'test.test_importlib', | ||||
|         ] | ||||
|         if sys.version_info < (3, 10) | ||||
|         else [ | ||||
|             'test.test_distutils', | ||||
|         ] | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| for name in DistutilsMetaFinder.sensitive_tests: | ||||
|     setattr( | ||||
|         DistutilsMetaFinder, | ||||
|         f'spec_for_{name}', | ||||
|         DistutilsMetaFinder.spec_for_sensitive_tests, | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| DISTUTILS_FINDER = DistutilsMetaFinder() | ||||
| 
 | ||||
| 
 | ||||
| def add_shim(): | ||||
|     DISTUTILS_FINDER in sys.meta_path or insert_shim() | ||||
| 
 | ||||
| 
 | ||||
| class shim: | ||||
|     def __enter__(self): | ||||
|         insert_shim() | ||||
| 
 | ||||
|     def __exit__(self, exc, value, tb): | ||||
|         remove_shim() | ||||
| 
 | ||||
| 
 | ||||
| def insert_shim(): | ||||
|     sys.meta_path.insert(0, DISTUTILS_FINDER) | ||||
| 
 | ||||
| 
 | ||||
| def remove_shim(): | ||||
|     try: | ||||
|         sys.meta_path.remove(DISTUTILS_FINDER) | ||||
|     except ValueError: | ||||
|         pass | ||||
											
												Binary file not shown.
											
										
									
								
											
												Binary file not shown.
											
										
									
								| @ -0,0 +1 @@ | ||||
| __import__('_distutils_hack').do_override() | ||||
| @ -0,0 +1,64 @@ | ||||
| Bottle is written and maintained by Marcel Hellkamp <marc@bottlepy.org>. | ||||
| 
 | ||||
| Thanks to all the people who found bugs, sent patches, spread the word, helped each other on the mailing-list and made this project possible. I hope the following (alphabetically sorted) list is complete. If you miss your name on that list (or want your name removed) please :doc:`tell me <contact>` or add it yourself. | ||||
| 
 | ||||
| * acasajus | ||||
| * Adam R. Smith | ||||
| * Alexey Borzenkov | ||||
| * Alexis Daboville | ||||
| * Anton I. Sipos | ||||
| * Anton Kolechkin | ||||
| * apexi200sx | ||||
| * apheage | ||||
| * BillMa | ||||
| * Brad Greenlee | ||||
| * Brandon Gilmore | ||||
| * Branko Vukelic | ||||
| * Brian Sierakowski | ||||
| * Brian Wickman | ||||
| * Carl Scharenberg | ||||
| * Damien Degois | ||||
| * David Buxton | ||||
| * Duane Johnson | ||||
| * fcamel | ||||
| * Frank Murphy | ||||
| * Frederic Junod | ||||
| * goldfaber3012 | ||||
| * Greg Milby | ||||
| * gstein | ||||
| * Ian Davis | ||||
| * Itamar Nabriski | ||||
| * Iuri de Silvio | ||||
| * Jaimie Murdock | ||||
| * Jeff Nichols | ||||
| * Jeremy Kelley | ||||
| * joegester | ||||
| * Johannes Krampf | ||||
| * Jonas Haag | ||||
| * Joshua Roesslein | ||||
| * Karl | ||||
| * Kevin Zuber | ||||
| * Kraken | ||||
| * Kyle Fritz | ||||
| * m35 | ||||
| * Marcos Neves | ||||
| * masklinn | ||||
| * Michael Labbe | ||||
| * Michael Soulier | ||||
| * `reddit <http://reddit.com/r/python>`_ | ||||
| * Nicolas Vanhoren | ||||
| * Robert Rollins | ||||
| * rogererens | ||||
| * rwxrwx | ||||
| * Santiago Gala | ||||
| * Sean M. Collins | ||||
| * Sebastian Wollrath | ||||
| * Seth | ||||
| * Sigurd Høgsbro | ||||
| * Stuart Rackham | ||||
| * Sun Ning | ||||
| * Tomás A. Schertel | ||||
| * Tristan Zajonc | ||||
| * voltron | ||||
| * Wieland Hoffmann | ||||
| * zombat | ||||
| @ -0,0 +1 @@ | ||||
| pip | ||||
| @ -0,0 +1,19 @@ | ||||
| Copyright (c) 2012, Marcel Hellkamp. | ||||
| 
 | ||||
| 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. | ||||
| @ -0,0 +1,43 @@ | ||||
| Metadata-Version: 2.1 | ||||
| Name: bottle | ||||
| Version: 0.12.25 | ||||
| Summary: Fast and simple WSGI-framework for small web-applications. | ||||
| Home-page: http://bottlepy.org/ | ||||
| Author: Marcel Hellkamp | ||||
| Author-email: marc@gsites.de | ||||
| License: MIT | ||||
| Platform: any | ||||
| Classifier: Development Status :: 4 - Beta | ||||
| Classifier: Intended Audience :: Developers | ||||
| Classifier: License :: OSI Approved :: MIT License | ||||
| Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries | ||||
| Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers | ||||
| Classifier: Topic :: Internet :: WWW/HTTP :: WSGI | ||||
| Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application | ||||
| Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware | ||||
| Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server | ||||
| Classifier: Topic :: Software Development :: Libraries :: Application Frameworks | ||||
| Classifier: Programming Language :: Python :: 2.5 | ||||
| Classifier: Programming Language :: Python :: 2.6 | ||||
| Classifier: Programming Language :: Python :: 2.7 | ||||
| Classifier: Programming Language :: Python :: 3 | ||||
| Classifier: Programming Language :: Python :: 3.2 | ||||
| Classifier: Programming Language :: Python :: 3.3 | ||||
| Classifier: Programming Language :: Python :: 3.4 | ||||
| Classifier: Programming Language :: Python :: 3.5 | ||||
| Classifier: Programming Language :: Python :: 3.6 | ||||
| Classifier: Programming Language :: Python :: 3.7 | ||||
| License-File: LICENSE | ||||
| License-File: AUTHORS | ||||
| 
 | ||||
| 
 | ||||
| Bottle is a fast and simple micro-framework for small web applications. It | ||||
| offers request dispatching (Routes) with url parameter support, templates, | ||||
| a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and | ||||
| template engines - all in a single file and with no dependencies other than the | ||||
| Python Standard Library. | ||||
| 
 | ||||
| Homepage and documentation: http://bottlepy.org/ | ||||
| 
 | ||||
| Copyright (c) 2016, Marcel Hellkamp. | ||||
| License: MIT (see LICENSE for details) | ||||
| @ -0,0 +1,11 @@ | ||||
| ../../../bin/__pycache__/bottle.cpython-311.pyc,, | ||||
| ../../../bin/bottle.py,sha256=0F2U2N0T9sp9TZB7vNL02rcqoRJEa9hgiV2zOinvKxM,152025 | ||||
| __pycache__/bottle.cpython-311.pyc,, | ||||
| bottle-0.12.25.dist-info/AUTHORS,sha256=A0Y_uWygTzQczXdwcMI8h6XqqWns2pGsJnZOGwu_IPo,1308 | ||||
| bottle-0.12.25.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | ||||
| bottle-0.12.25.dist-info/LICENSE,sha256=0OchHxw8GhxW850YvLB_J_SAyKlVJhd1bdo6M1kzuKY,1061 | ||||
| bottle-0.12.25.dist-info/METADATA,sha256=rJhg3ktccumQ24G8lM8XPLztRVnpo4S4C4fjCRtOWdM,1836 | ||||
| bottle-0.12.25.dist-info/RECORD,, | ||||
| bottle-0.12.25.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 | ||||
| bottle-0.12.25.dist-info/top_level.txt,sha256=cK8mpC1WUvVJAVL1XsjCoCGkD-0Yc-pcrqfH0fRXkhg,7 | ||||
| bottle.py,sha256=iJVdWAfpOi2ksPZlyZtALczPj9aqqcNXrSXSClUCJwc,151993 | ||||
| @ -0,0 +1,5 @@ | ||||
| Wheel-Version: 1.0 | ||||
| Generator: bdist_wheel (0.38.4) | ||||
| Root-Is-Purelib: true | ||||
| Tag: py3-none-any | ||||
| 
 | ||||
| @ -0,0 +1 @@ | ||||
| bottle | ||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| @ -0,0 +1 @@ | ||||
| import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'local') == 'local'; enabled and __import__('_distutils_hack').add_shim();  | ||||
| @ -0,0 +1,51 @@ | ||||
| Original Authors | ||||
| ---------------- | ||||
| * Armin Rigo | ||||
| * Christian Tismer | ||||
| 
 | ||||
| Contributors | ||||
| ------------ | ||||
| * Al Stone | ||||
| * Alexander Schmidt | ||||
| * Alexey Borzenkov | ||||
| * Andreas Schwab | ||||
| * Armin Ronacher | ||||
| * Bin Wang <feisuzhu@163.com> | ||||
| * 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 | ||||
| @ -0,0 +1 @@ | ||||
| pip | ||||
| @ -0,0 +1,30 @@ | ||||
| The 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. | ||||
| @ -0,0 +1,47 @@ | ||||
| PYTHON 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. | ||||
| @ -0,0 +1,102 @@ | ||||
| Metadata-Version: 2.1 | ||||
| Name: greenlet | ||||
| Version: 3.0.3 | ||||
| Summary: Lightweight in-process concurrent programming | ||||
| Home-page: https://greenlet.readthedocs.io/ | ||||
| Author: Alexey Borzenkov | ||||
| Author-email: snaury@gmail.com | ||||
| Maintainer: Jason Madden | ||||
| Maintainer-email: jason@seecoresoftware.com | ||||
| License: MIT License | ||||
| Project-URL: Bug Tracker, https://github.com/python-greenlet/greenlet/issues | ||||
| Project-URL: Source Code, https://github.com/python-greenlet/greenlet/ | ||||
| Project-URL: Documentation, https://greenlet.readthedocs.io/ | ||||
| Keywords: greenlet coroutine concurrency threads cooperative | ||||
| Platform: any | ||||
| Classifier: Development Status :: 5 - Production/Stable | ||||
| Classifier: Intended Audience :: Developers | ||||
| Classifier: License :: OSI Approved :: MIT License | ||||
| Classifier: Natural Language :: English | ||||
| Classifier: Programming Language :: C | ||||
| Classifier: Programming Language :: Python | ||||
| Classifier: Programming Language :: Python :: 3 | ||||
| Classifier: Programming Language :: Python :: 3 :: Only | ||||
| Classifier: Programming Language :: Python :: 3.7 | ||||
| Classifier: Programming Language :: Python :: 3.8 | ||||
| Classifier: Programming Language :: Python :: 3.9 | ||||
| Classifier: Programming Language :: Python :: 3.10 | ||||
| Classifier: Programming Language :: Python :: 3.11 | ||||
| Classifier: Programming Language :: Python :: 3.12 | ||||
| Classifier: Operating System :: OS Independent | ||||
| Classifier: Topic :: Software Development :: Libraries :: Python Modules | ||||
| Requires-Python: >=3.7 | ||||
| Description-Content-Type: text/x-rst | ||||
| License-File: LICENSE | ||||
| License-File: LICENSE.PSF | ||||
| License-File: AUTHORS | ||||
| Provides-Extra: docs | ||||
| Requires-Dist: Sphinx ; extra == 'docs' | ||||
| Requires-Dist: furo ; extra == 'docs' | ||||
| Provides-Extra: test | ||||
| Requires-Dist: objgraph ; extra == 'test' | ||||
| Requires-Dist: psutil ; extra == 'test' | ||||
| 
 | ||||
| .. 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 | ||||
| <https://github.com/python-greenlet/greenlet/blob/adca19bf1f287b3395896a8f41f3f4fd1797fdc7/src/greenlet/tests/test_generator.py#L1>`_). | ||||
| 
 | ||||
| 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 | ||||
| @ -0,0 +1,116 @@ | ||||
| ../../../include/site/python3.11/greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755 | ||||
| greenlet-3.0.3.dist-info/AUTHORS,sha256=swW28t2knVRxRkaEQNZtO7MP9Sgnompb7B6cNgJM8Gk,849 | ||||
| greenlet-3.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | ||||
| greenlet-3.0.3.dist-info/LICENSE,sha256=dpgx1uXfrywggC-sz_H6-0wgJd2PYlPfpH_K1Z1NCXk,1434 | ||||
| greenlet-3.0.3.dist-info/LICENSE.PSF,sha256=5f88I8EQ5JTNfXNsEP2W1GJFe6_soxCEDbZScpjH1Gs,2424 | ||||
| greenlet-3.0.3.dist-info/METADATA,sha256=CHtHlitUM_AS9hKoJfYLF3Vz-UFJlqRnhbRl2-1JrjU,3779 | ||||
| greenlet-3.0.3.dist-info/RECORD,, | ||||
| greenlet-3.0.3.dist-info/WHEEL,sha256=xlJUan517virathN2lKmlOcMObJx20JZaCR_iv23glU,153 | ||||
| greenlet-3.0.3.dist-info/top_level.txt,sha256=YSnRsCRoO61JGlP57o8iKL6rdLWDWuiyKD8ekpWUsDc,9 | ||||
| greenlet/TBrokenGreenlet.cpp,sha256=YgKaHkQV6_dKBrgS0HKDSqZroskv0IwSZDo4bsiwz3w,1029 | ||||
| greenlet/TExceptionState.cpp,sha256=Ctg2YfyEYNjOYbteRB_oIJa9lNGyC7N1F3h4XqqQdg8,1367 | ||||
| greenlet/TGreenlet.cpp,sha256=1xwAzGNqO68AZ4D5lD5DHmGPBohM6nv4BYnLatgIL68,25637 | ||||
| greenlet/TGreenletGlobals.cpp,sha256=qLi1icS1UDSbefTkolz9TycEi_GOUblsEznMp0HFywQ,3268 | ||||
| greenlet/TMainGreenlet.cpp,sha256=FvWtGJDKb64DLy0n-ddcTF6xJDwczPMKSm9mXSsHJKg,3365 | ||||
| greenlet/TPythonState.cpp,sha256=QUoIQzF0HYmAJO_nwX5gXSSlMNL1mkxlN24KJCXIrIQ,14861 | ||||
| greenlet/TStackState.cpp,sha256=VclDR-qiMeJjuiJxL9_u24MJiTgdSaYvr8bWQdTEZjY,7389 | ||||
| greenlet/TThreadStateDestroy.cpp,sha256=EqZ-GjksrWNC20CY_P0yXN43wVRMYEh659SmRRqBaI4,7214 | ||||
| greenlet/TUserGreenlet.cpp,sha256=b_Bmh4WZdS6I1yM2AfHRtd535WovtpYMkpfu2GQpaDs,23618 | ||||
| greenlet/__init__.py,sha256=Dw4tovn18bpPaWQ4SK7jDJe24uV4ao264UfaT0uufxU,1723 | ||||
| greenlet/__pycache__/__init__.cpython-311.pyc,, | ||||
| greenlet/_greenlet.cpython-311-x86_64-linux-gnu.so,sha256=89kThwDfvkHXs3GXeuXnnZb-wShF60h1XyHXZYmkymU,1506232 | ||||
| greenlet/greenlet.cpp,sha256=k9RZolayY79WgjPXwcA3Vcv48MuW7TAtogIZPaDD3gM,48815 | ||||
| greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755 | ||||
| greenlet/greenlet_allocator.hpp,sha256=kxyWW4Qdwlrc7ufgdb5vd6Y7jhauQ699Kod0mqiO1iM,1582 | ||||
| greenlet/greenlet_compiler_compat.hpp,sha256=m7wvwrZqBoCQpDMTP-Z7whdXIES7e3AuXBgvPHSsfxg,4140 | ||||
| greenlet/greenlet_cpython_add_pending.hpp,sha256=apAwIhGlgYrnYn03zWL6Sxy68kltDeb1e0QupZfb3DQ,6043 | ||||
| greenlet/greenlet_cpython_compat.hpp,sha256=ZpN8gewZeOtd6T-mLidA7zteQ_P4vG8T1za_KPvCijg,3621 | ||||
| greenlet/greenlet_exceptions.hpp,sha256=Dt8YdaQn8AK9nBfwU9rrDoMlR2Lw5aLTQV6ZAsHmfsw,3683 | ||||
| greenlet/greenlet_greenlet.hpp,sha256=Ct_EAx4OJL6FvF5g3jV1ybSxnqzLVaRdPi2EcYT1iq4,27728 | ||||
| greenlet/greenlet_internal.hpp,sha256=ZXH5zemWCN8wH8zAqMUGycvz_3IulRL6Gf2hZA6CknE,2703 | ||||
| greenlet/greenlet_refs.hpp,sha256=ECkHKV1CVamtzmWWGKXXMpw8lXLeIzastXM9tfqlsNI,33864 | ||||
| greenlet/greenlet_slp_switch.hpp,sha256=kM1QHA2iV-gH4cFyN6lfIagHQxvJZjWOVJdIxRE3TlQ,3198 | ||||
| greenlet/greenlet_thread_state.hpp,sha256=0UwJCNd86ifwM2yDd3QrNmHAECL-eNADHubwiB_XGA4,20614 | ||||
| greenlet/greenlet_thread_state_dict_cleanup.hpp,sha256=tEN0rI1pZiEsdtr7Oda24gr52fGiHnYTLyM8Vme3Gns,3831 | ||||
| greenlet/greenlet_thread_support.hpp,sha256=XUJ6ljWjf9OYyuOILiz8e_yHvT3fbaUiHdhiPNQUV4s,867 | ||||
| greenlet/platform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | ||||
| greenlet/platform/__pycache__/__init__.cpython-311.pyc,, | ||||
| greenlet/platform/setup_switch_x64_masm.cmd,sha256=ZpClUJeU0ujEPSTWNSepP0W2f9XiYQKA8QKSoVou8EU,143 | ||||
| greenlet/platform/switch_aarch64_gcc.h,sha256=GKC0yWNXnbK2X--X6aguRCMj2Tg7hDU1Zkl3RljDvC8,4307 | ||||
| greenlet/platform/switch_alpha_unix.h,sha256=Z-SvF8JQV3oxWT8JRbL9RFu4gRFxPdJ7cviM8YayMmw,671 | ||||
| greenlet/platform/switch_amd64_unix.h,sha256=EcSFCBlodEBhqhKjcJqY_5Dn_jn7pKpkJlOvp7gFXLI,2748 | ||||
| greenlet/platform/switch_arm32_gcc.h,sha256=Z3KkHszdgq6uU4YN3BxvKMG2AdDnovwCCNrqGWZ1Lyo,2479 | ||||
| greenlet/platform/switch_arm32_ios.h,sha256=mm5_R9aXB92hyxzFRwB71M60H6AlvHjrpTrc72Pz3l8,1892 | ||||
| greenlet/platform/switch_arm64_masm.asm,sha256=4kpTtfy7rfcr8j1CpJLAK21EtZpGDAJXWRU68HEy5A8,1245 | ||||
| greenlet/platform/switch_arm64_masm.obj,sha256=DmLnIB_icoEHAz1naue_pJPTZgR9ElM7-Nmztr-o9_U,746 | ||||
| greenlet/platform/switch_arm64_msvc.h,sha256=RqK5MHLmXI3Q-FQ7tm32KWnbDNZKnkJdq8CR89cz640,398 | ||||
| greenlet/platform/switch_csky_gcc.h,sha256=kDikyiPpewP71KoBZQO_MukDTXTXBiC7x-hF0_2DL0w,1331 | ||||
| greenlet/platform/switch_loongarch64_linux.h,sha256=7M-Dhc4Q8tRbJCJhalDLwU6S9Mx8MjmN1RbTDgIvQTM,779 | ||||
| greenlet/platform/switch_m68k_gcc.h,sha256=VSa6NpZhvyyvF-Q58CTIWSpEDo4FKygOyTz00whctlw,928 | ||||
| greenlet/platform/switch_mips_unix.h,sha256=E0tYsqc5anDY1BhenU1l8DW-nVHC_BElzLgJw3TGtPk,1426 | ||||
| greenlet/platform/switch_ppc64_aix.h,sha256=_BL0iyRr3ZA5iPlr3uk9SJ5sNRWGYLrXcZ5z-CE9anE,3860 | ||||
| greenlet/platform/switch_ppc64_linux.h,sha256=0rriT5XyxPb0GqsSSn_bP9iQsnjsPbBmu0yqo5goSyQ,3815 | ||||
| greenlet/platform/switch_ppc_aix.h,sha256=pHA4slEjUFP3J3SYm1TAlNPhgb2G_PAtax5cO8BEe1A,2941 | ||||
| greenlet/platform/switch_ppc_linux.h,sha256=YwrlKUzxlXuiKMQqr6MFAV1bPzWnmvk6X1AqJZEpOWU,2759 | ||||
| greenlet/platform/switch_ppc_macosx.h,sha256=L8sB0c00V4G2_5cQCG3zX-23DKq3le_Dcj0sUDcACos,2624 | ||||
| greenlet/platform/switch_ppc_unix.h,sha256=POy4bRBcH74Chfw4viFE9bVlZ-7BaNsFC0NnXr1L2tg,2652 | ||||
| greenlet/platform/switch_riscv_unix.h,sha256=jX3vC_xZXiUho8tz4J6Ai8BNQB80yLn03fxkoMztVCU,740 | ||||
| greenlet/platform/switch_s390_unix.h,sha256=RRlGu957ybmq95qNNY4Qw1mcaoT3eBnW5KbVwu48KX8,2763 | ||||
| greenlet/platform/switch_sparc_sun_gcc.h,sha256=xZish9GsMHBienUbUMsX1-ZZ-as7hs36sVhYIE3ew8Y,2797 | ||||
| greenlet/platform/switch_x32_unix.h,sha256=nM98PKtzTWc1lcM7TRMUZJzskVdR1C69U1UqZRWX0GE,1509 | ||||
| greenlet/platform/switch_x64_masm.asm,sha256=nu6n2sWyXuXfpPx40d9YmLfHXUc1sHgeTvX1kUzuvEM,1841 | ||||
| greenlet/platform/switch_x64_masm.obj,sha256=GNtTNxYdo7idFUYsQv-mrXWgyT5EJ93-9q90lN6svtQ,1078 | ||||
| greenlet/platform/switch_x64_msvc.h,sha256=LIeasyKo_vHzspdMzMHbosRhrBfKI4BkQOh4qcTHyJw,1805 | ||||
| greenlet/platform/switch_x86_msvc.h,sha256=TtGOwinbFfnn6clxMNkCz8i6OmgB6kVRrShoF5iT9to,12838 | ||||
| greenlet/platform/switch_x86_unix.h,sha256=VplW9H0FF0cZHw1DhJdIUs5q6YLS4cwb2nYwjF83R1s,3059 | ||||
| greenlet/slp_platformselect.h,sha256=JEnia_2HsTwdqvnnEsDxHQqalYvFJqx_CDsqvNUQYe8,3600 | ||||
| greenlet/tests/__init__.py,sha256=F282jaIavKrhsYgHJEXtIQXKHdHpe9OJOPTK7R40JzI,9022 | ||||
| greenlet/tests/__pycache__/__init__.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/fail_cpp_exception.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/fail_slp_switch.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/leakcheck.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_contextvars.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_cpp.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_extension_interface.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_gc.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_generator.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_generator_nested.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_greenlet.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_greenlet_trash.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_leaks.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_stack_saved.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_throw.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_tracing.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_version.cpython-311.pyc,, | ||||
| greenlet/tests/__pycache__/test_weakref.cpython-311.pyc,, | ||||
| greenlet/tests/_test_extension.c,sha256=vkeGA-6oeJcGILsD7oIrT1qZop2GaTOHXiNT7mcSl-0,5773 | ||||
| greenlet/tests/_test_extension.cpython-311-x86_64-linux-gnu.so,sha256=cYvKKnDFhjTDjM_mYc_4l53g44Iz-CJR5woKXR6Ddqg,36624 | ||||
| greenlet/tests/_test_extension_cpp.cpp,sha256=e0kVnaB8CCaEhE9yHtNyfqTjevsPDKKx-zgxk7PPK48,6565 | ||||
| greenlet/tests/_test_extension_cpp.cpython-311-x86_64-linux-gnu.so,sha256=de1fYlFMrBJRAwPKHWl-OMuBy8AmSXsh14FYYyLj6dI,57288 | ||||
| greenlet/tests/fail_clearing_run_switches.py,sha256=o433oA_nUCtOPaMEGc8VEhZIKa71imVHXFw7TsXaP8M,1263 | ||||
| greenlet/tests/fail_cpp_exception.py,sha256=o_ZbipWikok8Bjc-vjiQvcb5FHh2nVW-McGKMLcMzh0,985 | ||||
| greenlet/tests/fail_initialstub_already_started.py,sha256=txENn5IyzGx2p-XR1XB7qXmC8JX_4mKDEA8kYBXUQKc,1961 | ||||
| greenlet/tests/fail_slp_switch.py,sha256=rJBZcZfTWR3e2ERQtPAud6YKShiDsP84PmwOJbp4ey0,524 | ||||
| greenlet/tests/fail_switch_three_greenlets.py,sha256=zSitV7rkNnaoHYVzAGGLnxz-yPtohXJJzaE8ehFDQ0M,956 | ||||
| greenlet/tests/fail_switch_three_greenlets2.py,sha256=FPJensn2EJxoropl03JSTVP3kgP33k04h6aDWWozrOk,1285 | ||||
| greenlet/tests/fail_switch_two_greenlets.py,sha256=1CaI8s3504VbbF1vj1uBYuy-zxBHVzHPIAd1LIc8ONg,817 | ||||
| greenlet/tests/leakcheck.py,sha256=inbfM7_oVzd8jIKGxCgo4JqpFZaDAnWPkSULJ8vIE1s,11964 | ||||
| greenlet/tests/test_contextvars.py,sha256=0n5pR_lbpAppc5wFfK0e1SwYLM-fsSFp72B5_ArLPGE,10348 | ||||
| greenlet/tests/test_cpp.py,sha256=hpxhFAdKJTpAVZP8CBGs1ZcrKdscI9BaDZk4btkI5d4,2736 | ||||
| greenlet/tests/test_extension_interface.py,sha256=eJ3cwLacdK2WbsrC-4DgeyHdwLRcG4zx7rrkRtqSzC4,3829 | ||||
| greenlet/tests/test_gc.py,sha256=PCOaRpIyjNnNlDogGL3FZU_lrdXuM-pv1rxeE5TP5mc,2923 | ||||
| greenlet/tests/test_generator.py,sha256=tONXiTf98VGm347o1b-810daPiwdla5cbpFg6QI1R1g,1240 | ||||
| greenlet/tests/test_generator_nested.py,sha256=7v4HOYrf1XZP39dk5IUMubdZ8yc3ynwZcqj9GUJyMSA,3718 | ||||
| greenlet/tests/test_greenlet.py,sha256=95qgDR-xtB0jzEFLirNx7HPUdwHikVMvDdyUoCvyjOo,45354 | ||||
| greenlet/tests/test_greenlet_trash.py,sha256=P6r-3K4fmXX8foW8BVgthuqVKjicHMDvxfK7Al4x028,7508 | ||||
| greenlet/tests/test_leaks.py,sha256=wskLqCAvqZ3qTZkam_wXzd-E5zelUjlXS5Ss8KshtZY,17465 | ||||
| greenlet/tests/test_stack_saved.py,sha256=eyzqNY2VCGuGlxhT_In6TvZ6Okb0AXFZVyBEnK1jDwA,446 | ||||
| greenlet/tests/test_throw.py,sha256=u2TQ_WvvCd6N6JdXWIxVEcXkKu5fepDlz9dktYdmtng,3712 | ||||
| greenlet/tests/test_tracing.py,sha256=VlwzMU0C1noospZhuUMyB7MHw200emIvGCN_6G2p2ZU,8250 | ||||
| greenlet/tests/test_version.py,sha256=O9DpAITsOFgiRcjd4odQ7ejmwx_N9Q1zQENVcbtFHIc,1339 | ||||
| greenlet/tests/test_weakref.py,sha256=F8M23btEF87bIbpptLNBORosbQqNZGiYeKMqYjWrsak,883 | ||||
| @ -0,0 +1,6 @@ | ||||
| Wheel-Version: 1.0 | ||||
| Generator: bdist_wheel (0.42.0) | ||||
| Root-Is-Purelib: false | ||||
| Tag: cp311-cp311-manylinux_2_24_x86_64 | ||||
| Tag: cp311-cp311-manylinux_2_28_x86_64 | ||||
| 
 | ||||
| @ -0,0 +1 @@ | ||||
| greenlet | ||||
| @ -0,0 +1,45 @@ | ||||
| /* -*- 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<BrokenGreenlet*>(ptr), | ||||
|                                 1); | ||||
| } | ||||
| 
 | ||||
| greenlet::PythonAllocator<greenlet::BrokenGreenlet> 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
 | ||||
| @ -0,0 +1,62 @@ | ||||
| #ifndef GREENLET_EXCEPTION_STATE_CPP | ||||
| #define GREENLET_EXCEPTION_STATE_CPP | ||||
| 
 | ||||
| #include <Python.h> | ||||
| #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
 | ||||
| @ -0,0 +1,714 @@ | ||||
| /* -*- 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
 | ||||
| @ -0,0 +1,94 @@ | ||||
| /* -*- 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<greenlet::cleanup_queue_t&>(this->thread_states_to_destroy); | ||||
|         q.push_back(ts); | ||||
|     } | ||||
| 
 | ||||
|     ThreadState* take_next_to_destroy() const | ||||
|     { | ||||
|         greenlet::cleanup_queue_t& q = const_cast<greenlet::cleanup_queue_t&>(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
 | ||||
| @ -0,0 +1,155 @@ | ||||
| /* -*- 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> 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<MainGreenlet*>(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
 | ||||
| @ -0,0 +1,375 @@ | ||||
| #ifndef GREENLET_PYTHON_STATE_CPP | ||||
| #define GREENLET_PYTHON_STATE_CPP | ||||
| 
 | ||||
| #include <Python.h> | ||||
| #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 | ||||
|     ,trash_delete_nesting(0) | ||||
| #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 = C_RECURSION_LIMIT - tstate->c_recursion_remaining; | ||||
|   #else // not 312
 | ||||
|     this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; | ||||
|   #endif // GREENLET_PY312
 | ||||
|     this->current_frame = tstate->cframe->current_frame; | ||||
|     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_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 = 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
 | ||||
|     tstate->cframe->current_frame = this->current_frame; | ||||
|     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_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 ``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
 | ||||
| @ -0,0 +1,265 @@ | ||||
| #ifndef GREENLET_STACK_STATE_CPP | ||||
| #define GREENLET_STACK_STATE_CPP | ||||
| 
 | ||||
| #include "greenlet_greenlet.hpp" | ||||
| 
 | ||||
| namespace greenlet { | ||||
| 
 | ||||
| #ifdef GREENLET_USE_STDIO | ||||
| #include <iostream> | ||||
| 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<StackState*>(¤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<StackState*>(¤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<char*>(vdest); | ||||
|     const char* src = static_cast<const char*>(vsrc); | ||||
|     if (src + n <= this->_stack_start | ||||
|         || src >= this->_stack_start + this->_stack_saved | ||||
|         || this->_stack_saved == 0) { | ||||
|         // Nothing we're copying was spilled from the stack
 | ||||
|         memcpy(dest, src, n); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (src < this->_stack_start) { | ||||
|         // Copy the part before the saved stack.
 | ||||
|         // We know src + n > _stack_start due to the test above.
 | ||||
|         const size_t nbefore = this->_stack_start - src; | ||||
|         memcpy(dest, src, nbefore); | ||||
|         dest += nbefore; | ||||
|         src += nbefore; | ||||
|         n -= nbefore; | ||||
|     } | ||||
|     // We know src >= _stack_start after the before-copy, and
 | ||||
|     // src < _stack_start + _stack_saved due to the first if condition
 | ||||
|     size_t nspilled = std::min<size_t>(n, this->_stack_start + this->_stack_saved - src); | ||||
|     memcpy(dest, this->stack_copy + (src - this->_stack_start), nspilled); | ||||
|     dest += nspilled; | ||||
|     src += nspilled; | ||||
|     n -= nspilled; | ||||
|     if (n > 0) { | ||||
|         // Copy the part after the saved stack
 | ||||
|         memcpy(dest, src, n); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| }; // namespace greenlet
 | ||||
| 
 | ||||
| #endif // GREENLET_STACK_STATE_CPP
 | ||||
| @ -0,0 +1,195 @@ | ||||
| /* -*- 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<MainGreenlet*>(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) | ||||
|     { | ||||
|         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<MainGreenlet*>(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 happenend 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<greenlet::ThreadState_DestroyNoGIL> ThreadStateCreator; | ||||
| static thread_local ThreadStateCreator g_thread_state_global; | ||||
| #define GET_THREAD_STATE() g_thread_state_global | ||||
| 
 | ||||
| #endif //T_THREADSTATE_DESTROY
 | ||||
| @ -0,0 +1,667 @@ | ||||
| /* -*- 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> 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<UserGreenlet*>(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<UserGreenlet*>(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
 | ||||
| @ -0,0 +1,71 @@ | ||||
| # -*- 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.0.3' | ||||
| 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. | ||||
											
												Binary file not shown.
											
										
									
								
											
												Binary file not shown.
											
										
									
								
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| @ -0,0 +1,164 @@ | ||||
| /* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ | ||||
| 
 | ||||
| /* Greenlet object interface */ | ||||
| 
 | ||||
| #ifndef Py_GREENLETOBJECT_H | ||||
| #define Py_GREENLETOBJECT_H | ||||
| 
 | ||||
| 
 | ||||
| #include <Python.h> | ||||
| 
 | ||||
| #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 */ | ||||
| @ -0,0 +1,63 @@ | ||||
| #ifndef GREENLET_ALLOCATOR_HPP | ||||
| #define GREENLET_ALLOCATOR_HPP | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include <Python.h> | ||||
| #include <memory> | ||||
| #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 <class T> | ||||
|     struct PythonAllocator : public std::allocator<T> { | ||||
| 
 | ||||
|         PythonAllocator(const PythonAllocator& UNUSED(other)) | ||||
|             : std::allocator<T>() | ||||
|         { | ||||
|         } | ||||
| 
 | ||||
|         PythonAllocator(const std::allocator<T> other) | ||||
|             : std::allocator<T>(other) | ||||
|         {} | ||||
| 
 | ||||
|         template <class U> | ||||
|         PythonAllocator(const std::allocator<U>& other) | ||||
|             : std::allocator<T>(other) | ||||
|         { | ||||
|         } | ||||
| 
 | ||||
|         PythonAllocator() : std::allocator<T>() {} | ||||
| 
 | ||||
|         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<T*>(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<U> other; | ||||
|         }; | ||||
| 
 | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,95 @@ | ||||
| /* -*- 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 <cstdint> | ||||
| 
 | ||||
| # 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 | ||||
| @ -0,0 +1,172 @@ | ||||
| #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 | ||||
| @ -0,0 +1,127 @@ | ||||
| /* -*- 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 | ||||
| /*
 | ||||
| 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. | ||||
| */ | ||||
| #    define GREENLET_USE_CFRAME 1 | ||||
| #else | ||||
| #    define GREENLET_USE_CFRAME 0 | ||||
| #    define GREENLET_PY310 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 | ||||
| 
 | ||||
| #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 | ||||
| 
 | ||||
| #endif /* GREENLET_CPYTHON_COMPAT_H */ | ||||
| @ -0,0 +1,150 @@ | ||||
| #ifndef GREENLET_EXCEPTIONS_HPP | ||||
| #define GREENLET_EXCEPTIONS_HPP | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include <Python.h> | ||||
| #include <stdexcept> | ||||
| #include <string> | ||||
| 
 | ||||
| #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 | ||||
| @ -0,0 +1,805 @@ | ||||
| #ifndef GREENLET_GREENLET_HPP | ||||
| #define GREENLET_GREENLET_HPP | ||||
| /*
 | ||||
|  * Declarations of the core data structures. | ||||
| */ | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include <Python.h> | ||||
| 
 | ||||
| #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 | ||||
| #  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<typename T> | ||||
|     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<typename T> | ||||
|         inline static PyObject* context(T* tstate) | ||||
|         { | ||||
|             return tstate->context; | ||||
|         } | ||||
| 
 | ||||
|         template<typename T> | ||||
|         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<struct _frame> 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 | ||||
|         int trash_delete_nesting; | ||||
| #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<UserGreenlet> 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<BrokenGreenlet> 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<MainGreenlet> 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<typename T> | ||||
|     void operator<<(const PyThreadState *const lhs, T& rhs) | ||||
|     { | ||||
|         rhs.operator<<(lhs); | ||||
|     } | ||||
| 
 | ||||
| } // namespace greenlet ;
 | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,106 @@ | ||||
| /* -*- 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 <vector> | ||||
| #include <string> | ||||
| 
 | ||||
| #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<MainGreenlet*>(g)) { | ||||
|         std::string err("MainGreenlet: Expected exactly a main greenlet, not a "); | ||||
|         err += Py_TYPE(p)->tp_name; | ||||
|         throw greenlet::TypeError(err); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| template <typename T, greenlet::refs::TypeChecker TC> | ||||
| inline greenlet::Greenlet* greenlet::refs::_OwnedGreenlet<T, TC>::operator->() const noexcept | ||||
| { | ||||
|     return reinterpret_cast<PyGreenlet*>(this->p)->pimpl; | ||||
| } | ||||
| 
 | ||||
| template <typename T, greenlet::refs::TypeChecker TC> | ||||
| inline greenlet::Greenlet* greenlet::refs::_BorrowedGreenlet<T, TC>::operator->() const noexcept | ||||
| { | ||||
|     return reinterpret_cast<PyGreenlet*>(this->p)->pimpl; | ||||
| } | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <stdexcept> | ||||
| 
 | ||||
| 
 | ||||
| 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:
 | ||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| @ -0,0 +1,99 @@ | ||||
| #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 | ||||
| @ -0,0 +1,543 @@ | ||||
| #ifndef GREENLET_THREAD_STATE_HPP | ||||
| #define GREENLET_THREAD_STATE_HPP | ||||
| 
 | ||||
| #include <ctime> | ||||
| #include <stdexcept> | ||||
| 
 | ||||
| #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<PyGreenlet*, PythonAllocator<PyGreenlet*> > 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<refs::OwnedGreenlet> 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<ThreadState> 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<ThreadState*>(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<typename T, refs::TypeChecker TC> | ||||
|     inline bool is_current(const refs::PyObjectPointer<T, TC>& 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> ThreadState::allocator; | ||||
| std::clock_t ThreadState::_clocks_used_doing_gc(0); | ||||
| 
 | ||||
| template<typename Destructor> | ||||
| 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<ThreadState*> cleanup_queue_t; | ||||
| 
 | ||||
| }; // namespace greenlet
 | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,118 @@ | ||||
| #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 | ||||
| @ -0,0 +1,31 @@ | ||||
| #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 <stdexcept> | ||||
| #include <thread> | ||||
| #include <mutex> | ||||
| 
 | ||||
| #include "greenlet_compiler_compat.hpp" | ||||
| 
 | ||||
| namespace greenlet { | ||||
|     typedef std::mutex Mutex; | ||||
|     typedef std::lock_guard<Mutex> LockGuard; | ||||
|     class LockInitError : public std::runtime_error | ||||
|     { | ||||
|     public: | ||||
|         LockInitError(const char* what) : std::runtime_error(what) | ||||
|         {}; | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #endif /* GREENLET_THREAD_SUPPORT_HPP */ | ||||
											
												Binary file not shown.
											
										
									
								| @ -0,0 +1,2 @@ | ||||
| call "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 | ||||
| @ -0,0 +1,124 @@ | ||||
| /*
 | ||||
|  * 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 | ||||
| @ -0,0 +1,30 @@ | ||||
| #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 | ||||
| @ -0,0 +1,87 @@ | ||||
| /*
 | ||||
|  * this is the internal transfer function. | ||||
|  * | ||||
|  * HISTORY | ||||
|  * 3-May-13   Ralf Schmitt  <ralf@systemexit.de> | ||||
|  *     Add support for strange GCC caller-save decisions | ||||
|  *     (ported from switch_aarch64_gcc.h) | ||||
|  * 18-Aug-11  Alexey Borzenkov  <snaury@gmail.com> | ||||
|  *      Correctly save rbp, csr and cw | ||||
|  * 01-Apr-04  Hye-Shik Chang    <perky@FreeBSD.org> | ||||
|  *      Ported from i386 to amd64. | ||||
|  * 24-Nov-02  Christian Tismer  <tismer@tismer.com> | ||||
|  *      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  <tismer@tismer.com> | ||||
|  *      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       <gerd.woetzel@GMD.DE> | ||||
|  *      slightly changed framework for spark | ||||
|  * 31-Avr-02  Armin Rigo         <arigo@ulb.ac.be> | ||||
|  *      Added ebx, esi and edi register-saves. | ||||
|  * 01-Mar-02  Samual M. Rushing  <rushing@ironport.com> | ||||
|  *      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. | ||||
|  */ | ||||
| @ -0,0 +1,79 @@ | ||||
| /*
 | ||||
|  * 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 | ||||
| @ -0,0 +1,67 @@ | ||||
| /*
 | ||||
|  * this is the internal transfer function. | ||||
|  * | ||||
|  * HISTORY | ||||
|  * 31-May-15 iOS support. Ported from arm32. Proton <feisuzhu@163.com> | ||||
|  * | ||||
|  * 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 | ||||
| @ -0,0 +1,53 @@ | ||||
|   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 | ||||
											
												Binary file not shown.
											
										
									
								Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in New Issue