Skip to content

Commit 6068092

Browse files
committed
Prevent user exception serialization from failing
Currently, if an unhandled exception in user function cannot be serialized properly, the whole worker crashes. Prevent that from ever happening. Fixes: #221
1 parent 659c977 commit 6068092

File tree

6 files changed

+73
-3
lines changed

6 files changed

+73
-3
lines changed

azure/functions_worker/dispatcher.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,18 @@ def worker_id(self):
166166
return self._worker_id
167167

168168
def _serialize_exception(self, exc):
169-
return protos.RpcException(
170-
message=f'{type(exc).__name__}: {exc.args[0]}',
171-
stack_trace=''.join(traceback.format_tb(exc.__traceback__)))
169+
try:
170+
message = f'{type(exc).__name__}: {exc}'
171+
except Exception:
172+
message = (f'Unhandled exception in function. '
173+
f'Could not serialize original exception message.')
174+
175+
try:
176+
stack_trace = ''.join(traceback.format_tb(exc.__traceback__))
177+
except Exception:
178+
stack_trace = ''
179+
180+
return protos.RpcException(message=message, stack_trace=stack_trace)
172181

173182
async def _dispatch_grpc_request(self, request):
174183
content_type = request.WhichOneof('content')
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"scriptFile": "main.py",
3+
"disabled": false,
4+
"bindings": [
5+
{
6+
"type": "httpTrigger",
7+
"direction": "in",
8+
"name": "req"
9+
},
10+
{
11+
"type": "http",
12+
"direction": "out",
13+
"name": "$return"
14+
}
15+
]
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import azure.functions as func
2+
3+
4+
class UnserializableException(Exception):
5+
def __str__(self):
6+
raise RuntimeError('cannot serialize me')
7+
8+
9+
def main(req: func.HttpRequest) -> str:
10+
raise UnserializableException('foo')
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"scriptFile": "main.py",
3+
"disabled": false,
4+
"bindings": [
5+
{
6+
"type": "httpTrigger",
7+
"direction": "in",
8+
"name": "req"
9+
},
10+
{
11+
"type": "http",
12+
"direction": "out",
13+
"name": "$return"
14+
}
15+
]
16+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from urllib.request import urlopen
2+
3+
import azure.functions as func
4+
5+
6+
def main(req: func.HttpRequest) -> str:
7+
image_url = req.params.get('img')
8+
urlopen(image_url).read()

tests/test_http_functions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ def test_unhandled_error(self):
159159
# https://github.com/Azure/azure-functions-host/issues/2706
160160
# self.assertIn('Exception: ZeroDivisionError', r.text)
161161

162+
def test_unhandled_urllib_error(self):
163+
r = self.webhost.request(
164+
'GET', 'unhandled_urllib_error',
165+
params={'img': 'http://example.com/nonexistent.jpg'})
166+
self.assertEqual(r.status_code, 500)
167+
168+
def test_unhandled_unserializable_error(self):
169+
r = self.webhost.request(
170+
'GET', 'unhandled_unserializable_error')
171+
self.assertEqual(r.status_code, 500)
172+
162173
def test_return_route_params(self):
163174
r = self.webhost.request('GET', 'return_route_params/foo/bar')
164175
self.assertEqual(r.status_code, 200)

0 commit comments

Comments
 (0)