pax_global_header00006660000000000000000000000064147302514410014513gustar00rootroot0000000000000052 comment=841e3de49074d4b854342744ffcdca926e21bd6d fastrlock-0.8.3/000077500000000000000000000000001473025144100135135ustar00rootroot00000000000000fastrlock-0.8.3/.github/000077500000000000000000000000001473025144100150535ustar00rootroot00000000000000fastrlock-0.8.3/.github/workflows/000077500000000000000000000000001473025144100171105ustar00rootroot00000000000000fastrlock-0.8.3/.github/workflows/ci.yml000066400000000000000000000100671473025144100202320ustar00rootroot00000000000000name: Build & Publish wheel on: push: create: jobs: sdist: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install Python dependencies run: python -m pip install -r requirements.txt - name: Build sdist run: make sdist - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: dist/*.tar.gz - name: Upload sdist uses: actions/upload-artifact@v4 with: name: sdist path: dist/*.tar.gz Linux: strategy: # Allows for matrix sub-jobs to fail without canceling the rest fail-fast: false matrix: image: - manylinux1_x86_64 - manylinux1_i686 - manylinux_2_24_i686 - manylinux_2_24_x86_64 - manylinux_2_28_x86_64 - musllinux_1_1_x86_64 #- manylinux_2_24_ppc64le #- manylinux_2_24_s390x pyversion: ["cp"] # all CPython versions include: - image: manylinux_2_24_aarch64 pyversion: "cp37" - image: manylinux_2_24_aarch64 pyversion: "cp38" - image: manylinux_2_24_aarch64 pyversion: "cp39" - image: manylinux_2_24_aarch64 pyversion: "cp310" - image: manylinux_2_24_aarch64 pyversion: "cp311" - image: manylinux_2_28_aarch64 pyversion: "cp312" - image: manylinux_2_28_aarch64 pyversion: "cp313" - image: musllinux_1_1_aarch64 pyversion: "cp37" - image: musllinux_1_1_aarch64 pyversion: "cp38" - image: musllinux_1_1_aarch64 pyversion: "cp39" - image: musllinux_1_1_aarch64 pyversion: "cp310" - image: musllinux_1_1_aarch64 pyversion: "cp311" - image: musllinux_1_1_aarch64 pyversion: "cp312" - image: musllinux_1_1_aarch64 pyversion: "cp313" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install Dependencies run: | python -m pip install -r requirements.txt - name: Building wheel run: | make MANYLINUX_PYTHON="${{ matrix.pyversion }}*" sdist wheel_${{ matrix.image }} - name: Copy wheels in dist run: cp -v wheelhouse*/fastrlock*.whl dist/ - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: dist/*-m*linux*.whl # manylinux / musllinux - name: Archive Wheels uses: actions/upload-artifact@v4 with: name: ${{ matrix.image }}-${{ matrix.pyversion }} path: dist/ # manylinux / musllinux if-no-files-found: ignore other: strategy: # Allows for matrix sub-jobs to fail without canceling the rest fail-fast: false matrix: os: [macos-latest, windows-latest] python-version: - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" - "3.14-dev" runs-on: ${{ matrix.os }} env: MACOSX_DEPLOYMENT_TARGET: "11.0" steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Dependencies run: | python -m pip install -r requirements.txt - name: Building wheel run: | python setup.py bdist_wheel - name: Release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: dist/*.whl - name: Archive Wheels uses: actions/upload-artifact@v4 with: name: ${{ matrix.os }}-${{ matrix.python-version }}-wheels path: dist/*.whl if-no-files-found: ignore fastrlock-0.8.3/.gitignore000066400000000000000000000003231473025144100155010ustar00rootroot00000000000000*.pyc *.pyo __pycache__ fastrlock/*.c MANIFEST build/ dist/ venv*/ releases/ cython_debug/ *wheel*/ .idea/ .git/ .coverage .tox .hg .cache .hgignore *.egg-info *.orig *.rej *.dep *.swp *.pdb *.so *.o *.pyd *~ fastrlock-0.8.3/.travis.yml000066400000000000000000000002341473025144100156230ustar00rootroot00000000000000sudo: false language: python python: - "2.7" - "3.9" - "3.8" - "3.7" - "3.6" - "3.5" install: pip install tox-travis script: tox fastrlock-0.8.3/CHANGES.rst000066400000000000000000000025001473025144100153120ustar00rootroot00000000000000=================== fastrlock changelog =================== 0.8.3 (2024-12-17) ================== * Rebuilt with Cython 3.0.11 to add Python 3.13 support. 0.8.2 (2023-08-27) ================== * Rebuilt with Cython 3.0.2 to add Python 3.12 support. 0.8.1 (2022-11-02) ================== * Rebuilt with Cython 3.0.0a11 to add Python 3.11 support. 0.8 (2021-10-22) ================ * Rebuilt with Cython 3.0.0a9 to improve the performance in recent Python 3.x versions. 0.7 (2021-10-21) ================ * Adapted for unsigned thread IDs, as used by Py3.7+. (original patch by Guilherme Dantas) * Build with Cython 0.29.24 to support Py3.10 and later. 0.6 (2021-03-21) ================ * Rebuild with Cython 0.29.22 to support Py3.9 and later. 0.5 (2020-06-05) ================ * Rebuild with Cython 0.29.20 to support Py3.8 and later. 0.4 (2018-08-24) ================ * Rebuild with Cython 0.28.5. * Linux wheels are faster through profile guided optimisation. * Add missing file to sdist. (patch by Mark Harfouche, Github issue #5) 0.3 (2017-08-10) ================ * improve cimport support of C-API (patch by Naotoshi Seo, Github issue #3) * provide ``fastrlock.__version__`` 0.2 (2017-08-09) ================ * add missing readme file to sdist 0.1 (2017-06-04) ================ * initial release fastrlock-0.8.3/LICENSE000066400000000000000000000020471473025144100145230ustar00rootroot00000000000000MIT License Copyright (c) 2017 scoder 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. fastrlock-0.8.3/MANIFEST.in000066400000000000000000000003001473025144100152420ustar00rootroot00000000000000recursive-include fastrlock *.py *.pyx *.pxi *.pxd *.c include setup.py lockbench.py LICENSE MANIFEST.in *.rst Makefile tox.ini include appveyor.yml appveyor_env.cmd requirements-appveyor.txt fastrlock-0.8.3/Makefile000066400000000000000000000055321473025144100151600ustar00rootroot00000000000000PYTHON2?=python PYTHON?=python3 SETUPFLAGS= PACKAGENAME=fastrlock VERSION=$(shell python -c 'import re; f=open("fastrlock/__init__.py"); print(re.search("__version__\s*=\s*[\x27\x22](.+)[\x27\x22]", f.read()).group(1)); f.close()') PYTHON_WITH_CYTHON=$(shell $(PYTHON) -c 'import Cython.Compiler' >/dev/null 2>/dev/null && echo " --with-cython" || true) PY2_WITH_CYTHON=$(shell $(PYTHON2) -c 'import Cython.Compiler' >/dev/null 2>/dev/null && echo " --with-cython" || true) # manylinux1 images still include Python 2.7 MANYLINUX_PYTHON?=cp* MANYLINUX_IMAGES= \ manylinux1_x86_64 \ manylinux1_i686 \ manylinux_2_24_x86_64 \ manylinux_2_24_i686 \ manylinux_2_24_aarch64 \ manylinux_2_28_x86_64 \ manylinux_2_28_aarch64 \ musllinux_1_1_x86_64 \ musllinux_1_1_aarch64 .PHONY: all version inplace sdist build clean wheel_manylinux wheel all: inplace version: @echo $(VERSION) inplace: $(PYTHON) setup.py $(SETUPFLAGS) build_ext -i $(PYTHON_WITH_CYTHON) sdist dist/$(PACKAGENAME)-$(VERSION).tar.gz: $(PYTHON) setup.py $(SETUPFLAGS) sdist $(PYTHON_WITH_CYTHON) build: $(PYTHON) setup.py $(SETUPFLAGS) build $(PYTHON_WITH_CYTHON) wheel: $(PYTHON) setup.py $(SETUPFLAGS) bdist_wheel $(PYTHON_WITH_CYTHON) qemu-user-static: docker run --rm --privileged hypriot/qemu-register wheel_manylinux: sdist $(addprefix wheel_,$(MANYLINUX_IMAGES)) $(addprefix wheel_,$(filter-out %_x86_64, $(filter-out %_i686, $(MANYLINUX_IMAGES)))): qemu-user-static wheel_%: dist/$(PACKAGENAME)-$(VERSION).tar.gz echo "Building wheels for $(PACKAGENAME) $(VERSION)" mkdir -p wheelhouse_$(subst wheel_,,$@) time docker run --rm -t \ -v $(shell pwd):/io \ -e CFLAGS="-O3 -g1 -mtune=generic -pipe -fPIC" \ -e LDFLAGS="$(LDFLAGS) -fPIC" \ -e WHEELHOUSE=wheelhouse_$(subst wheel_,,$@) \ quay.io/pypa/$(subst wheel_,,$@) \ bash -c '\ rm -fr $(PACKAGENAME)-$(VERSION)/; \ tar zxf /io/$< && cd $(PACKAGENAME)-$(VERSION)/ || exit 1; \ for PYBIN in /opt/python/$(MANYLINUX_PYTHON)/bin; do \ PYVER="$$($$PYBIN/python -V)"; \ PROFDIR="prof-$${PYVER// /_}"; \ echo $$PYVER; \ $$PYBIN/pip install -U pip setuptools; \ make clean; rm -fr $$PROFDIR; \ CFLAGS="$$CFLAGS -fprofile-generate -fprofile-dir=$$PROFDIR" $$PYBIN/python setup.py build_ext -i; \ $$PYBIN/python lockbench.py flock quick; \ CFLAGS="$$CFLAGS -fprofile-use -fprofile-correction -fprofile-dir=$$PROFDIR" $$PYBIN/python setup.py build_ext -i -f; \ $$PYBIN/python lockbench.py rlock flock quick; \ CFLAGS="$$CFLAGS -fprofile-use -fprofile-correction -fprofile-dir=$$PROFDIR" $$PYBIN/python setup.py bdist_wheel; \ done; \ for whl in dist/$(PACKAGENAME)-$(VERSION)-*-linux_*.whl; do auditwheel repair $$whl -w /io/$$WHEELHOUSE; done; \ ' clean: find . \( -name '*.o' -o -name '*.so' -o -name '*.py[cod]' -o -name '*.dll' \) -exec rm -f {} \; rm -rf build fastrlock-0.8.3/README.rst000066400000000000000000000130451473025144100152050ustar00rootroot00000000000000FastRLock --------- This is a C-level implementation of a fast, re-entrant, optimistic lock for CPython. It is a drop-in replacement for `threading.RLock `_. FastRLock is implemented in `Cython `_ and also provides a C-API for direct use from Cython code via ``from fastrlock cimport rlock`` or ``from cython.cimports.fastrlock import rlock``. Under normal conditions, it is about 10x faster than ``threading.RLock`` in Python 2.7 because it avoids all locking unless two or more threads try to acquire it at the same time. Under congestion, it is still about 10% faster than RLock due to being implemented in Cython. This is mostly equivalent to the revised RLock implementation in Python 3.2, but still faster due to being implemented in Cython. However, in Python 3.4 and later, the ``threading.RLock`` implementation in the stdlib tends to be as fast or even faster than the lock provided by this package, when called through the Python API. ``FastRLock`` is still faster also on these systems when called through its Cython API from other Cython modules. It was initially published as a code recipe here: https://code.activestate.com/recipes/577336-fast-re-entrant-optimistic-lock-implemented-in-cyt/ FastRLock has been used and tested in `Lupa `_ for several years. How does it work? ----------------- The FastRLock implementation optimises for the non-congested case. It works by exploiting the availability of the GIL. Since it knows that it holds the GIL when the acquire()/release() methods are called, it can safely check the lock for being held by other threads and just count any re-entries as long as it is always the same thread that acquires it. This is a lot faster than actually acquiring the underlying lock. When a second thread wants to acquire the lock as well, it first checks the lock count and finds out that the lock is already owned. If the underlying lock is also held by another thread already, it then just frees the GIL and asks for acquiring the lock, just like RLock does. If the underlying lock is not held, however, it acquires it immediately and basically hands over the ownership by telling the current owner to free it when it's done. Then, it falls back to the normal non-owner behaviour that asks for the lock and will eventually acquire it when it gets released. This makes sure that the real lock is only acquired when at least two threads want it. All of these operations are basically atomic because any thread that modifies the lock state always holds the GIL. Note that the implementation must not call any Python code while handling the lock, as calling into Python may lead to a context switch which hands over the GIL to another thread and thus breaks atomicity. Therefore, the code misuses Cython's 'nogil' annotation to make sure that no Python code slips in accidentally. How fast is it? --------------- Here are some timings for the following scenarios: 1) five acquire-release cycles ('lock_unlock') 2) five acquire calls followed by five release calls (nested locking, 'reentrant_lock_unlock') 3) a mixed and partly nested sequence of acquire and release calls ('mixed_lock_unlock') 4) five acquire-release cycles that do not block ('lock_unlock_nonblocking') All four are benchmarked for the single threaded case and the multi threaded case with 10 threads. I also tested it with 20 threads only to see that it then takes about twice the time for both versions. Note also that the congested case is substantially slower for both locks and the benchmark includes the thread creation time, so I only looped 1000x here to get useful timings instead of 100000x for the single threaded case. The results here are mixed. Depending on the optimisation of the CPython installation, it can be faster, about the same speed, or somewhat slower. In any case, the direct Cython interface is always faster than going through the Python API, because it avoids the Python call overhead and executes a C call instead. :: Testing RLock (3.10.1) sequential (x100000): lock_unlock : 138.36 msec reentrant_lock_unlock : 95.35 msec mixed_lock_unlock : 102.05 msec lock_unlock_nonblocking : 131.44 msec context_manager : 616.83 msec threaded 10T (x1000): lock_unlock : 1386.60 msec reentrant_lock_unlock : 1207.75 msec mixed_lock_unlock : 1319.62 msec lock_unlock_nonblocking : 1325.07 msec context_manager : 1357.93 msec Testing FastRLock (0.8.1) sequential (x100000): lock_unlock : 77.47 msec reentrant_lock_unlock : 64.14 msec mixed_lock_unlock : 73.51 msec lock_unlock_nonblocking : 70.31 msec context_manager : 393.34 msec threaded 10T (x1000): lock_unlock : 1214.13 msec reentrant_lock_unlock : 1171.75 msec mixed_lock_unlock : 1184.33 msec lock_unlock_nonblocking : 1207.42 msec context_manager : 1232.20 msec Testing Cython interface of FastRLock (0.8.1) sequential (x100000): lock_unlock : 18.70 msec reentrant_lock_unlock : 15.88 msec mixed_lock_unlock : 14.96 msec lock_unlock_nonblocking : 13.47 msec threaded 10T (x1000): lock_unlock : 1236.21 msec reentrant_lock_unlock : 1245.77 msec mixed_lock_unlock : 1194.25 msec lock_unlock_nonblocking : 1206.96 msec fastrlock-0.8.3/appveyor.yml000066400000000000000000000051711473025144100161070ustar00rootroot00000000000000# parts from # - matrix: https://github.com/pythonnet/pythonnet/blob/master/appveyor.yml # - Visual Studio 2010 py33 py34 on x64: https://github.com/ogrisel/python-appveyor-demo/blob/master/appveyor/run_with_env.cmd branches: only: - /default/ platform: - x86 - x64 environment: global: # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the # /E:ON and /V:ON options are not enabled in the batch script intepreter # See: http://stackoverflow.com/a/13751649/163740 CMD_IN_ENV: 'cmd /E:ON /V:ON /C .\appveyor_env.cmd' # CIBW_BEFORE_BUILD: python build_ext --with-cython --inplace # TWINE_USERNAME: scoder # Note: TWINE_PASSWORD is set in Appveyor settings matrix: - PYTHON_VERSION: "3.11" - PYTHON_VERSION: "3.10" - PYTHON_VERSION: 3.9 - PYTHON_VERSION: 3.8 - PYTHON_VERSION: 3.7 - PYTHON_VERSION: 3.6 - PYTHON_VERSION: 3.5 - PYTHON_VERSION: 2.7 init: - set PY_VER=%PYTHON_VERSION:.=% - set TOXENV=py%PY_VER% - set PYTHON=C:\PYTHON%PY_VER% - if %PLATFORM%==x64 (set PYTHON=%PYTHON%-x64) - set TOXPYTHON=%PYTHON%\python.exe - set PYTHON_ARCH=32 - if %PLATFORM%==x64 (set PYTHON_ARCH=64) # Put desired Python version in PATH - set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH% - 'ECHO %TOXENV% ' - python --version install: - ECHO 'Installed SDKs:' - ps: 'ls "C:/Program Files/Microsoft SDKs/Windows"' #- python -m pip install --upgrade setuptools pip - "powershell .\\appveyor_install.ps1" - "%PYTHON%\\python.exe --version" - "%PYTHON%\\python.exe -m pip install -r requirements-appveyor.txt" - "%PYTHON%\\python.exe setup.py build_ext --inplace --with-cython" build: false # First tests then build (is python not C) test_script: - tox -e %TOXENV%-windows after_test: - "%PYTHON%\\Scripts\\pip.exe install --upgrade wheel" - "%PYTHON%\\python.exe setup.py build_ext --inplace" - rm -rf build # account for: https://bitbucket.org/pypa/wheel/issues/147/bdist_wheel-should-start-by-cleaning-up - "%PYTHON%\\python.exe setup.py sdist bdist_wheel" artifacts: # bdist_wheel puts your built wheel in the dist directory - path: "dist\\*.whl" name: Wheels # Where in the appveyor cloud are the wheels pushed to? # https://www.appveyor.com/docs/packaging-artifacts/#permalink-to-the-last-successful-build-artifact # borrowed from https://github.com/joerick/pyinstrument_cext/blob/master/appveyor.yml#L11-L15 # - ps: >- # if ($env:APPVEYOR_REPO_TAG -eq "true") { # python -m pip install twine # python -m twine upload (resolve-path wheelhouse\*.whl) # } on_failure: - ps: dir "env:" - ps: get-content .tox\*\log\* fastrlock-0.8.3/appveyor_env.cmd000066400000000000000000000064461473025144100167270ustar00rootroot00000000000000:: To build extensions for 64 bit Python 3, we need to configure environment :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) :: :: To build extensions for 64 bit Python 2, we need to configure environment :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) :: :: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific :: environment configurations. :: :: Note: this script needs to be run with the /E:ON and /V:ON flags for the :: cmd interpreter, at least for (SDK v7.0) :: :: More details at: :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows :: http://stackoverflow.com/a/13751649/163740 :: :: Author: Olivier Grisel :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ :: :: Notes about batch files for Python people: :: :: Quotes in values are literally part of the values: :: SET FOO="bar" :: FOO is now five characters long: " b a r " :: If you don't want quotes, don't include them on the right-hand side. :: :: The CALL lines at the end of this file look redundant, but if you move them :: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y :: case, I don't know why. @ECHO OFF SET COMMAND_TO_RUN=%* SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf :: Extract the major and minor versions, and allow for the minor version to be :: more than 9. This requires the version number to have two dots in it. SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1% IF "%PYTHON_VERSION:~3,1%" == "." ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1% ) ELSE ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2% ) :: Based on the Python version, determine what SDK version to use, and whether :: to set the SDK for 64-bit. IF %MAJOR_PYTHON_VERSION% == 2 ( SET WINDOWS_SDK_VERSION="v7.0" SET SET_SDK_64=Y ) ELSE ( IF %MAJOR_PYTHON_VERSION% == 3 ( SET WINDOWS_SDK_VERSION="v7.1" IF %MINOR_PYTHON_VERSION% LEQ 4 ( SET SET_SDK_64=Y ) ELSE ( SET SET_SDK_64=N IF EXIST "%WIN_WDK%" ( :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ REN "%WIN_WDK%" 0wdf ) ) ) ELSE ( ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" EXIT 1 ) ) IF %PYTHON_ARCH% == 64 ( IF %SET_SDK_64% == Y ( ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture SET DISTUTILS_USE_SDK=1 SET MSSdk=1 "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ELSE ( ECHO Using default MSVC build environment for 64 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ) ELSE ( ECHO Using default MSVC build environment for 32 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) fastrlock-0.8.3/appveyor_install.ps1000066400000000000000000000076661473025144100175520ustar00rootroot00000000000000# Sample script to install Python and pip under Windows # Authors: Olivier Grisel and Kyle Kastner # License: CC0 1.0 Universal: https://creativecommons.org/publicdomain/zero/1.0/ $PYTHON_BASE_URL = "https://www.python.org/ftp/python/" $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" $GET_PIP_PATH = "C:\get-pip.py" $DOWNLOADS = "C:\Downloads\Cython" function Download ($url, $filename, $destdir) { if ($destdir) { $item = New-Item $destdir -ItemType directory -Force $destdir = $item.FullName } else { $destdir = $pwd.Path } $filepath = Join-Path $destdir $filename if (Test-Path $filepath) { Write-Host "Reusing" $filename "from" $destdir return $filepath } Write-Host "Downloading" $filename "from" $url $webclient = New-Object System.Net.WebClient foreach($i in 1..3) { try { $webclient.DownloadFile($url, $filepath) Write-Host "File saved at" $filepath return $filepath } Catch [Exception] { Start-Sleep 1 } } Write-Host "Failed to download" $filename "from" $url return $null } function InstallPython ($python_version, $architecture, $python_home) { Write-Host "Installing Python $python_version ($architecture-bit) to $python_home" if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return } $py_major = $python_version[0]; $py_minor = $python_version[2] $installer_exe = ($py_major + $py_minor) -as [int] -ge 35 if ($installer_exe) { $arch_suffix = @{"32"="";"64"="-amd64"}[$architecture] $dl_filename = "python-" + $python_version + $arch_suffix + ".exe" $filename = "python-" + $py_major + "." + $py_minor + $arch_suffix + ".exe" } else { $arch_suffix = @{"32"="";"64"=".amd64"}[$architecture] $dl_filename = "python-" + $python_version + $arch_suffix + ".msi" $filename = "python-" + $py_major + "." + $py_minor + $arch_suffix + ".msi" } $url = $PYTHON_BASE_URL + $python_version + "/" + $dl_filename $filepath = Download $url $filename $DOWNLOADS Write-Host "Installing" $filename "to" $python_home if ($installer_exe) { $prog = "$filepath" $args = "/quiet TargetDir=$python_home" } else { $prog = "msiexec.exe" $args = "/quiet /qn /i $filepath TARGETDIR=$python_home" } Write-Host $prog $args Start-Process -FilePath $prog -ArgumentList $args -Wait Write-Host "Python $python_version ($architecture-bit) installation complete" } function InstallPip ($python_home) { $python_path = Join-Path $python_home "python.exe" $pip_path = Join-Path $python_home "Scripts\pip.exe" if (Test-Path $pip_path) { Write-Host "Upgrading pip" $args = "-m pip.__main__ install --upgrade pip" Write-Host "Executing:" $python_path $args Start-Process -FilePath $python_path -ArgumentList $args -Wait Write-Host "pip upgrade complete" } else { Write-Host "Installing pip" $webclient = New-Object System.Net.WebClient $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) Write-Host "Executing:" $python_path $GET_PIP_PATH Start-Process -FilePath $python_path -ArgumentList "$GET_PIP_PATH" -Wait Write-Host "pip installation complete" } } function InstallPipPackage ($python_home, $package) { $pip_path = Join-Path $python_home "Scripts\pip.exe" Write-Host "Installing/Upgrading $package" $args = "install --upgrade $package" Write-Host "Executing:" $pip_path $args Start-Process -FilePath $pip_path -ArgumentList $args -Wait Write-Host "$package install/upgrade complete" } function main () { $full_version = $env:PYTHON_VERSION + ".0" InstallPython $full_version $env:PYTHON_ARCH $env:PYTHON InstallPip $env:PYTHON InstallPipPackage $env:PYTHON setuptools InstallPipPackage $env:PYTHON wheel } main fastrlock-0.8.3/download_artefacts.py000077500000000000000000000122371473025144100177400ustar00rootroot00000000000000#!/usr/bin/python3 import json import logging import shutil import datetime from concurrent.futures import ProcessPoolExecutor as Pool, as_completed from pathlib import Path from urllib.request import urlopen, Request from urllib.parse import urljoin logger = logging.getLogger() PARALLEL_DOWNLOADS = 6 GITHUB_API_URL = "https://api.github.com/repos/scoder/fastrlock" APPVEYOR_PACKAGE_URL = "https://ci.appveyor.com/api/projects/scoder/fastrlock" APPVEYOR_BUILDJOBS_URL = "https://ci.appveyor.com/api/buildjobs" def find_github_files(version, api_url=GITHUB_API_URL): url = f"{api_url}/releases/tags/{version}" release, _ = read_url(url, accept="application/vnd.github+json", as_json=True) for asset in release.get('assets', ()): yield asset['browser_download_url'] def find_appveyor_files(version, base_package_url=APPVEYOR_PACKAGE_URL, base_job_url=APPVEYOR_BUILDJOBS_URL): url = f"{base_package_url}/history?recordsNumber=20" with urlopen(url) as p: builds = json.load(p)["builds"] tag = f"{version}" for build in builds: if build['isTag'] and build['tag'] == tag: build_id = build['buildId'] break else: logger.warning(f"No appveyor build found for tag '{tag}'") return build_url = f"{base_package_url}/builds/{build_id}" with urlopen(build_url) as p: jobs = json.load(p)["build"]["jobs"] for job in jobs: artifacts_url = f"{base_job_url}/{job['jobId']}/artifacts/" with urlopen(artifacts_url) as p: for artifact in json.load(p): yield urljoin(artifacts_url, artifact['fileName']) def read_url(url, decode=True, accept=None, as_json=False): if accept: request = Request(url, headers={'Accept': accept}) else: request = Request(url) with urlopen(request) as res: charset = _find_content_encoding(res) content_type = res.headers.get('Content-Type') data = res.read() if decode: data = data.decode(charset) if as_json: data = json.loads(data) return data, content_type def _find_content_encoding(response, default='iso8859-1'): from email.message import Message content_type = response.headers.get('Content-Type') if content_type: msg = Message() msg.add_header('Content-Type', content_type) charset = msg.get_content_charset(default) else: charset = default return charset def download1(wheel_url, dest_dir): wheel_name = wheel_url.rsplit("/", 1)[1] logger.info(f"Downloading {wheel_url} ...") with urlopen(wheel_url) as w: file_path = dest_dir / wheel_name if (file_path.exists() and "Content-Length" in w.headers and file_path.stat().st_size == int(w.headers["Content-Length"])): logger.info(f"Already have {wheel_name}") else: temp_file_path = file_path.with_suffix(".tmp") try: with open(temp_file_path, "wb") as f: shutil.copyfileobj(w, f) except: if temp_file_path.exists(): temp_file_path.unlink() raise else: temp_file_path.replace(file_path) logger.info(f"Finished downloading {wheel_name}") return wheel_name def download(urls, dest_dir, jobs=PARALLEL_DOWNLOADS): with Pool(max_workers=jobs) as pool: futures = [pool.submit(download1, url, dest_dir) for url in urls] try: for future in as_completed(futures): wheel_name = future.result() yield wheel_name except KeyboardInterrupt: for future in futures: future.cancel() raise def dedup(it): seen = set() for value in it: if value not in seen: seen.add(value) yield value def roundrobin(*iterables): "roundrobin('ABC', 'D', 'EF') --> A D E B F C" # Recipe credited to George Sakkis from itertools import cycle, islice num_active = len(iterables) nexts = cycle(iter(it).__next__ for it in iterables) while num_active: try: for next in nexts: yield next() except StopIteration: # Remove the iterator we just exhausted from the cycle. num_active -= 1 nexts = cycle(islice(nexts, num_active)) def main(*args): if not args: print("Please pass the version to download") return version = args[0] dest_dir = Path("dist") / version if not dest_dir.is_dir(): dest_dir.mkdir() start_time = datetime.datetime.now().replace(microsecond=0) urls = roundrobin(*map(dedup, [ find_github_files(version), find_appveyor_files(version), ])) count = sum(1 for _ in enumerate(download(urls, dest_dir))) duration = datetime.datetime.now().replace(microsecond=0) - start_time logger.info(f"Downloaded {count} files in {duration}.") if __name__ == "__main__": import sys logging.basicConfig( stream=sys.stderr, level=logging.INFO, format="%(asctime)-15s %(message)s", ) main(*sys.argv[1:]) fastrlock-0.8.3/fastrlock/000077500000000000000000000000001473025144100155035ustar00rootroot00000000000000fastrlock-0.8.3/fastrlock/__init__.pxd000066400000000000000000000000001473025144100177450ustar00rootroot00000000000000fastrlock-0.8.3/fastrlock/__init__.py000066400000000000000000000002511473025144100176120ustar00rootroot00000000000000# this is a package __version__ = "0.8.3" class LockNotAcquired(Exception): """ Exception raised when the lock was not acquired in non-blocking mode. """ fastrlock-0.8.3/fastrlock/_lock.pxi000066400000000000000000000050601473025144100173150ustar00rootroot00000000000000 from cpython cimport pythread from fastrlock import LockNotAcquired cdef extern from *: # Compatibility definitions for Python """ #if PY_VERSION_HEX >= 0x030700a2 typedef unsigned long pythread_t; #else typedef long pythread_t; #endif """ # Just let Cython understand that pythread_t is # a long type, but be aware that it is actually # signed for versions of Python prior to 3.7.0a2 and # unsigned for later versions ctypedef unsigned long pythread_t cdef struct _LockStatus: pythread.PyThread_type_lock lock pythread_t owner # thread ID of the current lock owner unsigned int entry_count # number of (re-)entries of the owner unsigned int pending_requests # number of pending requests for real lock bint is_locked # whether the real lock is acquired cdef bint _acquire_lock(_LockStatus *lock, long current_thread, bint blocking) nogil except -1: # Note that this function *must* hold the GIL when being called. # We just use 'nogil' in the signature to make sure that no Python # code execution slips in that might free the GIL wait = pythread.WAIT_LOCK if blocking else pythread.NOWAIT_LOCK if not lock.is_locked and not lock.pending_requests: # someone owns it but didn't acquire the real lock - do that # now and tell the owner to release it when done if pythread.PyThread_acquire_lock(lock.lock, pythread.NOWAIT_LOCK): lock.is_locked = True #assert lock._is_locked lock.pending_requests += 1 # wait for the lock owning thread to release it with nogil: while True: locked = pythread.PyThread_acquire_lock(lock.lock, wait) if locked: break if wait == pythread.NOWAIT_LOCK: lock.pending_requests -= 1 return False lock.pending_requests -= 1 #assert not lock.is_locked #assert lock.reentry_count == 0 #assert locked lock.is_locked = True lock.owner = current_thread lock.entry_count = 1 return True cdef inline void _unlock_lock(_LockStatus *lock) nogil noexcept: # Note that this function *must* hold the GIL when being called. # We just use 'nogil' in the signature to make sure that no Python # code execution slips in that might free the GIL #assert lock.entry_count > 0 lock.entry_count -= 1 if lock.entry_count == 0: if lock.is_locked: pythread.PyThread_release_lock(lock.lock) lock.is_locked = False fastrlock-0.8.3/fastrlock/fastrlock_helpers.h000066400000000000000000000002271473025144100213670ustar00rootroot00000000000000#if PY_VERSION_HEX < 0x03020000 && !defined(PY_LOCK_FAILURE) # define PY_LOCK_FAILURE 0 # define PY_LOCK_ACQUIRED 1 # define PY_LOCK_INTR -1 #endif fastrlock-0.8.3/fastrlock/rlock.pxd000066400000000000000000000004711473025144100173340ustar00rootroot00000000000000# cython: language_level=3 cdef create_fastrlock() # acquire the lock of a FastRlock instance # 'current_thread' may be -1 for the current thread cdef bint lock_fastrlock(rlock, long current_thread, bint blocking) except -1 # release the lock of a FastRlock instance cdef int unlock_fastrlock(rlock) except -1 fastrlock-0.8.3/fastrlock/rlock.pyx000066400000000000000000000070171473025144100173640ustar00rootroot00000000000000# cython: language_level=3 # cython: binding=True from cpython cimport pythread include "_lock.pxi" cdef class FastRLock: """Fast, re-entrant locking. Under non-congested conditions, the lock is never acquired but only counted. Only when a second thread comes in and notices that the lock is needed, it acquires the lock and notifies the first thread to release it when it's done. This is all made possible by the wonderful GIL. """ cdef _LockStatus _real_lock def __cinit__(self): self._real_lock = _LockStatus( lock=pythread.PyThread_allocate_lock(), owner=0, is_locked=False, pending_requests=0, entry_count=0) if not self._real_lock.lock: raise MemoryError() def __dealloc__(self): if self._real_lock.lock: pythread.PyThread_free_lock(self._real_lock.lock) self._real_lock.lock = NULL # compatibility with RLock and expected Python level interface def acquire(self, bint blocking=True): return _lock_rlock( &self._real_lock, pythread.PyThread_get_thread_ident(), blocking) def release(self): if self._real_lock.entry_count == 0: raise RuntimeError("cannot release un-acquired lock") _unlock_lock(&self._real_lock) def __enter__(self): # self.acquire() if not _lock_rlock( &self._real_lock, pythread.PyThread_get_thread_ident(), blocking=True): raise LockNotAcquired() def __exit__(self, t, v, tb): # self.release() if self._real_lock.entry_count == 0 or self._real_lock.owner != pythread.PyThread_get_thread_ident(): raise RuntimeError("cannot release un-acquired lock") _unlock_lock(&self._real_lock) def _is_owned(self): return self._real_lock.entry_count > 0 and self._real_lock.owner == pythread.PyThread_get_thread_ident() cdef inline bint _lock_rlock(_LockStatus *lock, pythread_t current_thread, bint blocking) nogil except -1: # Note that this function *must* hold the GIL when being called. # We just use 'nogil' in the signature to make sure that no Python # code execution slips in that might free the GIL if lock.entry_count: # locked! - by myself? if lock.owner == current_thread: lock.entry_count += 1 return True elif not lock.pending_requests: # not locked, not requested - go! lock.owner = current_thread lock.entry_count = 1 return True # need to get the real lock return _acquire_lock(lock, current_thread, blocking) ########################################################################### ## public C-API cdef create_fastrlock(): """ Public C level entry function for creating a FastRlock instance. """ return FastRLock.__new__(FastRLock) cdef bint lock_fastrlock(rlock, long current_thread, bint blocking) except -1: """ Public C level entry function for locking a FastRlock instance. The 'current_thread' argument is deprecated and ignored. Pass -1 for backwards compatibility. """ # Note: 'current_thread' used to be set to -1 or the current thread ID, but -1 is signed while "pythread_t" isn't. return _lock_rlock(&(rlock)._real_lock, pythread.PyThread_get_thread_ident(), blocking) cdef int unlock_fastrlock(rlock) except -1: """ Public C level entry function for unlocking a FastRlock instance. """ _unlock_lock(&(rlock)._real_lock) return 0 fastrlock-0.8.3/fastrlock/tests/000077500000000000000000000000001473025144100166455ustar00rootroot00000000000000fastrlock-0.8.3/fastrlock/tests/__init__.py000066400000000000000000000010761473025144100207620ustar00rootroot00000000000000 def suite(): import unittest import doctest import os test_dir = os.path.abspath(os.path.dirname(__file__)) tests = [] for filename in os.listdir(test_dir): if filename.endswith('.py') and not filename.startswith('_'): tests.append(unittest.defaultTestLoader.loadTestsFromName(__name__ + '.' + filename[:-3])) suite = unittest.TestSuite(tests) if len(tests) != 1 else tests[0] suite.addTest(doctest.DocFileSuite('../../README.rst')) return suite if __name__ == '__main__': import unittest unittest.main() fastrlock-0.8.3/fastrlock/tests/test_rlock.py000066400000000000000000000154161473025144100213770ustar00rootroot00000000000000# -*- coding: utf-8 -*- try: from thread import start_new_thread, get_ident except ImportError: # Python 3? from _thread import start_new_thread, get_ident import threading import unittest import time import sys import gc from fastrlock import rlock IS_PYTHON3 = sys.version_info[0] >= 3 try: _next = next except NameError: def _next(o): return o.next() unicode_type = type(IS_PYTHON3 and 'abc' or 'abc'.decode('ASCII')) def _wait(): # A crude wait/yield function not relying on synchronization primitives. time.sleep(0.01) class Bunch(object): """ A bunch of threads. """ def __init__(self, f, n, wait_before_exit=False): """ Construct a bunch of `n` threads running the same function `f`. If `wait_before_exit` is True, the threads won't terminate until do_finish() is called. """ self.f = f self.n = n self.started = [] self.finished = [] self._can_exit = not wait_before_exit def task(): tid = get_ident() self.started.append(tid) try: f() finally: self.finished.append(tid) while not self._can_exit: _wait() for i in range(n): start_new_thread(task, ()) def wait_for_started(self): while len(self.started) < self.n: _wait() def wait_for_finished(self): while len(self.finished) < self.n: _wait() def do_finish(self): self._can_exit = True ################################################################################ # tests for the FastRLock implementation class TestFastRLock(unittest.TestCase): """Copied from CPython's test.lock_tests module """ locktype = rlock.FastRLock Bunch = Bunch def tearDown(self): gc.collect() # the locking tests """ Tests for both recursive and non-recursive locks. """ def test_constructor(self): lock = self.locktype() del lock def test_acquire_destroy(self): lock = self.locktype() lock.acquire() del lock def test_acquire_release(self): lock = self.locktype() lock.acquire() lock.release() del lock def test_try_acquire(self): lock = self.locktype() self.assertTrue(lock.acquire(False)) lock.release() def test_try_acquire_contended(self): lock = self.locktype() lock.acquire() result = [] def f(): result.append(lock.acquire(False)) self.Bunch(f, 1).wait_for_finished() self.assertFalse(result[0]) lock.release() def test_acquire_contended(self): lock = self.locktype() lock.acquire() N = 5 def f(): lock.acquire() lock.release() b = self.Bunch(f, N) b.wait_for_started() _wait() self.assertEqual(len(b.finished), 0) lock.release() b.wait_for_finished() self.assertEqual(len(b.finished), N) def test_with(self): lock = self.locktype() def f(): lock.acquire() lock.release() def _with(err=None): with lock: if err is not None: raise err _with() # Check the lock is unacquired self.Bunch(f, 1).wait_for_finished() self.assertRaises(TypeError, _with, TypeError) # Check the lock is unacquired self.Bunch(f, 1).wait_for_finished() def test_thread_leak(self): # The lock shouldn't leak a Thread instance when used from a foreign # (non-threading) thread. lock = self.locktype() def f(): lock.acquire() lock.release() n = len(threading.enumerate()) # We run many threads in the hope that existing threads ids won't # be recycled. self.Bunch(f, 15).wait_for_finished() self.assertEqual(n, len(threading.enumerate())) """ Tests for non-recursive, weak locks (which can be acquired and released from different threads). """ def _test_reacquire_nonrecursive(self): # Lock needs to be released before re-acquiring. lock = self.locktype() phase = [] def f(): lock.acquire() phase.append(None) lock.acquire() phase.append(None) start_new_thread(f, ()) while len(phase) == 0: _wait() _wait() self.assertEqual(len(phase), 1) lock.release() while len(phase) == 1: _wait() self.assertEqual(len(phase), 2) def test_different_thread_release_succeeds(self): # Lock can be released from a different thread. lock = self.locktype() lock.acquire() def f(): lock.release() b = self.Bunch(f, 1) b.wait_for_finished() lock.acquire() lock.release() """ Tests for recursive locks. """ def test_reacquire(self): lock = self.locktype() lock.acquire() lock.acquire() lock.release() lock.acquire() lock.release() lock.release() def test_release_unacquired(self): # Cannot release an unacquired lock lock = self.locktype() self.assertRaises(RuntimeError, lock.release) lock.acquire() lock.acquire() lock.release() lock.acquire() lock.release() lock.release() self.assertRaises(RuntimeError, lock.release) def test_different_thread_release_fails(self): # Cannot release from a different thread lock = self.locktype() def f(): lock.acquire() b = self.Bunch(f, 1, True) try: self.assertRaises(RuntimeError, lock.release) finally: b.do_finish() def test__is_owned(self): lock = self.locktype() self.assertFalse(lock._is_owned()) lock.acquire() self.assertTrue(lock._is_owned()) lock.acquire() self.assertTrue(lock._is_owned()) result = [] def f(): result.append(lock._is_owned()) self.Bunch(f, 1).wait_for_finished() self.assertFalse(result[0]) lock.release() self.assertTrue(lock._is_owned()) lock.release() self.assertFalse(lock._is_owned()) if __name__ == '__main__': import doctest suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(rlock)) suite.addTest(unittest.defaultTestLoader.loadTestsFromName('__main__')) suite.addTest(doctest.DocFileSuite('../../README.rst')) runner = unittest.TextTestRunner(verbosity=2) if not runner.run(suite).wasSuccessful(): sys.exit(1) fastrlock-0.8.3/lockbench.py000066400000000000000000000153521473025144100160230ustar00rootroot00000000000000import sys from threading import Thread from threading import RLock from fastrlock.rlock import FastRLock as FLock # Benchmark functions: cython_code = [] def lock_unlock(l): l.acquire() l.release() l.acquire() l.release() l.acquire() l.release() l.acquire() l.release() l.acquire() l.release() cython_code.append(""" def lock_unlock(lock): lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) """) def reentrant_lock_unlock(l): l.acquire() l.acquire() l.acquire() l.acquire() l.acquire() l.release() l.release() l.release() l.release() l.release() cython_code.append(""" def reentrant_lock_unlock(lock): lock_fastrlock(lock, -1, True) lock_fastrlock(lock, -1, True) lock_fastrlock(lock, -1, True) lock_fastrlock(lock, -1, True) lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) unlock_fastrlock(lock) unlock_fastrlock(lock) unlock_fastrlock(lock) unlock_fastrlock(lock) """) def mixed_lock_unlock(l): l.acquire() l.release() l.acquire() l.acquire() l.release() l.acquire() l.release() l.acquire() l.release() l.release() cython_code.append(""" def mixed_lock_unlock(lock): lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) lock_fastrlock(lock, -1, True) lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) lock_fastrlock(lock, -1, True) unlock_fastrlock(lock) unlock_fastrlock(lock) """) def context_manager(l): with l: pass with l: with l: with l: pass with l: pass with l: with l: pass with l: with l: pass with l: pass with l: pass with l: with l: with l: pass with l: pass with l: with l: pass with l: pass def lock_unlock_nonblocking(l): if l.acquire(False): l.release() if l.acquire(False): l.release() if l.acquire(False): l.release() if l.acquire(False): l.release() if l.acquire(False): l.release() cython_code.append(""" def lock_unlock_nonblocking(lock): if lock_fastrlock(lock, -1, False): unlock_fastrlock(lock) if lock_fastrlock(lock, -1, False): unlock_fastrlock(lock) if lock_fastrlock(lock, -1, False): unlock_fastrlock(lock) if lock_fastrlock(lock, -1, False): unlock_fastrlock(lock) if lock_fastrlock(lock, -1, False): unlock_fastrlock(lock) """) # End of benchmark functions def threaded(l, test_func, tcount=10): threads = [ Thread(target=test_func, args=(l,)) for _ in range(tcount) ] for thread in threads: thread.start() for thread in threads: thread.join() def run_benchmark(name, lock, version, functions, repeat_count, repeat_count_t): print('Testing %s (%s)' % (name, version)) from timeit import Timer from functools import partial print("sequential (x%d):" % repeat_count) for function in functions: timer = Timer(partial(function, lock)) print('%-25s: %9.2f msec' % (function.__name__, max(timer.repeat(repeat=4, number=repeat_count)) * 1000.0)) print("threaded 10T (x%d):" % repeat_count_t) for function in functions: timer = Timer(partial(threaded, lock, function)) print('%-25s: %9.2f msec' % (function.__name__, max(timer.repeat(repeat=4, number=repeat_count_t)) * 1000.0)) if sys.version_info < (3, 5): import imp def load_dynamic(name, module_path): return imp.load_dynamic(name, module_path) else: import importlib.util as _importlib_util def load_dynamic(name, module_path): spec = _importlib_util.spec_from_file_location(name, module_path) module = _importlib_util.module_from_spec(spec) spec.loader.exec_module(module) return module def main(): functions = [ lock_unlock, reentrant_lock_unlock, mixed_lock_unlock, lock_unlock_nonblocking, context_manager, ] import fastrlock import glob import os.path import re import tempfile repeat_count = 100000 repeat_count_t = 1000 rlock = (RLock(), "%d.%d.%d" % sys.version_info[:3]) flock = (FLock(), fastrlock.__version__) locks = [] args = sys.argv[1:] if 'rlock' in args: locks.append(rlock) if 'flock' in args: locks.append(flock) if not args: locks = [rlock, flock] for _ in range(args.count('quick')): repeat_count = max(10, repeat_count // 100) repeat_count_t = max(5, repeat_count_t // 10) for lock, version in locks: name = type(lock).__name__ run_benchmark(name, lock, version, functions, repeat_count, repeat_count_t) if 'cython' in args or not args: lock, version = flock from Cython.Build.Cythonize import cython_compile, parse_args basepath = None try: with tempfile.NamedTemporaryFile(mode="w", suffix='.pyx') as f: code = '\n'.join(cython_code) cy_function_names = re.findall(r"def (\w+)\(", code) f.write("from fastrlock.rlock cimport lock_fastrlock, unlock_fastrlock\n\n") f.write(code) f.flush() options, _ = parse_args(["", f.name, "-3", "--force", "--inplace"]) cython_compile(f.name, options) basepath = os.path.splitext(f.name)[0] for ext in [".*.so", ".*.pyd", ".so", ".pyd"]: so_paths = glob.glob(basepath + ext) if so_paths: so_path = so_paths[0] break else: print("Failed to find Cython compiled module") sys.exit(1) module = load_dynamic(os.path.basename(basepath), so_path) cy_functions = [getattr(module, name) for name in cy_function_names] run_benchmark("Cython interface of %s" % type(lock).__name__, lock, version, cy_functions, repeat_count, repeat_count_t) finally: if basepath: files = glob.glob(basepath + ".*") # .c, .so / .pyd if len(files) > 3: print("Found too many artefacts, not deleting temporary files") else: for filename in files: os.unlink(filename) if __name__ == '__main__': main() fastrlock-0.8.3/requirements-appveyor.txt000066400000000000000000000000741473025144100206430ustar00rootroot00000000000000tox pytest coverage Cython==3.0.11 codecov setuptools wheel fastrlock-0.8.3/requirements.txt000066400000000000000000000000461473025144100167770ustar00rootroot00000000000000Cython>=3.0.11, <3.1 setuptools wheel fastrlock-0.8.3/setup.py000066400000000000000000000056231473025144100152330ustar00rootroot00000000000000 import sys import os import re from setuptools import setup, Extension with open('fastrlock/__init__.py') as f: VERSION = re.search( r'^__version__ \s* = \s* ["\'] ([0-9ab.]+) ["\']', f.read(), re.MULTILINE|re.VERBOSE).group(1) BASEDIR = os.path.dirname(__file__) PKGNAME = 'fastrlock' PKGDIR = os.path.join(BASEDIR, PKGNAME) MODULES = [filename[:-4] for filename in os.listdir(PKGDIR) if filename.endswith('.pyx')] def has_option(name): if name in sys.argv[1:]: sys.argv.remove(name) return True return False ext_args = { 'define_macros': [('CYTHON_CLINE_IN_TRACEBACK', '1')], } if has_option('--without-assert'): ext_args['define_macros'].append(('CYTHON_WITHOUT_ASSERTIONS', None)) use_cython = has_option('--with-cython') if not use_cython: if not all(os.path.isfile(os.path.join(PKGDIR, module_name+'.c')) for module_name in MODULES): print("NOTE: generated sources not available, need Cython to build") use_cython = True cythonize = None if use_cython: try: from Cython.Build import cythonize import Cython.Compiler.Version print("building with Cython " + Cython.Compiler.Version.version) source_extension = ".pyx" except ImportError: print("WARNING: trying to build with Cython, but it is not installed") cythonize = None source_extension = ".c" else: print("building without Cython") source_extension = ".c" ext_modules = [ Extension( '%s.%s' % (PKGNAME, module_name), sources=[os.path.join(PKGNAME, module_name+source_extension)], **ext_args) for module_name in MODULES] if cythonize is not None: ext_modules = cythonize(ext_modules) def read_file(filename): f = open(os.path.join(BASEDIR, filename)) try: return f.read() finally: f.close() long_description = '\n\n'.join([ read_file(text_file) for text_file in ['README.rst', 'CHANGES.rst']]) setup( name="fastrlock", version=VERSION, author="Stefan Behnel", author_email="stefan_ml@behnel.de", url="https://github.com/scoder/fastrlock", license='MIT style', description="Fast, re-entrant optimistic lock implemented in Cython", long_description=long_description, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'License :: OSI Approved :: MIT License', 'Programming Language :: Cython', 'Programming Language :: Python :: 3', 'Operating System :: OS Independent', 'Topic :: Software Development', ], packages=[PKGNAME], package_data={PKGNAME: ['*.pyx', '*.pxd', '*.pxi']}, ext_modules=ext_modules, zip_safe=False, # support 'test' target if setuptools/distribute is available test_suite='fastrlock.tests.suite', ) fastrlock-0.8.3/tox.ini000066400000000000000000000006271473025144100150330ustar00rootroot00000000000000[tox] envlist = py{37,38,39,310,311,312,313} [testenv] platform = windows: win32 linux: linux darwin: darwin deps = pip >= 18 setuptools >= 40 skip_install = true passenv = * commands_pre = python -m pip install pytest -r requirements.txt python setup.py build_ext -i python -m pip install . commands = python -m pytest fastrlock/tests --capture=no --strict {posargs}