From 6fc8c3b887347bccb2a76d1720a2df6152d70929 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sun, 28 Jun 2026 06:01:17 -0600 Subject: [PATCH 1/2] fix: use global fortran lock instead of per-library We are sharing Fortran SAVE state and other global things and using a shared library too, so we need to raise the lock up to the top-level and not try to optimize it for individual libraries. There are likely few people who need the per-library performance benefit. --- pymsis/msis.py | 8 ++++---- tests/test_msis.py | 10 +++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pymsis/msis.py b/pymsis/msis.py index 3588460..fe6ad57 100644 --- a/pymsis/msis.py +++ b/pymsis/msis.py @@ -13,13 +13,13 @@ # We need to point to the MSIS parameter file that was installed with the Python package _MSIS_PARAMETER_PATH = str(Path(__file__).resolve().parent) + "/" +# A single global lock guarding all calls into the Fortran code. +# per-library isn't sufficient because the Fortran code uses global state +_lock = threading.Lock() for lib in [msis00f, msis20f, msis21f]: # Store the previous options to avoid reinitializing the model # each iteration unless necessary lib._last_used_options = None - # Anytime we call into the Fortran code, we need to lock - # to avoid threading issues - lib._lock = threading.Lock() class Variable(IntEnum): @@ -282,7 +282,7 @@ def calculate( "one of the valid version numbers: (0, 2.0, 2.1)" ) - with msis_lib._lock: + with _lock: # Only reinitialize the model if the options have changed if msis_lib._last_used_options != options: msis_lib.pyinitswitch(options, parmpath=_MSIS_PARAMETER_PATH) diff --git a/tests/test_msis.py b/tests/test_msis.py index f0d7bbb..abd2f34 100644 --- a/tests/test_msis.py +++ b/tests/test_msis.py @@ -508,12 +508,20 @@ def test_multithreaded( [f107a] * n, ap * n, ) + expected_output20 = np.squeeze(pymsis.calculate(*input_data, version=2.0)) + expected_output20_with_options = np.squeeze( + pymsis.calculate(*input_data, version=2.0, options=[0] * 25) + ) + # Create a tuple of items (version, options, expected_output) - # 3 items cycled over 100 times + # cycled over 100 times. Mixing versions 2.0 and 2.1 with differing + # options forces frequent re-initialization of multiple libraries. list_of_inputs = [ (0, None, np.tile(expected_output00, (n, 1))), (2.1, None, np.tile(expected_output, (n, 1))), (2.1, [0] * 25, np.tile(expected_output_with_options, (n, 1))), + (2.0, None, expected_output20), + (2.0, [0] * 25, expected_output20_with_options), ] * 100 def run_function(input_items): From 49494c7a5e183c0be15a229cd7c5c85731f185b8 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sun, 28 Jun 2026 06:29:38 -0600 Subject: [PATCH 2/2] fix: initialize array output in fortran interface --- src/wrappers/msis2.F90 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wrappers/msis2.F90 b/src/wrappers/msis2.F90 index 959bf89..d4d0188 100644 --- a/src/wrappers/msis2.F90 +++ b/src/wrappers/msis2.F90 @@ -45,6 +45,11 @@ subroutine pymsiscalc(day, utsec, lon, lat, z, sflux, sfluxavg, ap, output, n) integer :: i + ! Zero the output before calling into the model. There are some issues with + ! potential nan-leakage on unitiailzed arrays in free-threading builds, + ! so just set it to 0 to avoid any potential issues. + output = 0.0_rp + do i=1, n call msiscalc(day(i), utsec(i), z(i), lat(i), lon(i), sfluxavg(i), & sflux(i), ap(i, :), output(i, 11), output(i, 1:10))