Skip to content

Commit

Permalink
Thread-safe state for visopener and a change note (#2988)
Browse files Browse the repository at this point in the history
Resolves #2987
  • Loading branch information
sgillies committed Dec 19, 2023
1 parent 8156bc9 commit c463337
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 10 deletions.
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Changes
Next
----

- Datasets stored in proprietary systems or addressable only through protocols
not directly supported by GDAL can be accessed using the new opener keyword
argument of rasterio.open() (#2898). This new feature is intended to
completely replace the FilePath class introduced in 1.3.0.
- Deallocate list of warp extras in _reproject(), fixing a potential leak
(#494).
- Adjust several tests to small differences in output between GDAL 3.7 and 3.8
Expand Down
30 changes: 20 additions & 10 deletions rasterio/_vsiopener.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Based on _filepath.pyx.
include "gdal.pxi"

import contextlib
from contextvars import ContextVar
import logging
from uuid import uuid4

Expand Down Expand Up @@ -37,8 +38,10 @@ cdef bytes PREFIX_BYTES = PREFIX.encode("utf-8")
# the plugin to determine what "files" exist on "disk".
# Currently the only way to "create" a file in the filesystem is to add
# an entry to this dictionary. GDAL will then Open the path later.
cdef _OPENER_REGISTRY = {}
cdef _OPEN_FILE_EXIT_STACKS = {}
_OPENER_REGISTRY = ContextVar("opener_registery")
_OPENER_REGISTRY.set({})
_OPEN_FILE_EXIT_STACKS = ContextVar("open_file_exit_stacks")
_OPEN_FILE_EXIT_STACKS.set({})


cdef int install_pyopener_plugin(VSIFilesystemPluginCallbacksStruct *callbacks_struct):
Expand Down Expand Up @@ -85,7 +88,8 @@ cdef void* pyopener_open(void *pUserData, const char *pszFilename, const char *p
log.error("Python opener registry is not initialized.")
return NULL

cdef dict registry = <object>pUserData
cdef object var = <object>pUserData
cdef dict registry = var.get()
filename = pszFilename.decode("utf-8")

log.debug("Looking up opener: registry=%r, filename=%r", registry, filename)
Expand Down Expand Up @@ -116,7 +120,9 @@ cdef void* pyopener_open(void *pUserData, const char *pszFilename, const char *p
except (AttributeError, TypeError):
log.debug("File object is not a context manager: file_obj=%r", file_obj)

_OPEN_FILE_EXIT_STACKS[file_obj] = stack
exit_stacks = _OPEN_FILE_EXIT_STACKS.get()
exit_stacks[file_obj] = stack
_OPEN_FILE_EXIT_STACKS.set(exit_stacks)
return <void *>file_obj


Expand Down Expand Up @@ -144,17 +150,21 @@ cdef size_t pyopener_read(void *pFile, void *pBuffer, size_t nSize, size_t nCoun
cdef int pyopener_close(void *pFile) except -1 with gil:
cdef object file_obj = <object>pFile
log.debug("Closing: file_obj=%r", file_obj)
stack = _OPEN_FILE_EXIT_STACKS.pop(file_obj)
exit_stacks = _OPEN_FILE_EXIT_STACKS.get()
stack = exit_stacks.pop(file_obj)
stack.close()
_OPEN_FILE_EXIT_STACKS.set(exit_stacks)
return 0


@contextlib.contextmanager
def _opener_registration(urlpath, opener):
filename = urlpath
_OPENER_REGISTRY[filename] = opener
registry = _OPENER_REGISTRY.get()
registry[urlpath] = opener
_OPENER_REGISTRY.set(registry)
try:
yield f"{PREFIX}{filename}"
yield f"{PREFIX}{urlpath}"
finally:
_ = _OPENER_REGISTRY.pop(filename)

registry = _OPENER_REGISTRY.get()
_ = registry.pop(urlpath)
_OPENER_REGISTRY.set(registry)

0 comments on commit c463337

Please sign in to comment.