comparison mercurial/wireprotoframing.py @ 37726:0c184ca594bb

wireprotov2: change behavior of error frame Now that we have a leading CBOR map in command response frames to indicate overall command result status, we don't need to use the error response frame to represent command errors. Instead, we can reserve it for protocol and server level errors. And for the special case of a command error that occurred after command response frames were emitted. The code for error handling still needs a ton of work. But we're slowly going in the right direction... Differential Revision: https://phab.mercurial-scm.org/D3386
author Gregory Szorc <gregory.szorc@gmail.com>
date Sat, 14 Apr 2018 15:36:12 -0700
parents 3ea8323d6f95
children 564a3eec6e63
comparison
equal deleted inserted replaced
37725:3ea8323d6f95 37726:0c184ca594bb
85 FLAGS_COMMAND_RESPONSE = { 85 FLAGS_COMMAND_RESPONSE = {
86 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION, 86 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
87 b'eos': FLAG_COMMAND_RESPONSE_EOS, 87 b'eos': FLAG_COMMAND_RESPONSE_EOS,
88 } 88 }
89 89
90 FLAG_ERROR_RESPONSE_PROTOCOL = 0x01
91 FLAG_ERROR_RESPONSE_APPLICATION = 0x02
92
93 FLAGS_ERROR_RESPONSE = {
94 b'protocol': FLAG_ERROR_RESPONSE_PROTOCOL,
95 b'application': FLAG_ERROR_RESPONSE_APPLICATION,
96 }
97
98 # Maps frame types to their available flags. 90 # Maps frame types to their available flags.
99 FRAME_TYPE_FLAGS = { 91 FRAME_TYPE_FLAGS = {
100 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST, 92 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
101 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA, 93 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
102 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE, 94 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
103 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE, 95 FRAME_TYPE_ERROR_RESPONSE: {},
104 FRAME_TYPE_TEXT_OUTPUT: {}, 96 FRAME_TYPE_TEXT_OUTPUT: {},
105 FRAME_TYPE_PROGRESS: {}, 97 FRAME_TYPE_PROGRESS: {},
106 FRAME_TYPE_STREAM_SETTINGS: {}, 98 FRAME_TYPE_STREAM_SETTINGS: {},
107 } 99 }
108 100
392 payload=chunk) 384 payload=chunk)
393 385
394 if done: 386 if done:
395 break 387 break
396 388
397 def createerrorframe(stream, requestid, msg, protocol=False, application=False): 389 def createerrorframe(stream, requestid, msg, errtype):
398 # TODO properly handle frame size limits. 390 # TODO properly handle frame size limits.
399 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE 391 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
400 392
401 flags = 0 393 payload = cbor.dumps({
402 if protocol: 394 b'type': errtype,
403 flags |= FLAG_ERROR_RESPONSE_PROTOCOL 395 b'message': [{b'msg': msg}],
404 if application: 396 }, canonical=True)
405 flags |= FLAG_ERROR_RESPONSE_APPLICATION
406 397
407 yield stream.makeframe(requestid=requestid, 398 yield stream.makeframe(requestid=requestid,
408 typeid=FRAME_TYPE_ERROR_RESPONSE, 399 typeid=FRAME_TYPE_ERROR_RESPONSE,
409 flags=flags, 400 flags=0,
410 payload=msg) 401 payload=payload)
411 402
412 def createtextoutputframe(stream, requestid, atoms, 403 def createtextoutputframe(stream, requestid, atoms,
413 maxframesize=DEFAULT_MAX_FRAME_SIZE): 404 maxframesize=DEFAULT_MAX_FRAME_SIZE):
414 """Create a text output frame to render text to people. 405 """Create a text output frame to render text to people.
415 406
662 653
663 return 'sendframes', { 654 return 'sendframes', {
664 'framegen': makegen(), 655 'framegen': makegen(),
665 } 656 }
666 657
667 def onapplicationerror(self, stream, requestid, msg): 658 def onservererror(self, stream, requestid, msg):
668 ensureserverstream(stream) 659 ensureserverstream(stream)
669 660
670 return 'sendframes', { 661 return 'sendframes', {
671 'framegen': createerrorframe(stream, requestid, msg, 662 'framegen': createerrorframe(stream, requestid, msg,
672 application=True), 663 errtype='server'),
673 } 664 }
674 665
675 def makeoutputstream(self): 666 def makeoutputstream(self):
676 """Create a stream to be used for sending data to the client.""" 667 """Create a stream to be used for sending data to the client."""
677 streamid = self._nextoutgoingstreamid 668 streamid = self._nextoutgoingstreamid
1049 request = self._activerequests[frame.requestid] 1040 request = self._activerequests[frame.requestid]
1050 request.state = 'receiving' 1041 request.state = 'receiving'
1051 1042
1052 handlers = { 1043 handlers = {
1053 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe, 1044 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1045 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1054 } 1046 }
1055 1047
1056 meth = handlers.get(frame.typeid) 1048 meth = handlers.get(frame.typeid)
1057 if not meth: 1049 if not meth:
1058 raise error.ProgrammingError('unhandled frame type: %d' % 1050 raise error.ProgrammingError('unhandled frame type: %d' %
1069 'request': request, 1061 'request': request,
1070 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION, 1062 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1071 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS, 1063 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1072 'data': frame.payload, 1064 'data': frame.payload,
1073 } 1065 }
1066
1067 def _onerrorresponseframe(self, request, frame):
1068 request.state = 'errored'
1069 del self._activerequests[request.requestid]
1070
1071 # The payload should be a CBOR map.
1072 m = cbor.loads(frame.payload)
1073
1074 return 'error', {
1075 'request': request,
1076 'type': m['type'],
1077 'message': m['message'],
1078 }