True free-threaded Python support: full memory + CPU profiling #769
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: tests | |
| on: | |
| push: | |
| branches: [ master ] | |
| pull_request: | |
| branches: [ master ] | |
| workflow_dispatch: | |
| jobs: | |
| run-tests: | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ ubuntu-latest, macos-latest ] | |
| python: [ '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.13t', '3.14t' ] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: select Xcode version | |
| # MacOS > 14.2 requires Xcode >= 15.3; otherwise loading native extension modules fails with e.g.: | |
| # dlopen(/opt/homebrew/lib/python3.11/site-packages/slipcover/probe.abi3.so, 0x0002): bad bind opcode 0x00 | |
| if: startsWith(matrix.os, 'macos-') | |
| run: | | |
| if [ -d /Applications/Xcode_15.3.app/Contents/Developer ]; then sudo xcode-select --switch /Applications/Xcode_15.3.app/Contents/Developer; fi | |
| clang++ --version | |
| g++ --version | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python }} | |
| - name: Work around arm64 support on MacOS | |
| # https://github.com/actions/virtual-environments/issues/2557 | |
| if: matrix.os == 'macos-latest' | |
| run: sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/* | |
| - name: Install system build dependencies | |
| # Free-threaded Python may lack pre-built wheels for packages like lxml | |
| if: runner.os == 'Linux' | |
| run: sudo apt-get install -y libxml2-dev libxslt-dev | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -r requirements.txt | |
| python -m pip install numpy | |
| - name: Build scalene | |
| run: pip -v install -e . | |
| - name: install test dependencies | |
| run: | | |
| python3 -m pip install pytest pytest-asyncio hypothesis | |
| # torch/JAX/TensorFlow may not be available on free-threaded Python builds | |
| python3 -m pip install torch --index-url https://download.pytorch.org/whl/cpu || true | |
| python3 -m pip install -e ".[test]" || python3 -m pip install -e . | |
| - name: Enable core dumps for free-threaded builds | |
| if: runner.os == 'Linux' && endsWith(matrix.python, 't') | |
| run: | | |
| ulimit -c unlimited | |
| echo '/tmp/core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern | |
| sudo apt-get install -y gdb | |
| - name: Quick memory profiling smoke test | |
| if: runner.os == 'Linux' && endsWith(matrix.python, 't') | |
| run: | | |
| echo 'x = [i*i for i in range(500000)]' > /tmp/test_ft.py | |
| echo "=== Check PyMem_SetAllocator symbol ===" | |
| nm -D $(python3 -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))")/libpython*.so 2>/dev/null | grep PyMem_SetAllocator || echo "Symbol not found in libpython" | |
| echo "=== Check libscalene symbols ===" | |
| nm -D $(python3 -c "import scalene; print(scalene.__path__[0])")/libscalene.so 2>/dev/null | grep "PyMem_\|get_real" || echo "No PyMem symbols in libscalene" | |
| echo "=== Running scalene with memory profiling ===" | |
| LD_PRELOAD=libscalene.so LD_LIBRARY_PATH=$(python3 -c "import scalene; print(scalene.__path__[0])"):$LD_LIBRARY_PATH SCALENE_ALLOCATION_SAMPLING_WINDOW=10485767 gdb -batch -ex 'set follow-fork-mode child' -ex 'set pagination off' -ex run -ex 'thread apply all bt 15' --args python3 -m scalene run --memory -o /tmp/ft_profile.json /tmp/test_ft.py 2>&1 | tail -50 || true | |
| - name: run tests | |
| # Free-threaded Python support is experimental; don't block CI on failures | |
| continue-on-error: ${{ endsWith(matrix.python, 't') }} | |
| run: | | |
| python3 -m pytest | |
| - name: Free-threaded parity test | |
| # Runs on ALL matrix entries to verify CPU+memory profiling with | |
| # native code and threads produces comparable results everywhere. | |
| if: runner.os != 'Windows' | |
| continue-on-error: ${{ endsWith(matrix.python, 't') }} | |
| run: | | |
| python3 test/test_freethreaded_parity.py | |
| - name: Collect crash backtraces | |
| if: failure() && runner.os == 'Linux' && endsWith(matrix.python, 't') | |
| run: | | |
| for core in /tmp/core.*; do | |
| [ -f "$core" ] || continue | |
| echo "=== Backtrace from $core ===" | |
| gdb -batch -ex "thread apply all bt full" python3 "$core" 2>/dev/null || true | |
| done |