diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44f2c893..09844a2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: PYTHON: - {VERSION: "3.8", TOXENV: "py38"} - {VERSION: "3.13", TOXENV: "py313"} + - {VERSION: "3.13t", TOXENV: "py313"} MACOS: - macos-13 - macos-latest @@ -24,7 +25,7 @@ jobs: - uses: actions/checkout@v4.2.2 - name: Setup python id: setup-python - uses: actions/setup-python@v5.3.0 + uses: quansight-labs/setup-python@v5.3.1 with: python-version: ${{ matrix.PYTHON.VERSION }} - uses: actions/cache@v4.2.0 @@ -53,12 +54,13 @@ jobs: PYTHON: - {VERSION: "3.8", TOXENV: "py38"} - {VERSION: "3.13", TOXENV: "py313"} + - {VERSION: "3.13t", TOXENV: "py313"} name: "Python ${{ matrix.PYTHON.VERSION }} on ${{ matrix.WINDOWS.WINDOWS }}" steps: - uses: actions/checkout@v4.2.2 - name: Setup python id: setup-python - uses: actions/setup-python@v5.3.0 + uses: quansight-labs/setup-python@v5.3.1 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} @@ -92,6 +94,7 @@ jobs: - {VERSION: "3.11", TOXENV: "py311"} - {VERSION: "3.12", TOXENV: "py312"} - {VERSION: "3.13", TOXENV: "py313"} + - {VERSION: "3.13t", TOXENV: "py313"} - {VERSION: "pypy-3.9", TOXENV: "pypy3"} - {VERSION: "pypy-3.10", TOXENV: "pypy3"} @@ -99,12 +102,12 @@ jobs: - {VERSION: "3.13", TOXENV: "py313", RUST_VERSION: "1.64.0"} - {VERSION: "3.13", TOXENV: "py313", RUST_VERSION: "beta"} - {VERSION: "3.13", TOXENV: "py313", RUST_VERSION: "nightly"} - name: "${{ matrix.PYTHON.TOXENV }} on linux, Rust ${{ matrix.PYTHON.RUST_VERSION || 'stable' }}" + name: "${{ matrix.PYTHON.VERSION }} on linux, Rust ${{ matrix.PYTHON.RUST_VERSION || 'stable' }}" steps: - uses: actions/checkout@v4.2.2 - name: Setup python id: setup-python - uses: actions/setup-python@v5.3.0 + uses: quansight-labs/setup-python@v5.3.1 with: python-version: ${{ matrix.PYTHON.VERSION }} - uses: actions/cache@v4.2.0 diff --git a/README.rst b/README.rst index d2e76984..86e02c10 100644 --- a/README.rst +++ b/README.rst @@ -285,7 +285,7 @@ Compatibility ------------- This library should be compatible with py-bcrypt and it will run on Python -3.6+, and PyPy 3. +3.8+ (including free-threaded builds), and PyPy 3. Security -------- diff --git a/src/_bcrypt/src/lib.rs b/src/_bcrypt/src/lib.rs index 3159bf95..127f2e78 100644 --- a/src/_bcrypt/src/lib.rs +++ b/src/_bcrypt/src/lib.rs @@ -182,7 +182,7 @@ fn kdf<'p>( }) } -#[pyo3::pymodule] +#[pyo3::pymodule(gil_used = false)] mod _bcrypt { use pyo3::types::PyModuleMethods; diff --git a/tests/test_bcrypt.py b/tests/test_bcrypt.py index b0e0182a..2b3e78e0 100644 --- a/tests/test_bcrypt.py +++ b/tests/test_bcrypt.py @@ -1,3 +1,6 @@ +import uuid +from concurrent.futures import ThreadPoolExecutor + import pytest import bcrypt @@ -171,7 +174,7 @@ ] -def test_gensalt_basic(monkeypatch): +def test_gensalt_basic(): salt = bcrypt.gensalt() assert salt.startswith(b"$2b$12$") @@ -219,7 +222,7 @@ def test_gensalt_bad_prefix(): bcrypt.gensalt(prefix=b"bad") -def test_gensalt_2a_prefix(monkeypatch): +def test_gensalt_2a_prefix(): salt = bcrypt.gensalt(prefix=b"2a") assert salt.startswith(b"$2a$12$") @@ -494,3 +497,24 @@ def test_2a_wraparound_bug(): ) == b"$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi" ) + + +def test_multithreading(): + def create_user(pw): + salt = bcrypt.gensalt(4) + hash_ = bcrypt.hashpw(pw, salt) + key = bcrypt.kdf(pw, salt, 32, 50) + assert bcrypt.checkpw(pw, hash_) + return (salt, hash_, key) + + user_creator = ThreadPoolExecutor(max_workers=4) + pws = [uuid.uuid4().bytes for _ in range(50)] + + futures = [user_creator.submit(create_user, pw) for pw in pws] + + users = [future.result() for future in futures] + + for pw, (salt, hash_, key) in zip(pws, users): + assert bcrypt.hashpw(pw, salt) == hash_ + assert bcrypt.checkpw(pw, hash_) + assert bcrypt.kdf(pw, salt, 32, 50) == key