From 31fc2613015214b1bc34a43b07430e85d24fbd6d Mon Sep 17 00:00:00 2001 From: Amin Solhizadeh Date: Thu, 23 Apr 2026 14:51:25 +0200 Subject: [PATCH 1/6] Debugging linuxbrew workflow --- .github/workflows/linuxbrew.yml | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/.github/workflows/linuxbrew.yml b/.github/workflows/linuxbrew.yml index 51b0db1e..4a027e91 100644 --- a/.github/workflows/linuxbrew.yml +++ b/.github/workflows/linuxbrew.yml @@ -28,17 +28,39 @@ jobs: - name: Install build dependencies run: | brew update - brew install python@${{ matrix.python }} gcc libxml2 libxmlsec1 pkg-config + brew install python@${{ matrix.python }} gcc libxml2 libxslt libxmlsec1 pkg-config echo "/home/linuxbrew/.linuxbrew/opt/python@${{ matrix.python }}/libexec/bin" >> $GITHUB_PATH + - name: Configure Homebrew toolchain + run: | + HOMEBREW_PREFIX="$(brew --prefix)" + LIBXML2_PREFIX="$(brew --prefix libxml2)" + LIBXSLT_PREFIX="$(brew --prefix libxslt)" + XMLSEC_PREFIX="$(brew --prefix libxmlsec1)" + OPENSSL_PREFIX="$(brew --prefix openssl@3)" + + { + echo "PKG_CONFIG_PATH=${LIBXML2_PREFIX}/lib/pkgconfig:${LIBXSLT_PREFIX}/lib/pkgconfig:${XMLSEC_PREFIX}/lib/pkgconfig:${OPENSSL_PREFIX}/lib/pkgconfig" + echo "CPPFLAGS=-I${LIBXML2_PREFIX}/include -I${LIBXSLT_PREFIX}/include -I${XMLSEC_PREFIX}/include/xmlsec1 -I${OPENSSL_PREFIX}/include" + echo "CFLAGS=-I${LIBXML2_PREFIX}/include -I${LIBXSLT_PREFIX}/include -I${XMLSEC_PREFIX}/include/xmlsec1 -I${OPENSSL_PREFIX}/include" + echo "LDFLAGS=-L${LIBXML2_PREFIX}/lib -L${LIBXSLT_PREFIX}/lib -L${XMLSEC_PREFIX}/lib -L${OPENSSL_PREFIX}/lib" + echo "LD_LIBRARY_PATH=${LIBXML2_PREFIX}/lib:${LIBXSLT_PREFIX}/lib:${XMLSEC_PREFIX}/lib:${OPENSSL_PREFIX}/lib" + echo "LIBRARY_PATH=${LIBXML2_PREFIX}/lib:${LIBXSLT_PREFIX}/lib:${XMLSEC_PREFIX}/lib:${OPENSSL_PREFIX}/lib" + echo "C_INCLUDE_PATH=${LIBXML2_PREFIX}/include:${LIBXSLT_PREFIX}/include:${XMLSEC_PREFIX}/include/xmlsec1:${OPENSSL_PREFIX}/include" + echo "XML2_CONFIG=${LIBXML2_PREFIX}/bin/xml2-config" + echo "XSLT_CONFIG=${LIBXSLT_PREFIX}/bin/xslt-config" + } >> $GITHUB_ENV + + echo "${LIBXML2_PREFIX}/bin" >> $GITHUB_PATH + echo "${LIBXSLT_PREFIX}/bin" >> $GITHUB_PATH + - name: Build wheel run: | python3 -m venv build_venv source build_venv/bin/activate - pip3 install --upgrade setuptools wheel build - export CFLAGS="-I$(brew --prefix)/include" - export LDFLAGS="-L$(brew --prefix)/lib" - python3 -m build + pip3 install --upgrade setuptools wheel build setuptools_scm pkgconfig + pip3 install --upgrade --no-binary=lxml -r requirements.txt + python3 -m build --no-isolation rm -rf build/ - name: Run tests From 28cc689fbe0609881bf128f92f089f23f81cec1a Mon Sep 17 00:00:00 2001 From: Amin Solhizadeh Date: Thu, 23 Apr 2026 14:52:50 +0200 Subject: [PATCH 2/6] Disable all workflows except linuxbrew, for testing purposes --- .github/workflows/linuxbrew.yml | 4 ++-- .github/workflows/macosx.yml | 3 ++- .github/workflows/manylinux.yml | 3 ++- .github/workflows/sdist.yml | 3 ++- .github/workflows/wheels.yml | 12 ------------ 5 files changed, 8 insertions(+), 17 deletions(-) diff --git a/.github/workflows/linuxbrew.yml b/.github/workflows/linuxbrew.yml index 4a027e91..fce518f3 100644 --- a/.github/workflows/linuxbrew.yml +++ b/.github/workflows/linuxbrew.yml @@ -1,5 +1,5 @@ name: linuxbrew -on: [push, pull_request] +on: [push] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref_name != 'master' }} @@ -10,7 +10,7 @@ jobs: strategy: matrix: - python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.14"] env: # For some unknown reason, linuxbrew tries to use "gcc-11" by default, which doesn't exist. diff --git a/.github/workflows/macosx.yml b/.github/workflows/macosx.yml index c9d8034e..6ab8b42f 100644 --- a/.github/workflows/macosx.yml +++ b/.github/workflows/macosx.yml @@ -1,5 +1,6 @@ name: macOS -on: [push, pull_request] +on: + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref_name != 'master' }} diff --git a/.github/workflows/manylinux.yml b/.github/workflows/manylinux.yml index fe31b66e..ad8a04ab 100644 --- a/.github/workflows/manylinux.yml +++ b/.github/workflows/manylinux.yml @@ -1,5 +1,6 @@ name: manylinux -on: [push, pull_request] +on: + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref_name != 'master' }} diff --git a/.github/workflows/sdist.yml b/.github/workflows/sdist.yml index f48ca02c..18ac1c74 100644 --- a/.github/workflows/sdist.yml +++ b/.github/workflows/sdist.yml @@ -1,5 +1,6 @@ name: sdist -on: [push, pull_request] +on: + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref_name != 'master' }} diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 422b038e..fdf4630d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,18 +1,6 @@ name: Wheel build on: - release: - types: [created] - schedule: - # ┌───────────── minute (0 - 59) - # │ ┌───────────── hour (0 - 23) - # │ │ ┌───────────── day of the month (1 - 31) - # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) - # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) - # │ │ │ │ │ - - cron: "42 3 * * 4" - push: - pull_request: workflow_dispatch: concurrency: From 9b633ca4cf713cb1b76b36831e1652f1ee385d34 Mon Sep 17 00:00:00 2001 From: Amin Solhizadeh Date: Thu, 23 Apr 2026 15:03:35 +0200 Subject: [PATCH 3/6] Shutdown crypto too --- src/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.c b/src/main.c index 61eac139..c8bdb319 100644 --- a/src/main.c +++ b/src/main.c @@ -44,6 +44,7 @@ static void PyXmlSec_Free(int what) { PYXMLSEC_DEBUGF("free resources %d", what); switch (what) { case _PYXMLSEC_FREE_ALL: + xmlSecCryptoShutdown(); xmlSecCryptoAppShutdown(); case _PYXMLSEC_FREE_CRYPTOLIB: #ifndef XMLSEC_NO_CRYPTO_DYNAMIC_LOADING From 1285eb1caa5f1da6ad74e07f8c4312fdb0eefbda Mon Sep 17 00:00:00 2001 From: Amin Solhizadeh Date: Thu, 23 Apr 2026 15:52:45 +0200 Subject: [PATCH 4/6] Remove clear --- src/main.c | 11 +++-------- tests/test_xmlsec.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main.c b/src/main.c index c8bdb319..22e7d8db 100644 --- a/src/main.c +++ b/src/main.c @@ -114,8 +114,8 @@ static PyObject* PyXmlSec_PyInit(PyObject *self) { static char PyXmlSec_PyShutdown__doc__[] = \ "shutdown() -> None\n" "Shutdowns the library and cleanup any leftover resources.\n\n" - "This is called automatically upon interpreter termination and\n" - "should not need to be called explicitly."; + "This is not called automatically upon interpreter termination because\n" + "xmlsec-owned objects may still be finalized during Python shutdown."; static PyObject* PyXmlSec_PyShutdown(PyObject* self) { PyXmlSec_Free(free_mode); Py_RETURN_NONE; @@ -488,11 +488,6 @@ int PyXmlSec_EncModule_Init(PyObject* package); // templates management int PyXmlSec_TemplateModule_Init(PyObject* package); -static int PyXmlSec_PyClear(PyObject *self) { - PyXmlSec_Free(free_mode); - return 0; -} - static PyModuleDef PyXmlSecModule = { PyModuleDef_HEAD_INIT, STRINGIFY(MODULE_NAME), /* name of module */ @@ -502,7 +497,7 @@ static PyModuleDef PyXmlSecModule = { PyXmlSec_MainMethods, /* m_methods */ NULL, /* m_slots */ NULL, /* m_traverse */ - PyXmlSec_PyClear, /* m_clear */ + NULL, /* m_clear */ NULL, /* m_free */ }; diff --git a/tests/test_xmlsec.py b/tests/test_xmlsec.py index 52dce2b3..167a5e7c 100644 --- a/tests/test_xmlsec.py +++ b/tests/test_xmlsec.py @@ -1,3 +1,6 @@ +import subprocess +import sys + import xmlsec from tests import base @@ -11,3 +14,15 @@ def test_reinitialize_module(self): """ xmlsec.shutdown() xmlsec.init() + + def test_interpreter_exit_with_live_xmlsec_objects(self): + key_path = self.path('rsakey.pem') + script = f""" +import xmlsec + +key = xmlsec.Key.from_file({key_path!r}, format=xmlsec.constants.KeyDataFormatPem) +ctx = xmlsec.SignatureContext() +ctx.key = key +""" + proc = subprocess.run([sys.executable, '-c', script], capture_output=True, text=True) + self.assertEqual(proc.returncode, 0, proc.stderr) From 120beedcbedf85ba26bfd58eac07b78cb73ca81a Mon Sep 17 00:00:00 2001 From: Amin Solhizadeh Date: Thu, 23 Apr 2026 16:14:21 +0200 Subject: [PATCH 5/6] Some random change --- src/main.c | 12 +++++++++++- tests/test_xmlsec.py | 24 ++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/main.c b/src/main.c index 22e7d8db..2011b889 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,7 @@ #define _PYXMLSEC_FREE_XMLSEC 1 #define _PYXMLSEC_FREE_CRYPTOLIB 2 #define _PYXMLSEC_FREE_ALL 3 +#define _PYXMLSEC_FREE_ALL_BUT_CRYPTOLIB 4 static int free_mode = _PYXMLSEC_FREE_NONE; @@ -52,6 +53,12 @@ static void PyXmlSec_Free(int what) { #endif case _PYXMLSEC_FREE_XMLSEC: xmlSecShutdown(); + break; + case _PYXMLSEC_FREE_ALL_BUT_CRYPTOLIB: + xmlSecCryptoShutdown(); + xmlSecCryptoAppShutdown(); + xmlSecShutdown(); + break; } free_mode = _PYXMLSEC_FREE_NONE; } @@ -95,7 +102,10 @@ static int PyXmlSec_Init(void) { // We thus reinstall our callback now. PyXmlSec_InstallErrorCallback(); - free_mode = _PYXMLSEC_FREE_ALL; + // Keep the dynamically loaded crypto backend resident for the lifetime of + // the process. Python-level constants cache xmlsec transform/keydata ids, + // and unloading the backend invalidates those pointers after shutdown/init. + free_mode = _PYXMLSEC_FREE_ALL_BUT_CRYPTOLIB; return 0; } diff --git a/tests/test_xmlsec.py b/tests/test_xmlsec.py index 167a5e7c..d9b8db20 100644 --- a/tests/test_xmlsec.py +++ b/tests/test_xmlsec.py @@ -1,5 +1,7 @@ +from pathlib import Path import subprocess import sys +import unittest import xmlsec from tests import base @@ -15,14 +17,32 @@ def test_reinitialize_module(self): xmlsec.shutdown() xmlsec.init() + +class TestInterpreterShutdown(unittest.TestCase): def test_interpreter_exit_with_live_xmlsec_objects(self): - key_path = self.path('rsakey.pem') + key_path = Path(__file__).with_name('data') / 'rsakey.pem' script = f""" import xmlsec -key = xmlsec.Key.from_file({key_path!r}, format=xmlsec.constants.KeyDataFormatPem) +key = xmlsec.Key.from_file({str(key_path)!r}, format=xmlsec.constants.KeyDataFormatPem) ctx = xmlsec.SignatureContext() ctx.key = key +""" + proc = subprocess.run([sys.executable, '-c', script], capture_output=True, text=True) + self.assertEqual(proc.returncode, 0, proc.stderr) + + def test_reinitialize_module_preserves_constants(self): + script = """ +import xmlsec + +transform = xmlsec.constants.TransformExclC14N +keydata = xmlsec.constants.KeyDataAes + +xmlsec.shutdown() +xmlsec.init() + +assert transform.name == "exc-c14n" +assert keydata.name == "aes" """ proc = subprocess.run([sys.executable, '-c', script], capture_output=True, text=True) self.assertEqual(proc.returncode, 0, proc.stderr) From 5ed50be122f13f661ba1e8a4b6d34d519287688b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 14:14:45 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_xmlsec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_xmlsec.py b/tests/test_xmlsec.py index d9b8db20..5d69d106 100644 --- a/tests/test_xmlsec.py +++ b/tests/test_xmlsec.py @@ -1,7 +1,7 @@ -from pathlib import Path import subprocess import sys import unittest +from pathlib import Path import xmlsec from tests import base