@@ -7,7 +7,7 @@ use tokio_io::{AsyncRead, AsyncWrite};
7
7
8
8
use body:: { Body , Payload } ;
9
9
use body:: internal:: FullDataArg ;
10
- use common:: Never ;
10
+ use common:: { Never , YieldNow } ;
11
11
use proto:: { BodyLength , DecodedLength , Conn , Dispatched , MessageHead , RequestHead , RequestLine , ResponseHead } ;
12
12
use super :: Http1Transaction ;
13
13
use service:: Service ;
@@ -18,6 +18,10 @@ pub(crate) struct Dispatcher<D, Bs: Payload, I, T> {
18
18
body_tx : Option < :: body:: Sender > ,
19
19
body_rx : Option < Bs > ,
20
20
is_closing : bool ,
21
+ /// If the poll loop reaches its max spin count, it will yield by notifying
22
+ /// the task immediately. This will cache that `Task`, since it usually is
23
+ /// the same one.
24
+ yield_now : YieldNow ,
21
25
}
22
26
23
27
pub ( crate ) trait Dispatch {
58
62
body_tx : None ,
59
63
body_rx : None ,
60
64
is_closing : false ,
65
+ yield_now : YieldNow :: new ( ) ,
61
66
}
62
67
}
63
68
@@ -98,7 +103,29 @@ where
98
103
fn poll_inner ( & mut self , should_shutdown : bool ) -> Poll < Dispatched , :: Error > {
99
104
T :: update_date ( ) ;
100
105
101
- loop {
106
+ try_ready ! ( self . poll_loop( ) ) ;
107
+
108
+ if self . is_done ( ) {
109
+ if let Some ( pending) = self . conn . pending_upgrade ( ) {
110
+ self . conn . take_error ( ) ?;
111
+ return Ok ( Async :: Ready ( Dispatched :: Upgrade ( pending) ) ) ;
112
+ } else if should_shutdown {
113
+ try_ready ! ( self . conn. shutdown( ) . map_err( :: Error :: new_shutdown) ) ;
114
+ }
115
+ self . conn . take_error ( ) ?;
116
+ Ok ( Async :: Ready ( Dispatched :: Shutdown ) )
117
+ } else {
118
+ Ok ( Async :: NotReady )
119
+ }
120
+ }
121
+
122
+ fn poll_loop ( & mut self ) -> Poll < ( ) , :: Error > {
123
+ // Limit the looping on this connection, in case it is ready far too
124
+ // often, so that other futures don't starve.
125
+ //
126
+ // 16 was chosen arbitrarily, as that is number of pipelined requests
127
+ // benchmarks often use. Perhaps it should be a config option instead.
128
+ for _ in 0 ..16 {
102
129
self . poll_read ( ) ?;
103
130
self . poll_write ( ) ?;
104
131
self . poll_flush ( ) ?;
@@ -112,21 +139,19 @@ where
112
139
// Using this instead of task::current() and notify() inside
113
140
// the Conn is noticeably faster in pipelined benchmarks.
114
141
if !self . conn . wants_read_again ( ) {
115
- break ;
142
+ //break;
143
+ return Ok ( Async :: Ready ( ( ) ) ) ;
116
144
}
117
145
}
118
146
119
- if self . is_done ( ) {
120
- if let Some ( pending) = self . conn . pending_upgrade ( ) {
121
- self . conn . take_error ( ) ?;
122
- return Ok ( Async :: Ready ( Dispatched :: Upgrade ( pending) ) ) ;
123
- } else if should_shutdown {
124
- try_ready ! ( self . conn. shutdown( ) . map_err( :: Error :: new_shutdown) ) ;
125
- }
126
- self . conn . take_error ( ) ?;
127
- Ok ( Async :: Ready ( Dispatched :: Shutdown ) )
128
- } else {
129
- Ok ( Async :: NotReady )
147
+ trace ! ( "poll_loop yielding (self = {:p})" , self ) ;
148
+
149
+ match self . yield_now . poll_yield ( ) {
150
+ Ok ( Async :: NotReady ) => Ok ( Async :: NotReady ) ,
151
+ // maybe with `!` this can be cleaner...
152
+ // but for now, just doing this to eliminate branches
153
+ Ok ( Async :: Ready ( never) ) |
154
+ Err ( never) => match never { }
130
155
}
131
156
}
132
157
0 commit comments