Skip to content

Commit 64cbce7

Browse files
authored
Add more exception handled, as observed by restarting webapi while sending a batch (#44)
1 parent 62ac862 commit 64cbce7

File tree

2 files changed

+99
-7
lines changed

2 files changed

+99
-7
lines changed

tests/test_client.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os.path
22
import unittest
3-
import sys
3+
import time
44

55
import future.utils
66

@@ -643,3 +643,76 @@ def _object_contents(idx):
643643
self.assertEqual(404, response.status_code)
644644
else:
645645
self.assertEqual(200, response.status_code)
646+
647+
648+
class TestConnectonErrorRecovery(Test):
649+
650+
def setUp(self):
651+
super(TestConnectonErrorRecovery, self).setUp()
652+
653+
self._object_dir = '/v3io-py-test-connection-error'
654+
self._object_path = self._object_dir + '/object.txt'
655+
656+
self._emd_path = 'some_dir/v3io-py-test-emd'
657+
self._delete_dir(self._emd_path)
658+
659+
# clean up
660+
self._delete_dir(self._object_dir)
661+
662+
@unittest.skip("Manually executed")
663+
def test_object(self):
664+
665+
for i in range(100):
666+
body = 'iteration {}'.format(i)
667+
668+
if i == 10:
669+
self._restart_webapi()
670+
671+
# put contents to some object
672+
self._client.put_object(container=self._container,
673+
path=self._object_path,
674+
body=body)
675+
676+
response = self._client.get_object(container=self._container,
677+
path=self._object_path)
678+
679+
if not isinstance(response.body, str):
680+
response.body = response.body.decode('utf-8')
681+
682+
self.assertEqual(response.body, body)
683+
684+
time.sleep(0.1)
685+
686+
@unittest.skip("Manually executed")
687+
def test_emd_batch(self):
688+
items = {
689+
'bob': {'age': 42, 'feature': 'mustache'},
690+
'linda': {'age': 41, 'feature': 'singing'},
691+
'louise': {'age': 9, 'feature': 'bunny ears'},
692+
'tina': {'age': 14, 'feature': 'butts'},
693+
}
694+
695+
# put the item in a batch
696+
for item_key, item_attributes in future.utils.viewitems(items):
697+
self._client.batch.put_item(container=self._container,
698+
path=v3io.common.helpers.url_join(self._emd_path, item_key),
699+
attributes=item_attributes)
700+
701+
responses = self._client.batch.wait()
702+
for response in responses:
703+
self.assertEqual(200, response.status_code)
704+
705+
self._restart_webapi()
706+
707+
for item_key in items.keys():
708+
self._client.batch.get_item(container=self._container,
709+
path=v3io.common.helpers.url_join(self._emd_path, item_key),
710+
attribute_names=['__size', 'age'])
711+
712+
responses = self._client.batch.wait()
713+
for response in responses:
714+
self.assertEqual(200, response.status_code)
715+
716+
def _restart_webapi(self):
717+
print('Restart webapi now')
718+
time.sleep(15)

v3io/dataplane/transport/httpclient.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ssl
22
import sys
33
import http.client
4+
import socket
45

56
import v3io.dataplane.response
67
import v3io.dataplane.request
@@ -25,12 +26,12 @@ def __init__(self, logger, endpoint=None, max_connections=None, timeout=None, ve
2526

2627
# python 2 and 3 have different exceptions
2728
if sys.version_info[0] >= 3:
28-
self._remote_disconnect_exception = http.client.RemoteDisconnected
29-
self._disconnection_exceptions = (BrokenPipeError, http.client.CannotSendRequest, http.client.RemoteDisconnected)
29+
self._wait_response_exceptions = (http.client.RemoteDisconnected, ConnectionResetError, ConnectionRefusedError)
30+
self._send_request_exceptions = (BrokenPipeError, http.client.CannotSendRequest, http.client.RemoteDisconnected)
3031
self._get_status_and_headers = self._get_status_and_headers_py3
3132
else:
32-
self._remote_disconnect_exception = http.client.BadStatusLine
33-
self._disconnection_exceptions = (http.client.CannotSendRequest, http.client.BadStatusLine)
33+
self._wait_response_exceptions = (http.client.BadStatusLine, socket.error)
34+
self._send_request_exceptions = (http.client.CannotSendRequest, http.client.BadStatusLine)
3435
self._get_status_and_headers = self._get_status_and_headers_py2
3536

3637
def restart(self):
@@ -86,17 +87,30 @@ def wait_response(self, request, raise_for_status=None, num_retries=1):
8687
# return the response
8788
return response
8889

89-
except self._remote_disconnect_exception as e:
90+
except self._wait_response_exceptions as e:
9091
if num_retries == 0:
92+
self._logger.warn_with('Remote disconnected while waiting for response and ran out of retries',
93+
e=type(e),
94+
connection_idx=connection_idx)
95+
9196
raise e
9297

98+
self._logger.info_with('Remote disconnected while waiting for response',
99+
retries_left=num_retries,
100+
connection_idx=connection_idx)
101+
93102
num_retries -= 1
94103

95104
# create a connection
96105
connection = self._recreate_connection_at_index(connection_idx)
97106

98107
# re-send the request on the connection
99108
request = self._send_request_on_connection(request, connection_idx)
109+
except BaseException as e:
110+
self._logger.warn_with('Unhandled exception while waiting for response',
111+
e=type(e),
112+
connection_idx=connection_idx)
113+
raise e
100114

101115
def _send_request_on_connection(self, request, connection_idx):
102116
self.log('Tx',
@@ -110,11 +124,16 @@ def _send_request_on_connection(self, request, connection_idx):
110124

111125
try:
112126
connection.request(request.method, request.path, request.body, request.headers)
113-
except self._disconnection_exceptions:
127+
except self._send_request_exceptions as e:
128+
self._logger.info_with('Disconnected while attempting to send. Recreating connection', e=type(e))
129+
114130
connection = self._recreate_connection_at_index(connection_idx)
115131

116132
# re-request
117133
connection.request(request.method, request.path, request.body, request.headers)
134+
except BaseException as e:
135+
self._logger.warn_with('Unhandled exception while sending request', e=type(e))
136+
raise e
118137

119138
return request
120139

0 commit comments

Comments
 (0)