You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ja...@apache.org on 2026/08/10 18:11:04 UTC
(sedona-db) branch main updated: fix(sedona-gdal): skip GDALClose during interpreter shutdown (#1135)
This is an automated email from the ASF dual-hosted git repository.
james-willis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sedona-db.git
The following commit(s) were added to refs/heads/main by this push:
new 4f844cf14 fix(sedona-gdal): skip GDALClose during interpreter shutdown (#1135)
4f844cf14 is described below
commit 4f844cf14fe4cb38a7a849937303b980f8b6502c
Author: James Willis <ja...@users.noreply.github.com>
AuthorDate: Mon Aug 10 11:10:57 2026 -0700
fix(sedona-gdal): skip GDALClose during interpreter shutdown (#1135)
---
c/sedona-gdal/src/dataset.rs | 6 +++++-
c/sedona-gdal/src/global.rs | 24 ++++++++++++++++++++++++
python/sedonadb/python/sedonadb/__init__.py | 7 +++++++
python/sedonadb/src/lib.rs | 10 ++++++++++
4 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/c/sedona-gdal/src/dataset.rs b/c/sedona-gdal/src/dataset.rs
index 4577aa851..d6ce7990f 100644
--- a/c/sedona-gdal/src/dataset.rs
+++ b/c/sedona-gdal/src/dataset.rs
@@ -46,7 +46,11 @@ unsafe impl Send for Dataset {}
impl Drop for Dataset {
fn drop(&mut self) {
- if !self.c_dataset.is_null() {
+ // Once the process is shutting down, leave the handle open. On Windows,
+ // closing a dataset while GDAL's shared library is being unloaded
+ // corrupts teardown and aborts the process (0xC0000409); leaking the
+ // handle at process exit is harmless since the OS reclaims it.
+ if !self.c_dataset.is_null() && !crate::global::is_gdal_shutting_down() {
unsafe { call_gdal_api!(self.api, GDALClose, self.c_dataset) };
}
}
diff --git a/c/sedona-gdal/src/global.rs b/c/sedona-gdal/src/global.rs
index 5b9581163..3de26d7a2 100644
--- a/c/sedona-gdal/src/global.rs
+++ b/c/sedona-gdal/src/global.rs
@@ -19,8 +19,32 @@ use crate::errors::GdalInitLibraryError;
use crate::gdal::Gdal;
use crate::gdal_api::GdalApi;
use std::path::PathBuf;
+use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
+/// Set once the process is shutting down so GDAL datasets are left open during
+/// teardown instead of being closed.
+///
+/// On Windows, running `GDALClose` from a (thread-local) dataset cache's
+/// destructor while the GDAL shared library is being unloaded corrupts the
+/// teardown and aborts the process (`0xC0000409`). Leaking the handles at
+/// process exit is harmless — the OS reclaims them — so once shutdown begins
+/// [`crate::dataset::Dataset`]'s `Drop` skips `GDALClose`.
+static GDAL_SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
+
+/// Signal that the process is shutting down; see [`GDAL_SHUTTING_DOWN`].
+///
+/// Intended to run on the interpreter thread during finalization (e.g. a Python
+/// `atexit` callback), before GDAL's library is unloaded.
+pub fn begin_gdal_shutdown() {
+ GDAL_SHUTTING_DOWN.store(true, Ordering::SeqCst);
+}
+
+/// Whether [`begin_gdal_shutdown`] has been called.
+pub fn is_gdal_shutting_down() -> bool {
+ GDAL_SHUTTING_DOWN.load(Ordering::SeqCst)
+}
+
/// Minimum GDAL version required by sedona-gdal.
const MIN_GDAL_VERSION_MAJOR: i32 = 3;
const MIN_GDAL_VERSION_MINOR: i32 = 5;
diff --git a/python/sedonadb/python/sedonadb/__init__.py b/python/sedonadb/python/sedonadb/__init__.py
index 94cdf73d4..a66e244cb 100644
--- a/python/sedonadb/python/sedonadb/__init__.py
+++ b/python/sedonadb/python/sedonadb/__init__.py
@@ -14,6 +14,8 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+import atexit
+
from sedonadb import _lib
from sedonadb.context import connect, configure_proj, configure_gdal
@@ -30,3 +32,8 @@ __all__ = ["connect", "options"]
# GDAL-backed operation (e.g., raster I/O).
configure_proj("auto")
configure_gdal("auto")
+
+# During interpreter shutdown, leave GDAL datasets open instead of closing them:
+# on Windows, closing a dataset while GDAL's library is being unloaded aborts the
+# process. Registered here so it runs before the extension is torn down.
+atexit.register(_lib.begin_gdal_shutdown)
diff --git a/python/sedonadb/src/lib.rs b/python/sedonadb/src/lib.rs
index 6663e3153..43bb50790 100644
--- a/python/sedonadb/src/lib.rs
+++ b/python/sedonadb/src/lib.rs
@@ -140,11 +140,21 @@ fn gdal_version() -> Result<Option<String>, PySedonaError> {
}
}
+/// Signal that GDAL is shutting down so datasets are left open during
+/// interpreter/library teardown. Registered as a Python `atexit` callback to
+/// avoid a Windows process-exit abort (`0xC0000409`) when a GDAL dataset is
+/// closed while the library is being unloaded.
+#[pyfunction]
+fn begin_gdal_shutdown() {
+ sedona_gdal::global::begin_gdal_shutdown();
+}
+
#[pymodule(gil_used = false)]
fn _lib(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
#[cfg(feature = "mimalloc")]
configure_tg_allocator();
+ m.add_function(wrap_pyfunction!(begin_gdal_shutdown, m)?)?;
m.add_function(wrap_pyfunction!(configure_gdal_shared, m)?)?;
m.add_function(wrap_pyfunction!(configure_proj_shared, m)?)?;
m.add_function(wrap_pyfunction!(expr::expr_binary, m)?)?;