comparison mercurial/wireprotoframing.py @ 37056:861e9d37e56e

wireproto: buffer output frames when in half duplex mode Previously, when told that a response was ready, the server reactor would instruct the caller to send frames immediately. This was OK as an initial implementation. But it would not work for half-duplex connections where the sender can't receive until all data has been transmitted - such as httplib based clients. In this commit, we teach the reactor that output frames should be buffered until end of input is seen. This required a new event to inform the reactor of end of input. The result from that event will instruct the consumer to send all buffered frames. The HTTP server is buffered by default. This change effectively hides the complexity of buffering within the reactor so that transports need not be concerned about it. This helps keep the transports "dumb" and will make implementing multiple requests-responses per atomic exchange (like an HTTP request) much simpler. Differential Revision: https://phab.mercurial-scm.org/D2860
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 14 Mar 2018 14:01:16 -0700
parents 61393f888dfe
children 2ec1fb9de638
comparison
equal deleted inserted replaced
37055:61393f888dfe 37056:861e9d37e56e
306 of the command to run are given in the data structure. 306 of the command to run are given in the data structure.
307 307
308 wantframe 308 wantframe
309 Indicates that nothing of interest happened and the server is waiting on 309 Indicates that nothing of interest happened and the server is waiting on
310 more frames from the client before anything interesting can be done. 310 more frames from the client before anything interesting can be done.
311
312 noop
313 Indicates no additional action is required.
311 """ 314 """
312 315
313 def __init__(self): 316 def __init__(self, deferoutput=False):
317 """Construct a new server reactor.
318
319 ``deferoutput`` can be used to indicate that no output frames should be
320 instructed to be sent until input has been exhausted. In this mode,
321 events that would normally generate output frames (such as a command
322 response being ready) will instead defer instructing the consumer to
323 send those frames. This is useful for half-duplex transports where the
324 sender cannot receive until all data has been transmitted.
325 """
326 self._deferoutput = deferoutput
314 self._state = 'idle' 327 self._state = 'idle'
328 self._bufferedframegens = []
315 self._activecommand = None 329 self._activecommand = None
316 self._activeargs = None 330 self._activeargs = None
317 self._activedata = None 331 self._activedata = None
318 self._expectingargs = None 332 self._expectingargs = None
319 self._expectingdata = None 333 self._expectingdata = None
342 def onbytesresponseready(self, data): 356 def onbytesresponseready(self, data):
343 """Signal that a bytes response is ready to be sent to the client. 357 """Signal that a bytes response is ready to be sent to the client.
344 358
345 The raw bytes response is passed as an argument. 359 The raw bytes response is passed as an argument.
346 """ 360 """
361 framegen = createbytesresponseframesfrombytes(data)
362
363 if self._deferoutput:
364 self._bufferedframegens.append(framegen)
365 return 'noop', {}
366 else:
367 return 'sendframes', {
368 'framegen': framegen,
369 }
370
371 def oninputeof(self):
372 """Signals that end of input has been received.
373
374 No more frames will be received. All pending activity should be
375 completed.
376 """
377 if not self._deferoutput or not self._bufferedframegens:
378 return 'noop', {}
379
380 # If we buffered all our responses, emit those.
381 def makegen():
382 for gen in self._bufferedframegens:
383 for frame in gen:
384 yield frame
385
347 return 'sendframes', { 386 return 'sendframes', {
348 'framegen': createbytesresponseframesfrombytes(data), 387 'framegen': makegen(),
349 } 388 }
350 389
351 def onapplicationerror(self, msg): 390 def onapplicationerror(self, msg):
352 return 'sendframes', { 391 return 'sendframes', {
353 'framegen': createerrorframe(msg, application=True), 392 'framegen': createerrorframe(msg, application=True),