From d32195c582ff966487c12ff2b5171aa3613321de Mon Sep 17 00:00:00 2001 From: mattip Date: Fri, 16 Oct 2026 12:07:53 +0300 Subject: [PATCH 1/6] use a per-thread queue on windows --- tools/build_steps_windows.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/build_steps_windows.sh b/tools/build_steps_windows.sh index bfa1b8e8..2003e85e 100644 --- a/tools/build_steps_windows.sh +++ b/tools/build_steps_windows.sh @@ -90,9 +90,10 @@ interface_flags="$interface_flags SYMBOLPREFIX=scipy_ LIBNAMEPREFIX=scipy_ FIXED GCC_TAG="gcc_$(gcc -dumpversion | tr .- _)" OPENBLAS_VERSION=$(git describe --tags --abbrev=8) -# Patch OpenBLAS build to resolve all symbols and avoid linking -# with libquadmath -patch -p1 < ../patches-windows/openblas-make-libs.patch +for p in ../patches-windows/*.patch; do + [ -e "$p" ] || continue + patch -p1 < "$p" +done # Patch VERSION version=$(grep "^version =" ../pyproject.toml | sed 's/version = "//;s/"//') From f058496d84f141743822a59b48ecb9fc0e2b3834 Mon Sep 17 00:00:00 2001 From: mattip Date: Fri, 16 Oct 2026 12:08:37 +0300 Subject: [PATCH 2/6] increment build number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 13381375..69ed4ff2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta" [project] name = "scipy-openblas64" # v0.3.34-237-g1ad90fb1 -version = "0.3.34.237.1" +version = "0.3.34.237.2" requires-python = ">=3.7" description = "Provides OpenBLAS for python packaging" readme = "README.md" From ca11ec4b67c77d6760916b5fe40508c2a3e94062 Mon Sep 17 00:00:00 2001 From: mattip Date: Fri, 16 Oct 2026 14:31:51 +0300 Subject: [PATCH 3/6] also patch on win-arm64 --- patches-windows/openblas-make-libs.patch | 5 +- .../openblas-win32-pool-queue-link.patch | 101 ++++++++++++++++++ tools/build_steps_win_arm64.bat | 8 +- 3 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 patches-windows/openblas-win32-pool-queue-link.patch diff --git a/patches-windows/openblas-make-libs.patch b/patches-windows/openblas-make-libs.patch index 1e4d4aeb..9fd3ba99 100644 --- a/patches-windows/openblas-make-libs.patch +++ b/patches-windows/openblas-make-libs.patch @@ -1,8 +1,7 @@ diff --git a/exports/Makefile b/exports/Makefile -index 668a4866e..956b51bf4 100644 --- a/exports/Makefile +++ b/exports/Makefile -@@ -126,8 +126,9 @@ dll : ../$(LIBDLLNAME) +@@ -129,8 +129,9 @@ dll : ../$(LIBDLLNAME) ../$(LIBDLLNAME) : ../$(LIBNAME) $(LIBPREFIX).def dllinit.$(SUFFIX) $(RANLIB) ../$(LIBNAME) $(CC) $(CFLAGS) $(LDFLAGS) $(LIBPREFIX).def dllinit.$(SUFFIX) \ @@ -12,4 +11,4 @@ index 668a4866e..956b51bf4 100644 + dlltool -D $(LIBDLLNAME) -d $(LIBPREFIX).def -l ../$(LIBDLLNAME).a $(LIBPREFIX).def : $(GENSYM) - ./$(GENSYM) win2k $(ARCH) dummy $(EXPRECISION) $(NO_CBLAS) $(NO_LAPACK) $(NO_LAPACKE) $(NEED2UNDERSCORES) $(ONLY_CBLAS) "$(SYMBOLPREFIX)" "$(SYMBOLSUFFIX)" $(BUILD_LAPACK_DEPRECATED) $(BUILD_BFLOAT16) $(BUILD_SINGLE) $(BUILD_DOUBLE) $(BUILD_COMPLEX) $(BUILD_COMPLEX16) > $(@F) + ./$(GENSYM) win2k $(ARCH) dummy $(EXPRECISION) $(NO_CBLAS) $(NO_LAPACK) $(NO_LAPACKE) $(NEED2UNDERSCORES) $(ONLY_CBLAS) "$(SYMBOLPREFIX)" "$(SYMBOLSUFFIX)" $(BUILD_LAPACK_DEPRECATED) $(BUILD_BFLOAT16) $(BUILD_HFLOAT16) $(BUILD_SINGLE) $(BUILD_DOUBLE) $(BUILD_COMPLEX) $(BUILD_COMPLEX16) > $(@F) diff --git a/patches-windows/openblas-win32-pool-queue-link.patch b/patches-windows/openblas-win32-pool-queue-link.patch new file mode 100644 index 00000000..80251ba1 --- /dev/null +++ b/patches-windows/openblas-win32-pool-queue-link.patch @@ -0,0 +1,101 @@ +Fix memory corruption from concurrent BLAS calls on Windows + +The Windows thread server keeps one global list of jobs waiting to be picked +up, and it links that list through blas_queue_t::next -- the same field the +*caller* owns and rebuilds on every call. + +diff --git a/common_thread.h b/common_thread.h +index 7f5d7b2..cd7ca77 100644 +--- a/common_thread.h ++++ b/common_thread.h +@@ -119,6 +119,14 @@ typedef struct blas_queue { + struct blas_queue *next; + + #if defined( __WIN32__) || defined(__CYGWIN32__) || defined(_WIN32) || defined(__CYGWIN__) ++ /* ++ * Link used exclusively by the Windows thread pool to chain the jobs that ++ * are waiting to be picked up. It is deliberately kept separate from ++ * "next" above, which is owned (and rebuilt on every call) by whoever ++ * submitted the job: appending to the pool must never write into the ++ * queue nodes of another caller. ++ */ ++ struct blas_queue *pool_next; + CRITICAL_SECTION lock; + HANDLE finish; + volatile int finished; +diff --git a/driver/others/blas_server_win32.c b/driver/others/blas_server_win32.c +index e827cee..4f24823 100644 +--- a/driver/others/blas_server_win32.c ++++ b/driver/others/blas_server_win32.c +@@ -57,6 +57,7 @@ typedef struct{ + HANDLE killed; + + blas_queue_t *queue; /* Parameter Pointer */ ++ blas_queue_t *tail; /* Last job waiting in "queue", for O(1) append */ + int shutdown; /* server shutdown flag */ + + } blas_pool_t; +@@ -239,7 +240,10 @@ static DWORD WINAPI blas_thread_server(void *arg){ + EnterCriticalSection(&pool.lock); + + queue = pool.queue; +- if (queue) pool.queue = queue->next; ++ if (queue) { ++ pool.queue = queue->pool_next; ++ if (pool.queue == NULL) pool.tail = NULL; ++ } + + LeaveCriticalSection(&pool.lock); + +@@ -374,6 +378,7 @@ int blas_thread_init(void){ + + pool.shutdown = 0; + pool.queue = NULL; ++ pool.tail = NULL; + + for(i = 0; i < blas_cpu_number - 1; i++){ + blas_threads[i] = CreateThread(NULL, 0, +@@ -405,7 +410,7 @@ int exec_blas_async(BLASLONG pos, blas_queue_t *queue){ + if (unlikely(blas_server_avail == 0)) blas_thread_init(); + #endif + +- blas_queue_t *current; ++ blas_queue_t *current, *last = NULL; + + current = queue; + +@@ -419,19 +424,22 @@ int exec_blas_async(BLASLONG pos, blas_queue_t *queue){ + __asm__ __volatile__ ("stmxcsr %0" : "=m" (current -> sse_mode)); + #endif + ++ /* Build the pool's own chain over this caller's jobs */ ++ current -> pool_next = current -> next; ++ last = current; ++ + current = current -> next; + pos ++; + } + + EnterCriticalSection(&pool.lock); + +- if (pool.queue) { +- current = pool.queue; +- while (current -> next) current = current -> next; +- current -> next = queue; ++ if (pool.tail) { ++ pool.tail -> pool_next = queue; + } else { + pool.queue = queue; + } ++ pool.tail = last; + + LeaveCriticalSection(&pool.lock); + +@@ -568,6 +576,7 @@ void goto_set_num_threads(int num_threads) + + pool.shutdown = 0; + pool.queue = NULL; ++ pool.tail = NULL; + blas_server_avail = 1; + } + diff --git a/tools/build_steps_win_arm64.bat b/tools/build_steps_win_arm64.bat index 9edc1260..e2332b00 100755 --- a/tools/build_steps_win_arm64.bat +++ b/tools/build_steps_win_arm64.bat @@ -71,8 +71,12 @@ if errorlevel 1 ( ) REM Patch -for /r %%f in (..\patches\*) do git apply %%f -if errorlevel 1 exit /b 1 +set patch_rc=0 +for %%f in (..\patches\*.patch ..\patches-windows\*.patch) do ( + git apply %%f + if errorlevel 1 set patch_rc=1 +) +if not "%patch_rc%"=="0" exit /b 1 REM Patch VERSION REM version=$(grep "^version =" ../pyproject.toml | sed 's/version = "//;s/"//') From fa30e5157e5c2e3f3a633b6c5b8d328c03bd37d5 Mon Sep 17 00:00:00 2001 From: mattip Date: Fri, 16 Oct 2026 16:25:01 +0300 Subject: [PATCH 4/6] fix patching lineending handling --- .gitattributes | 3 +++ patches-windows/openblas-win32-pool-queue-link.patch | 8 +------- tools/build_steps_win_arm64.bat | 2 +- tools/build_steps_windows.sh | 5 ++++- 4 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4bce0d61 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Patches must reach the build byte-identical: their line endings have to match +# the files they patch, and checkout conversion would break that. +*.patch -text diff --git a/patches-windows/openblas-win32-pool-queue-link.patch b/patches-windows/openblas-win32-pool-queue-link.patch index 80251ba1..98f78387 100644 --- a/patches-windows/openblas-win32-pool-queue-link.patch +++ b/patches-windows/openblas-win32-pool-queue-link.patch @@ -1,11 +1,6 @@ -Fix memory corruption from concurrent BLAS calls on Windows - -The Windows thread server keeps one global list of jobs waiting to be picked -up, and it links that list through blas_queue_t::next -- the same field the -*caller* owns and rebuilds on every call. +Give the Windows thread pool its own queue link, see MacPython/openblas-libs#283 diff --git a/common_thread.h b/common_thread.h -index 7f5d7b2..cd7ca77 100644 --- a/common_thread.h +++ b/common_thread.h @@ -119,6 +119,14 @@ typedef struct blas_queue { @@ -24,7 +19,6 @@ index 7f5d7b2..cd7ca77 100644 HANDLE finish; volatile int finished; diff --git a/driver/others/blas_server_win32.c b/driver/others/blas_server_win32.c -index e827cee..4f24823 100644 --- a/driver/others/blas_server_win32.c +++ b/driver/others/blas_server_win32.c @@ -57,6 +57,7 @@ typedef struct{ diff --git a/tools/build_steps_win_arm64.bat b/tools/build_steps_win_arm64.bat index e2332b00..d585a71c 100755 --- a/tools/build_steps_win_arm64.bat +++ b/tools/build_steps_win_arm64.bat @@ -73,7 +73,7 @@ if errorlevel 1 ( REM Patch set patch_rc=0 for %%f in (..\patches\*.patch ..\patches-windows\*.patch) do ( - git apply %%f + git apply --ignore-whitespace %%f if errorlevel 1 set patch_rc=1 ) if not "%patch_rc%"=="0" exit /b 1 diff --git a/tools/build_steps_windows.sh b/tools/build_steps_windows.sh index 2003e85e..7fbb9ae8 100644 --- a/tools/build_steps_windows.sh +++ b/tools/build_steps_windows.sh @@ -90,9 +90,12 @@ interface_flags="$interface_flags SYMBOLPREFIX=scipy_ LIBNAMEPREFIX=scipy_ FIXED GCC_TAG="gcc_$(gcc -dumpversion | tr .- _)" OPENBLAS_VERSION=$(git describe --tags --abbrev=8) +# --ignore-whitespace because the files patched here do not agree on line +# endings (blas_server_win32.c is CRLF, common_thread.h is not) and neither +# does checkout across platforms. for p in ../patches-windows/*.patch; do [ -e "$p" ] || continue - patch -p1 < "$p" + git apply --ignore-whitespace "$p" done # Patch VERSION From 1ef66087d338e92d347392bf31c43f3f17433306 Mon Sep 17 00:00:00 2001 From: mattip Date: Fri, 16 Oct 2026 16:31:13 +0300 Subject: [PATCH 5/6] use pre-built conda for anaconda upload --- .github/workflows/windows-arm.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/windows-arm.yml b/.github/workflows/windows-arm.yml index d2f2a512..16c7cf45 100644 --- a/.github/workflows/windows-arm.yml +++ b/.github/workflows/windows-arm.yml @@ -81,15 +81,12 @@ jobs: name: wheels-${{ env.PLAT }}-${{ env.INTERFACE64 }} path: dist/scipy_openblas*.whl - - name: Install Anaconda client - run: | - # Rust installation needed for rpds-py. - Invoke-WebRequest https://static.rust-lang.org/rustup/dist/aarch64-pc-windows-msvc/rustup-init.exe -UseBasicParsing -Outfile rustup-init.exe - .\rustup-init.exe -y - $env:PATH="$env:PATH;$env:USERPROFILE\.cargo\bin" - # cryptography dropped win-arm64 for 46.0.4, make sure we don't try to compile from source - pip install --only-binary :all: cryptography - pip install anaconda-client + - uses: conda-incubator/setup-miniconda@v4.0.1 + with: + channels: conda-forge + channel-priority: true + activate-environment: upload + miniforge-version: latest - name: Upload # see https://github.com/marketplace/actions/setup-miniconda for why @@ -98,5 +95,6 @@ jobs: env: ANACONDA_SCIENTIFIC_PYTHON_UPLOAD: ${{ secrets.ANACONDA_SCIENTIFIC_PYTHON_UPLOAD }} run: | + conda install -y anaconda-client source tools/upload_to_anaconda_staging.sh upload_wheels From f21b340ee885cc6f220c8be392a234a4703f531d Mon Sep 17 00:00:00 2001 From: mattip Date: Sat, 17 Oct 2026 11:21:16 +0300 Subject: [PATCH 6/6] use a single anaconda upload per job matrix --- .github/workflows/posix.yml | 13 ++++++++++++- .github/workflows/windows-arm.yml | 25 ++++++++++++++++--------- .github/workflows/windows.yml | 25 ++++++++++++++++--------- CHANGELOG.md | 5 +++++ tools/upload_to_anaconda_staging.sh | 10 +++++++--- 5 files changed, 56 insertions(+), 22 deletions(-) diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml index 2e77ec60..1e1b8b3b 100644 --- a/.github/workflows/posix.yml +++ b/.github/workflows/posix.yml @@ -119,8 +119,19 @@ jobs: name: wheels-${{ matrix.os }}-${{ matrix.PLAT }}-${{ matrix.INTERFACE64 }}-${{ matrix.MB_ML_LIBC }}-${{ matrix.MB_ML_VER }} path: dist/scipy_openblas*.whl + upload: + name: Upload to anaconda.org + needs: build + if: ${{ inputs.publish == false && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }} + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + pattern: wheels-* + merge-multiple: true + path: dist + - name: Upload to ananconda.org - if: ${{ inputs.publish == false && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }} uses: scientific-python/upload-nightly-action@0.6.4 with: artifacts_path: dist diff --git a/.github/workflows/windows-arm.yml b/.github/workflows/windows-arm.yml index 16c7cf45..060ff817 100644 --- a/.github/workflows/windows-arm.yml +++ b/.github/workflows/windows-arm.yml @@ -81,20 +81,27 @@ jobs: name: wheels-${{ env.PLAT }}-${{ env.INTERFACE64 }} path: dist/scipy_openblas*.whl - - uses: conda-incubator/setup-miniconda@v4.0.1 + upload: + name: Upload to anaconda.org + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.1 + + - uses: actions/setup-python@v6.2.0 + with: + python-version: 3.11 + + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: - channels: conda-forge - channel-priority: true - activate-environment: upload - miniforge-version: latest + pattern: wheels-* + merge-multiple: true + path: dist - name: Upload - # see https://github.com/marketplace/actions/setup-miniconda for why - # `-el {0}` is required. - shell: bash -el {0} env: ANACONDA_SCIENTIFIC_PYTHON_UPLOAD: ${{ secrets.ANACONDA_SCIENTIFIC_PYTHON_UPLOAD }} run: | - conda install -y anaconda-client + python -m pip install anaconda-client source tools/upload_to_anaconda_staging.sh upload_wheels diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9601ac82..303c422f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -168,20 +168,27 @@ jobs: python -m pip install pkgconf python -m pkgconf scipy-openblas --cflags - - uses: conda-incubator/setup-miniconda@v4.0.1 + upload: + name: Upload to anaconda.org + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.1 + + - uses: actions/setup-python@v6.2.0 + with: + python-version: 3.11 + + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: - channels: conda-forge - channel-priority: true - activate-environment: upload - miniforge-version: latest + pattern: wheels-* + merge-multiple: true + path: dist - name: Upload - # see https://github.com/marketplace/actions/setup-miniconda for why - # `-el {0}` is required. - shell: bash -el {0} env: ANACONDA_SCIENTIFIC_PYTHON_UPLOAD: ${{ secrets.ANACONDA_SCIENTIFIC_PYTHON_UPLOAD }} run: | - conda install -y anaconda-client + python -m pip install anaconda-client source tools/upload_to_anaconda_staging.sh upload_wheels diff --git a/CHANGELOG.md b/CHANGELOG.md index 9570f875..da5c7414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## OpenBLAS v0.3.34 (v0.3.34) +### 0.3.34.237.2 (2026-10-16) + +- Patch windows to use a per-thread queue instead of mistakenly sharing the + thread queue. Also simplify windows-arm64 upload to anaconda + ### 0.3.34.237.1 (2026-10-09) - Build every manylinux, musllinux and macOS wheel without a runtime diff --git a/tools/upload_to_anaconda_staging.sh b/tools/upload_to_anaconda_staging.sh index 31435a69..bf6da2ed 100644 --- a/tools/upload_to_anaconda_staging.sh +++ b/tools/upload_to_anaconda_staging.sh @@ -9,9 +9,13 @@ upload_wheels() { cd $our_wd fi - pushd OpenBLAS - VERSION=$(git describe --tags --abbrev=8) - popd + # The upload job only checks out openblas-libs, not the submodule; + # openblas_commit.txt holds the same string git describe would print. + if [ -d OpenBLAS/.git ]; then + VERSION=$(git -C OpenBLAS describe --tags --abbrev=8) + else + VERSION=$(cat openblas_commit.txt) + fi if [ "$ANACONDA_SCIENTIFIC_PYTHON_UPLOAD" == "" ]; then echo "ANACONDA_SCIENTIFIC_PYTHON_UPLOAD is not defined: skipping."