Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISILONQE-1417] Pike TimeoutError should be actionable #92

Merged
merged 9 commits into from
Oct 6, 2020
2 changes: 1 addition & 1 deletion .github/workflows/test_samba.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
max-parallel: 5
matrix:
python-version: [2.7, 3.6, 3.7, 3.8, 3.9-dev, 3.10-dev]
python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10-dev]

services:
samba:
Expand Down
4 changes: 3 additions & 1 deletion pike/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
# Core API
#
# Authors: Brian Koropoff (brian.koropoff@emc.com)
# Masen Furer (masen.furer@dell.com)
#

"""
Expand Down Expand Up @@ -468,7 +469,8 @@ def __setattr__(self, name, value):
def __str__(self):
return self._str(1)

def _value_str(self, value):
@staticmethod
def _value_str(value):
if isinstance(value, array.array) and value.typecode == 'B':
return '0x' + ''.join('%.2x' % b for b in value)
else:
Expand Down
64 changes: 50 additions & 14 deletions pike/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
# Transport and object model
#
# Authors: Brian Koropoff (brian.koropoff@emc.com)
# Masen Furer (masen.furer@dell.com)
#

"""
Expand Down Expand Up @@ -88,7 +89,38 @@ def loop(timeout=None, count=None):
transport.loop(timeout=timeout, count=count)

class TimeoutError(Exception):
pass
"""Future completion timed out"""
future = None

@classmethod
def with_future(cls, future, *args):
"""
Instantiate TimeoutError from a given future.

:param future: Future that timed out
:param args: passed to Exception.__init__
:return: TimeoutError
"""
ex = cls(*args)
ex.future = future
return ex

def __str__(self):
s = super(TimeoutError, self).__str__()
if self.future is not None:
if self.future.request is not None:
requests = [str(self.future.request)]
if not isinstance(self.future.request, (core.Frame, str, bytes)):
# attempt to recursively str format other iterables
try:
requests = [str(r) for r in self.future.request]
except TypeError:
pass
s += "\nRequest: {}".format("\n".join(requests))
if self.future.interim_response is not None:
s += "\nInterim: {}".format(self.future.interim_response)
return s


class StateError(Exception):
pass
Expand Down Expand Up @@ -202,37 +234,41 @@ def interim(self, response):
"""
self.interim_response = response

def wait(self, timeout=default_timeout):
def wait(self, timeout=None):
"""
Wait for future result to become available.

@param timeout: The time in seconds before giving up and raising TimeoutError
"""
if timeout is None:
timeout = default_timeout
deadline = time.time() + timeout
while self.response is None:
now = time.time()
if now > deadline:
raise TimeoutError('Timed out after %s seconds' % timeout)
raise TimeoutError.with_future(self, 'Timed out after %s seconds' % timeout)
loop(timeout=deadline-now, count=1)

return self

def wait_interim(self, timeout=default_timeout):
def wait_interim(self, timeout=None):
"""
Wait for interim response or actual result to become available.

@param timeout: The time in seconds before giving up and raising TimeoutError
"""
if timeout is None:
timeout = default_timeout
deadline = time.time() + timeout
while self.response is None and self.interim_response is None:
now = time.time()
if now > deadline:
raise TimeoutError('Timed out after %s seconds' % timeout)
raise TimeoutError.with_future(self, 'Timed out after %s seconds' % timeout)
loop(timeout=deadline-now, count=1)

return self

def result(self, timeout=default_timeout):
def result(self, timeout=None):
"""
Return result of future.

Expand Down Expand Up @@ -430,7 +466,7 @@ def oplock_break_future(self, file_id):
@param file_id: The file ID of the oplocked file.
"""

future = Future(None)
future = Future(request=("OplockBreak", file_id))

for smb_res in self._oplock_break_queue[:]:
if smb_res[0].file_id == file_id:
Expand All @@ -454,7 +490,7 @@ def lease_break_future(self, lease_key):
@param lease_key: The lease key for the lease.
"""

future = Future(None)
future = Future(request=("LeaseBreak", core.Frame._value_str(lease_key)))

for smb_res in self._lease_break_queue[:]:
if smb_res[0].lease_key == lease_key:
Expand Down Expand Up @@ -548,7 +584,7 @@ def __init__(self, client, server, port=445):
self._negotiate_request = None
self._negotiate_response = None
self.callbacks = {}
self.connection_future = Future()
self.connection_future = Future(request=(server, port))
self.credits = 0
self.client = client
self.server = server
Expand Down Expand Up @@ -909,10 +945,10 @@ def submit(self, req):
# Cancel by message id, still in send queue
future = [f for f in self._out_queue if f.request.message_id == smb_req.message_id][0]
# Add fake future for cancel since cancel has no response
self._out_queue.append(Future(smb_req))
self._out_queue.append(Future(request=smb_req))
futures.append(future)
else:
future = Future(smb_req)
future = Future(request=smb_req)
self._out_queue.append(future)
futures.append(future)

Expand Down Expand Up @@ -1010,7 +1046,7 @@ def __init__(self, conn, creds=None, bind=None, resume=None,
self.session_id = 0
self.requests = []
self.responses = []
self.session_future = Future()
self.session_future = Future(request=self.requests)
self.interim_future = None

if bind:
Expand Down Expand Up @@ -1324,7 +1360,7 @@ def tree_connect_request(self, path):
return tree_req

def tree_connect_submit(self, tree_req):
tree_future = Future()
tree_future = Future(request=tree_req.parent)
resp_future = self.connection.submit(tree_req.parent.parent)[0]
resp_future.then(lambda f: tree_future.complete(Tree(self.session,
tree_req.path,
Expand Down Expand Up @@ -1458,7 +1494,7 @@ def create_request(
timewarp_req = smb2.TimewarpTokenRequest(create_req)
timewarp_req.timestamp = nttime.NtTime(timewarp)

open_future = Future(None)
open_future = Future(request=create_req.parent)
def finish(f):
with open_future: open_future(
Open(
Expand Down