pynacl icon indicating copy to clipboard operation
pynacl copied to clipboard

Build error on macOS ARM no GIL

Open jaraco opened this issue 9 months ago • 8 comments

Attempting to build on Python 3.14t on macOS on ARM, I get the following failure:

      building '_sodium' extension
      creating build/temp.macosx-15.1-arm64-cpython-314t/build/temp.macosx-15.1-arm64-cpython-314t
      gcc -fno-strict-overflow -Wsign-compare -Wunreachable-code -DNDEBUG -g -O3 -Wall -I/opt/python/include/python3.14t -Ibuild/temp.macosx-15.1-arm64-cpython-314t/include -c build/temp.macosx-15.1-arm64-cpython-314t/_sodium.c -o build/temp.macosx-15.1-arm64-cpython-314t/build/temp.macosx-15.1-arm64-cpython-314t/_sodium.o
      In file included from build/temp.macosx-15.1-arm64-cpython-314t/_sodium.c:57:
      /opt/python/include/python3.14t/Python.h:51:4: error: "The limited API is not currently supported in the free-threaded build"
         51 | #  error "The limited API is not currently supported in the free-threaded build"
            |    ^
      In file included from build/temp.macosx-15.1-arm64-cpython-314t/_sodium.c:57:
      In file included from /opt/python/include/python3.14t/Python.h:72:
      /opt/python/include/python3.14t/object.h:146:5: error: unknown type name 'PyMutex'
        146 |     PyMutex ob_mutex;           // per-object lock
            |     ^
      /opt/python/include/python3.14t/object.h:299:5: error: call to undeclared function '_Py_atomic_store_ssize_relaxed'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        299 |     _Py_atomic_store_ssize_relaxed(&ob->ob_size, size);
            |     ^
      In file included from build/temp.macosx-15.1-arm64-cpython-314t/_sodium.c:57:
      In file included from /opt/python/include/python3.14t/Python.h:73:
      /opt/python/include/python3.14t/refcount.h:97:26: error: call to undeclared function '_Py_atomic_load_uint32_relaxed'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
         97 |         uint32_t local = _Py_atomic_load_uint32_relaxed(&ob->ob_ref_local);
            |                          ^
      /opt/python/include/python3.14t/refcount.h:101:29: error: call to undeclared function '_Py_atomic_load_ssize_relaxed'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        101 |         Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&ob->ob_ref_shared);
            |                             ^
      /opt/python/include/python3.14t/refcount.h:115:13: error: call to undeclared function '_Py_atomic_load_uint32_relaxed'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        115 |     return (_Py_atomic_load_uint32_relaxed(&op->ob_ref_local) ==
            |             ^
      /opt/python/include/python3.14t/refcount.h:115:63: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
        115 |     return (_Py_atomic_load_uint32_relaxed(&op->ob_ref_local) ==
            |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
        116 |             _Py_IMMORTAL_REFCNT_LOCAL);
            |             ~~~~~~~~~~~~~~~~~~~~~~~~~
      /opt/python/include/python3.14t/refcount.h:146:9: error: call to undeclared function '_Py_IsOwnedByCurrentThread'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        146 |     if (_Py_IsOwnedByCurrentThread(ob)) {
            |         ^
      /opt/python/include/python3.14t/refcount.h:241:22: error: call to undeclared function '_Py_atomic_load_uint32_relaxed'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        241 |     uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
            |                      ^
      /opt/python/include/python3.14t/refcount.h:248:9: error: call to undeclared function '_Py_IsOwnedByCurrentThread'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        248 |     if (_Py_IsOwnedByCurrentThread(op)) {
            |         ^
      /opt/python/include/python3.14t/refcount.h:249:9: error: call to undeclared function '_Py_atomic_store_uint32_relaxed'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        249 |         _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, new_local);
            |         ^
      /opt/python/include/python3.14t/refcount.h:252:9: error: call to undeclared function '_Py_atomic_add_ssize'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        252 |         _Py_atomic_add_ssize(&op->ob_ref_shared, (1 << _Py_REF_SHARED_SHIFT));
            |         ^
      /opt/python/include/python3.14t/refcount.h:335:22: error: call to undeclared function '_Py_atomic_load_uint32_relaxed'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        335 |     uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
            |                      ^
      /opt/python/include/python3.14t/refcount.h:341:9: error: call to undeclared function '_Py_IsOwnedByCurrentThread'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        341 |     if (_Py_IsOwnedByCurrentThread(op)) {
            |         ^
      /opt/python/include/python3.14t/refcount.h:343:9: error: call to undeclared function '_Py_atomic_store_uint32_relaxed'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        343 |         _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
            |         ^
      /opt/python/include/python3.14t/refcount.h:345:13: error: call to undeclared function '_Py_MergeZeroLocalRefcount'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        345 |             _Py_MergeZeroLocalRefcount(op);
            |             ^
      /opt/python/include/python3.14t/refcount.h:349:9: error: call to undeclared function '_Py_DecRefShared'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        349 |         _Py_DecRefShared(op);
            |         ^
      1 warning and 16 errors generated.
      error: command '/usr/bin/gcc' failed with exit code 1
      [end of output]
  
  note: This error orig

This may or may not be related to #834.

jaraco avatar Mar 05 '25 08:03 jaraco

CFFI does not support freethreaded Python at this point, so this is not expected to work, see: https://github.com/python-cffi/cffi/issues/119

alex avatar Mar 05 '25 09:03 alex

If I force the build to use our fork of CFFI:

diff --git a/pyproject.toml b/pyproject.toml
index d6cc581..5c91e55 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@
 requires = [
     "setuptools>=61.0.0,!=74.0.0",
     "wheel",
-    "cffi>=1.4.1; platform_python_implementation != 'PyPy'",
+    "cffi @ git+https://github.com/quansight-labs/cffi; platform_python_implementation != 'PyPy'",
 ]
 build-backend = "setuptools.build_meta"

After updating from tox to nox: https://github.com/pyca/pynacl/pull/862

I can build and run the tests on the free-threaded build. I don't see any test failures.

It would probably be helpful for our effort to upstream the changes in our fork of cffi if we could come up with multithreaded tests. Mostly to stress-test cffi.

ngoldbaum avatar Apr 02 '25 22:04 ngoldbaum

I tried building pynacl with TSAN support but hit a build error in libsodium's make check:

      make[3]: Leaving directory '/work/pynacl/build/temp.linux-x86_64-cpython-314t/test/default'
      make  check-TESTS
      make[3]: Entering directory '/work/pynacl/build/temp.linux-x86_64-cpython-314t/test/default'
      make[4]: Entering directory '/work/pynacl/build/temp.linux-x86_64-cpython-314t/test/default'
      PASS: aead_aegis128l
      PASS: aead_aegis256
      PASS: aead_aes256gcm
      PASS: aead_aes256gcm2
      PASS: aead_chacha20poly1305
      PASS: aead_chacha20poly13052
      PASS: aead_xchacha20poly1305
      PASS: auth
      PASS: auth2
      PASS: auth3
      PASS: auth5
      PASS: auth6
      PASS: auth7
      PASS: box
      PASS: box2
      PASS: box7
      PASS: box8
      PASS: box_easy
      PASS: box_easy2
      PASS: box_seal
      PASS: box_seed
      PASS: chacha20
      PASS: codecs
      PASS: core1
      PASS: core2
      PASS: core3
      PASS: core4
      PASS: core5
      PASS: core6
      PASS: ed25519_convert
      PASS: generichash
      PASS: generichash2
      PASS: generichash3
      PASS: hash
      PASS: hash3
      PASS: kdf
      PASS: keygen
      PASS: kx
      PASS: metamorphic
      PASS: misuse
      PASS: onetimeauth
      PASS: onetimeauth2
      PASS: onetimeauth7
      PASS: pwhash_argon2i
      PASS: pwhash_argon2id
      PASS: randombytes
      PASS: scalarmult
      PASS: scalarmult2
      PASS: scalarmult5
      PASS: scalarmult6
      PASS: scalarmult7
      PASS: scalarmult8
      PASS: secretbox
      PASS: secretbox2
      PASS: secretbox7
      PASS: secretbox8
      PASS: secretbox_easy
      PASS: secretbox_easy2
      PASS: secretstream_xchacha20poly1305
      PASS: shorthash
      PASS: sign
      PASS: sodium_core
      PASS: sodium_utils
      PASS: sodium_version
      PASS: stream
      PASS: stream2
      PASS: stream3
      PASS: stream4
      PASS: verify1
      PASS: sodium_utils2
      FAIL: sodium_utils3
      PASS: core_ed25519
      PASS: core_ristretto255
      PASS: kdf_hkdf
      PASS: pwhash_scrypt
      PASS: pwhash_scrypt_ll
      PASS: scalarmult_ed25519
      PASS: scalarmult_ristretto255
      PASS: siphashx24
      PASS: xchacha20
      ============================================================================
      Testsuite summary for libsodium 1.0.20
      ============================================================================
      # TOTAL: 80
      # PASS:  79
      # SKIP:  0
      # XFAIL: 0
      # FAIL:  1
      # XPASS: 0
      # ERROR: 0
      ============================================================================
      See test/default/test-suite.log for debugging.
      Some test(s) failed.  Please report this to https://github.com/jedisct1/libsodium/issues,
      together with the test-suite.log file (gzipped) and your system
      information.  Thanks.
      ============================================================================

I don't see an upstream issue mentioning TSAN so maybe no one has hit this before.

ngoldbaum avatar Apr 29 '25 17:04 ngoldbaum

That test file has a warning in it if you try to compile with asan and appears to be doing a bunch of signal handler testing.

reaperhulk avatar Apr 29 '25 17:04 reaperhulk

I opened https://github.com/jedisct1/libsodium/issues/1450. I might hack the libsodium build to ignore that error to see if I can run a test I wrote under TSAN that encrypts a message in chunks in a thread pool as well as the full test suite under pytest-run-parallel. The latter is imperfect because it will skip tests using hypothesis because hypothesis isn't thread-safe.

ngoldbaum avatar Apr 29 '25 17:04 ngoldbaum

I ended up finding some races in our fork of CFFI (https://github.com/Quansight-Labs/cffi/issues/2#issuecomment-2840037692). Nothing worrying and nothing that's problematic on the GIL-enabled build. Thanks for sanity checking what I was seeing earlier @reaperhulk.

ngoldbaum avatar Apr 29 '25 20:04 ngoldbaum

An update: we now have a version of CFFI that I feel much more confident is thread-safe on the free-threaded build thanks to lots of hard work from @colesbury, @kumaraditya303, and myself.

I'm currently in the process of packaging our fork, which includes renaming the project from CFFI to CFFI-ft and renaming the modules it exports to avoid stepping on upstream's modules or packaging metadata if both happen to be installed simultaneously.

@reaperhulk I'm curious what it would take practically from our side to convince you to add a dependency on CFFI-ft for Python 3.13 and newer.

Unfortunately there's currently no way to declare a dependency only on the free-threaded build in a pyproject.toml file, so at least in the PyPI ecosystem you'd need to depend on CFFI-ft for GIL-enabled Python 3.13 and newer. Hopefully this is all temporary and everyone can switch back to depending on upstream CFFI once we propose changes from our fork. I'm not sure how long that will take - upstream asked us to do real-world testing.

I'm planning to package CFFI-ft for PyPI and conda-forge. Are there other packaging ecosystems you'd like me to do some legwork on?

ngoldbaum avatar Jun 12 '25 17:06 ngoldbaum

CFFI 2.0.0 is out, so this is fixed now. Hopefully we'll have a PyNaCl release out shortly to get wheel builds too, but if you have a compiler then the build succeeds now:

± pip install pynacl
Collecting pynacl
  Downloading PyNaCl-1.5.0.tar.gz (3.4 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.4/3.4 MB 11.5 MB/s  0:00:00
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Requirement already satisfied: cffi>=1.4.1 in /Users/goldbaum/.pyenv/versions/3.14.0rc2t/lib/python3.14t/site-packages (from pynacl) (2.0.0b1)
Requirement already satisfied: pycparser in /Users/goldbaum/.pyenv/versions/3.14.0rc2t/lib/python3.14t/site-packages (from cffi>=1.4.1->pynacl) (2.22)
Building wheels for collected packages: pynacl
  Building wheel for pynacl (pyproject.toml) ... done
  Created wheel for pynacl: filename=pynacl-1.5.0-cp314-cp314t-macosx_15_0_arm64.whl size=101236 sha256=3db13bcb7af74711b5fe9d15cc8491ca9ae0771ba7476919fe4c036f0fdd6f43
  Stored in directory: /Users/goldbaum/Library/Caches/pip/wheels/46/db/8c/218b01e4fbcc47efc6ee41db2c8a110837d9341393e5969c01
Successfully built pynacl
Installing collected packages: pynacl
Successfully installed pynacl-1.5.0

ngoldbaum avatar Sep 09 '25 16:09 ngoldbaum