44"""
55
66import datetime
7+ import re
78import uuid
89from stat import S_ISDIR , S_ISLNK
910
1011import smbclient
12+ import smbprotocol .exceptions
1113
1214from .. import AbstractFileSystem
1315from ..utils import infer_storage_options
@@ -67,7 +69,9 @@ def __init__(
6769 timeout = 60 ,
6870 encrypt = None ,
6971 share_access = None ,
70- register_session_retries = 5 ,
72+ register_session_retries = 4 ,
73+ register_session_retry_wait = 1 ,
74+ register_session_retry_factor = 10 ,
7175 auto_mkdir = False ,
7276 ** kwargs ,
7377 ):
@@ -103,6 +107,19 @@ def __init__(
103107 - 'r': Allow other handles to be opened with read access.
104108 - 'w': Allow other handles to be opened with write access.
105109 - 'd': Allow other handles to be opened with delete access.
110+ register_session_retries: int
111+ Number of retries to register a session with the server. Retries are not performed
112+ for authentication errors, as they are considered as invalid credentials and not network
113+ issues. If set to negative value, no register attempts will be performed.
114+ register_session_retry_wait: int
115+ Time in seconds to wait between each retry. Number must be non-negative.
116+ register_session_retry_factor: int
117+ Base factor for the wait time between each retry. The wait time
118+ is calculated using exponential function. For factor=1 all wait times
119+ will be equal to `register_session_retry_wait`. For any number of retries,
120+ the last wait time will be equal to `register_session_retry_wait` and for retries>1
121+ the first wait time will be equal to `register_session_retry_wait / factor`.
122+ Number must be equal to or greater than 1. Optimal factor is 10.
106123 auto_mkdir: bool
107124 Whether, when opening a file, the directory containing it should
108125 be created (if it doesn't already exist). This is assumed by pyarrow
@@ -118,6 +135,17 @@ def __init__(
118135 self .temppath = kwargs .pop ("temppath" , "" )
119136 self .share_access = share_access
120137 self .register_session_retries = register_session_retries
138+ if register_session_retry_wait < 0 :
139+ raise ValueError (
140+ "register_session_retry_wait must be a non-negative integer"
141+ )
142+ self .register_session_retry_wait = register_session_retry_wait
143+ if register_session_retry_factor < 1 :
144+ raise ValueError (
145+ "register_session_retry_factor must be a positive "
146+ "integer equal to or greater than 1"
147+ )
148+ self .register_session_retry_factor = register_session_retry_factor
121149 self .auto_mkdir = auto_mkdir
122150 self ._connect ()
123151
@@ -128,7 +156,26 @@ def _port(self):
128156 def _connect (self ):
129157 import time
130158
131- for _ in range (self .register_session_retries ):
159+ if self .register_session_retries <= - 1 :
160+ return
161+
162+ retried_errors = []
163+
164+ wait_time = self .register_session_retry_wait
165+ n_waits = (
166+ self .register_session_retries - 1
167+ ) # -1 = No wait time after the last retry
168+ factor = self .register_session_retry_factor
169+
170+ # Generate wait times for each retry attempt.
171+ # Wait times are calculated using exponential function. For factor=1 all wait times
172+ # will be equal to `wait`. For any number of retries the last wait time will be
173+ # equal to `wait` and for retries>2 the first wait time will be equal to `wait / factor`.
174+ wait_times = iter (
175+ factor ** (n / n_waits - 1 ) * wait_time for n in range (0 , n_waits + 1 )
176+ )
177+
178+ for attempt in range (self .register_session_retries + 1 ):
132179 try :
133180 smbclient .register_session (
134181 self .host ,
@@ -138,9 +185,35 @@ def _connect(self):
138185 encrypt = self .encrypt ,
139186 connection_timeout = self .timeout ,
140187 )
141- break
142- except Exception :
143- time .sleep (0.1 )
188+ return
189+ except (
190+ smbprotocol .exceptions .SMBAuthenticationError ,
191+ smbprotocol .exceptions .LogonFailure ,
192+ ):
193+ # These exceptions should not be repeated, as they clearly indicate
194+ # that the credentials are invalid and not a network issue.
195+ raise
196+ except ValueError as exc :
197+ if re .findall (r"\[Errno -\d+]" , str (exc )):
198+ # This exception is raised by the smbprotocol.transport:Tcp.connect
199+ # and originates from socket.gaierror (OSError). These exceptions might
200+ # be raised due to network instability. We will retry to connect.
201+ retried_errors .append (exc )
202+ else :
203+ # All another ValueError exceptions should be raised, as they are not
204+ # related to network issues.
205+ raise exc
206+ except Exception as exc :
207+ # Save the exception and retry to connect. This except might be dropped
208+ # in the future, once all exceptions suited for retry are identified.
209+ retried_errors .append (exc )
210+
211+ if attempt < self .register_session_retries :
212+ time .sleep (next (wait_times ))
213+
214+ # Raise last exception to inform user about the connection issues.
215+ # Note: Should we use ExceptionGroup to raise all exceptions?
216+ raise retried_errors [- 1 ]
144217
145218 @classmethod
146219 def _strip_protocol (cls , path ):
0 commit comments