Mercurial > public > mercurial-scm > hg
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), |