From b5d6e1c6de98074873c798bcb476fc6c60f62e5b Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Tue, 5 May 2026 08:54:27 -0700 Subject: [PATCH] Use synchronous dask scheduler for different-CRS merge parity test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_merge_dask_different_crs_matches_eager intermittently SIGABRTs on macOS CI runners. The merge() dask graph creates a fresh pyproj.Transformer per chunk, and PROJ's first-time CRS-database load is not safe under concurrent threaded workers — multiple threads racing to init the SQLite DB on macOS triggers an abort. The test exercises dask graph correctness, not dask threading. Pin it to the synchronous scheduler and pre-warm pyproj.CRS objects so the parity assertion runs without the threading dimension. CRS thread-safety in the production reproject path is its own concern and is not regressed here. Refs the same crash on PR #1492 macOS-3.13 and on main run e25390058 (macOS-3.12). --- xrspatial/tests/test_reproject.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/xrspatial/tests/test_reproject.py b/xrspatial/tests/test_reproject.py index e3931a04..003f5224 100644 --- a/xrspatial/tests/test_reproject.py +++ b/xrspatial/tests/test_reproject.py @@ -2435,8 +2435,23 @@ def test_merge_dask_same_crs_matches_eager(self): np.testing.assert_array_equal(eager[~eager_nan], dasked[~dask_nan]) def test_merge_dask_different_crs_matches_eager(self): - """Different-CRS merge should match within float tolerance.""" + """Different-CRS merge should match within float tolerance. + + Uses the synchronous dask scheduler. Multi-CRS reprojection + creates a fresh ``pyproj.Transformer`` per chunk, and PROJ's + first-time CRS-database load is not safe under concurrent + threaded workers on macOS (the test would SIGABRT mid-compute). + Synchronous compute exercises the same dask graph without the + threading dimension; CRS thread-safety is its own concern, + outside the scope of this parity test. + """ + import pyproj from xrspatial.reproject import merge + # Pre-warm the PROJ database in this thread so that any first-init + # work happens here, not concurrently inside dask compute. + pyproj.CRS.from_epsg(4326) + pyproj.CRS.from_epsg(3857) + a_data = np.arange(256, dtype=np.float64).reshape(16, 16) b_data = (np.arange(256, dtype=np.float64) + 100.0).reshape(16, 16) # One in WGS84, one in Web Mercator (forces reprojection) @@ -2448,7 +2463,7 @@ def test_merge_dask_different_crs_matches_eager(self): eager = merge( [a, b], target_crs='EPSG:4326', resolution=1.0, - ).compute().values + ).compute(scheduler='synchronous').values a_dask = a.copy() b_dask = b.copy() @@ -2457,7 +2472,7 @@ def test_merge_dask_different_crs_matches_eager(self): dasked = merge( [a_dask, b_dask], target_crs='EPSG:4326', resolution=1.0, chunk_size=8, - ).compute().values + ).compute(scheduler='synchronous').values assert eager.shape == dasked.shape np.testing.assert_array_equal(np.isnan(eager), np.isnan(dasked))