include/secp256k1.h says a constructed context can be used from multiple threads simultaneously, but nothing in the repo tests this. The unit test runner parallelizes with fork(), so no test ever runs threads.
This adds thread_tests, which starts 4 threads on one randomized context. Each thread clones the context and then calls the API of every enabled module: ECDSA, ECDH, recovery, extrakeys, schnorrsig, musig, ellswift and silentpayments. Verification calls use secp256k1_context_static, since callers share that between threads too.
The test is meant to run under a race detector such as Helgrind or ThreadSanitizer, which reports any write to shared state from a call that takes a const context.
Build:
- New options
--enable-thread-testsandSECP256K1_BUILD_THREAD_TESTS. They default to on when tests are enabled andpthreadsare available. Configure fails if they are requested explicitly and pthreads are missing. - Only
thread_testslinks againstpthreads. The library does not. - On
MinGWthe test links statically, so it doesn't need thewinpthreadsDLL at runtime. Without that it fails under wine.MSVChas nopthreads, so the test is off there. (I haven't tested it on Windows).
CI:
ci.shjobs pass--enable-thread-tests=yes.- Where
Valgrindis enabled,ci.shalso runsthread_testsunderHelgrind, next to the existingValgrindrun ofctime_tests. That covers x86_64, i686 and arm64 Linux without a new job. - The
ASanjob now setsWITH_VALGRIND: 'no', sinceValgrindcan't runASanbinaries.
Follow-ups:
These are left out to keep this PR small:
- Run the test with a custom SHA256 compression function set through
secp256k1_context_set_sha256_compression. Apart from the callbacks, that's the only runtime state a context holds, and the test currently only uses the default. - Add a
TSanjob if the library ever uses atomics, for example for lazy initialization.HelgrindandDRDreport false positives on atomics that aren't annotated, andTSanhandles them correctly. - Test the read-write lock pattern that
secp256k1.hrecommends for re-randomizing a shared context: one thread callssecp256k1_context_randomizeunder the write lock while the others sign under read locks. - Call the remaining 16 const-context functions. They are all parse, serialize or convert helpers, such as
secp256k1_ecdsa_signature_parse_compactand the musig nonce and partial signature (de)serializers. - Exercise error paths by passing invalid inputs, such as an out-of-range secret key or a bad pubkey encoding, so that failing calls also run concurrently.
| I wrote this with help from Claude Code, and I've gone through all of it myself.