templates, sh to start
parent
65226c2be2
commit
769fa3c625
@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/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 cp lazy_wiki.sqlite3 lazywiki:/db/lazy_wiki.sqlite3
|
||||||
ssh vps docker restart lazywiki
|
ssh vps docker restart lazywiki
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from . import web
|
from . import web
|
||||||
|
import sys
|
||||||
|
|
||||||
def main():
|
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__':
|
if __name__ == '__main__':
|
||||||
print("444444")
|
|
||||||
main()
|
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