22Object Store plugin for the Integrated Rule-Oriented Data System (iRODS)
33"""
44
5+ import functools
56import logging
67import os
78import shutil
89import ssl
910import threading
11+ import time
1012from datetime import datetime
1113from pathlib import Path
1214
1618 from irods .exception import (
1719 CollectionDoesNotExist ,
1820 DataObjectDoesNotExist ,
21+ NetworkException ,
1922 )
2023 from irods .session import iRODSSession
2124except ImportError :
2225 irods = None
2326
27+
2428from galaxy .util import (
2529 ExecutionTimer ,
2630 string_as_bool ,
3539logging .getLogger ("irods.connection" ).setLevel (logging .INFO ) # irods logging generates gigabytes of logs
3640
3741
42+ _IRODS_RETRY_ATTEMPTS = 3
43+ _IRODS_RETRY_BACKOFF = 0.5
44+
45+
46+ def _retry_on_connection_error (func ):
47+ """Retry iRODS read operations on a transient connection error."""
48+
49+ @functools .wraps (func )
50+ def wrapper (* args , ** kwargs ):
51+ for attempt in range (1 , _IRODS_RETRY_ATTEMPTS + 1 ):
52+ try :
53+ return func (* args , ** kwargs )
54+ except (NetworkException , ssl .SSLError ) as exc :
55+ if attempt == _IRODS_RETRY_ATTEMPTS :
56+ raise
57+ log .warning (
58+ "Transient iRODS error in %s (attempt %d/%d), retrying on a fresh connection: %s" ,
59+ func .__name__ ,
60+ attempt ,
61+ _IRODS_RETRY_ATTEMPTS ,
62+ exc ,
63+ )
64+ time .sleep (_IRODS_RETRY_BACKOFF * attempt )
65+
66+ return wrapper
67+
68+
3869def _config_xml_error (tag ):
3970 msg = f"No { tag } element in config XML tree"
4071 raise Exception (msg )
@@ -376,6 +407,7 @@ def _config_to_dict(self):
376407 }
377408
378409 # rel_path is file or folder?
410+ @_retry_on_connection_error
379411 def _get_remote_size (self , rel_path ):
380412 ipt_timer = ExecutionTimer ()
381413 p = Path (rel_path )
@@ -396,6 +428,7 @@ def _get_remote_size(self, rel_path):
396428 log .debug ("irods_pt _get_remote_size: %s" , ipt_timer )
397429
398430 # rel_path is file or folder?
431+ @_retry_on_connection_error
399432 def _exists_remotely (self , rel_path ):
400433 ipt_timer = ExecutionTimer ()
401434 p = Path (rel_path )
@@ -415,6 +448,7 @@ def _exists_remotely(self, rel_path):
415448 finally :
416449 log .debug ("irods_pt _exists_remotely: %s" , ipt_timer )
417450
451+ @_retry_on_connection_error
418452 def _download (self , rel_path ):
419453 ipt_timer = ExecutionTimer ()
420454 cache_path = self ._get_cache_path (rel_path )
0 commit comments