Skip to content

Commit

Permalink
[_18] close iRODSFS file handles before interpreter exit.
Browse files Browse the repository at this point in the history
If available, the irods.at_client_exit.register feature of the
Python iRODS Client is called in an attempt to properly schedule
the auto-closing (and therefore auto-flushing) of content written
to iRODSFS handles before Python interpreter exit.
  • Loading branch information
d-w-moore committed Aug 28, 2024
1 parent 3e717e9 commit 89881a4
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 2 deletions.
45 changes: 44 additions & 1 deletion fs_irods/iRODSFS.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import atexit
import datetime
from io import BufferedRandom
import io
import logging
import os

from multiprocessing import RLock
from typing import Text
from weakref import WeakKeyDictionary
from fs.base import FS
from fs.info import Info
from fs.permissions import Permissions
Expand All @@ -18,6 +21,24 @@

from fs_irods.utils import can_create

fses = WeakKeyDictionary()
_logger = logging.getLogger(__name__)

# Close out dangling file handles.
def finalize():
for fs in list(fses):
fs._finalize_files()

try:
# (see python-irodsclient issue #614)
from irods.at_client_exit import (
register as register_cleanup_function,
BEFORE_PRC)
register_cleanup_function(BEFORE_PRC, finalize)
except ImportError:
_logger.info("Content written to iRODSFS file handles may not be automatically saved at process exit [#18]."
" Recommend upgrading to >=v3.0.0 of the Python iRODS Client.")

_utc=datetime.timezone(datetime.timedelta(0))

class iRODSFS(FS):
Expand All @@ -27,8 +48,9 @@ def __init__(self, session: iRODSSession) -> None:
self._host = session.host
self._port = session.port
self._zone = session.zone

self._session = session
self.files = WeakKeyDictionary()
fses[self] = None

def wrap(self, path: str) -> str:
if path.startswith(f"/{self._zone}"):
Expand Down Expand Up @@ -112,6 +134,27 @@ def makedir(self, path: str, permissions: Permissions|None = None, recreate: boo
with self._lock:
self._session.collections.create(self.wrap(path), recurse=False)

# Allow Python iRODS Client to preemptively close handles to data object (aka "file") handles opened via
# iRODSFS, if this is happening at interpreter exit, so it can ensure shutdown happen in the proper order.
def _finalize_files(self):
self._files_finalized = 1
l = list(self.files)
while l:
f = l.pop()
if not f.closed:
f.close()

def __del__(self):
if not getattr(self,'_files_finalized',None):
self._finalize_files()

# Store weak references to open file handles that maintain a hard reference to the iRODSFS object.
# In this way, the iRODSFS can only be destructed once these file handles are gone.
def open(self,*a,**kw):
fd = super().open(*a,**kw)
self.files[fd] = self
return fd

def openbin(self, path: str, mode:str = "r", buffering: int = -1, **options) -> BufferedRandom:
"""Open a binary file-like object on the filesystem.
Args:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ license = "MIT"
[tool.poetry.dependencies]
python = "^3.8"
fs = "^2.4.16"
python-irodsclient = "^1.1.9"
python-irodsclient = ">=1.1.9"


[tool.poetry.group.dev.dependencies]
Expand Down

0 comments on commit 89881a4

Please sign in to comment.