Skip to content

Commit 3ff5fca

Browse files
authored
Add exception handling for SMBFileSystem init connection errors (#1650)
1 parent a55f8e9 commit 3ff5fca

File tree

1 file changed

+78
-5
lines changed

1 file changed

+78
-5
lines changed

fsspec/implementations/smb.py

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
"""
55

66
import datetime
7+
import re
78
import uuid
89
from stat import S_ISDIR, S_ISLNK
910

1011
import smbclient
12+
import smbprotocol.exceptions
1113

1214
from .. import AbstractFileSystem
1315
from ..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

Comments
 (0)