1
- use std:: collections:: HashMap ;
1
+ use std:: collections:: { HashMap , VecDeque } ;
2
2
use std:: io:: { Read , Result as IoResult } ;
3
3
4
4
use crate :: stream:: Stream ;
@@ -7,14 +7,24 @@ use crate::unit::Unit;
7
7
use url:: Url ;
8
8
9
9
pub const DEFAULT_HOST : & str = "localhost" ;
10
+ const MAX_IDLE_CONNECTIONS : usize = 100 ;
10
11
11
12
/// Holder of recycled connections.
12
13
///
14
+ /// Invariant: The length of recycle and lru are the same.
15
+ /// Invariant: Each PoolKey exists as a key in recycle, and vice versa.
16
+ /// Invariant: Each PoolKey exists in recycle at most once and lru at most once.
17
+ ///
13
18
/// *Internal API*
14
19
#[ derive( Default , Debug ) ]
15
20
pub ( crate ) struct ConnectionPool {
16
21
// the actual pooled connection. however only one per hostname:port.
17
22
recycle : HashMap < PoolKey , Stream > ,
23
+ // This is used to keep track of which streams to expire when the
24
+ // pool reaches MAX_IDLE_CONNECTIONS. The corresponding PoolKeys for
25
+ // recently used Streams are added to the back of the queue;
26
+ // old streams are removed from the front.
27
+ lru : VecDeque < PoolKey > ,
18
28
}
19
29
20
30
impl ConnectionPool {
@@ -26,7 +36,44 @@ impl ConnectionPool {
26
36
27
37
/// How the unit::connect tries to get a pooled connection.
28
38
pub fn try_get_connection ( & mut self , url : & Url ) -> Option < Stream > {
29
- self . recycle . remove ( & PoolKey :: new ( url) )
39
+ let key = PoolKey :: new ( url) ;
40
+ self . remove ( & key)
41
+ }
42
+
43
+ fn remove ( & mut self , key : & PoolKey ) -> Option < Stream > {
44
+ if !self . recycle . contains_key ( & key) {
45
+ return None ;
46
+ }
47
+ let index = self . lru . iter ( ) . position ( |k| k == key) ;
48
+ assert ! (
49
+ index. is_some( ) ,
50
+ "invariant failed: key existed in recycle but not lru"
51
+ ) ;
52
+ self . lru . remove ( index. unwrap ( ) ) ;
53
+ self . recycle . remove ( & key)
54
+ }
55
+
56
+ fn add ( & mut self , key : PoolKey , stream : Stream ) {
57
+ // If an entry with the same key already exists, remove it.
58
+ // The more recently used stream is likely to live longer.
59
+ self . remove ( & key) ;
60
+ if self . recycle . len ( ) + 1 > MAX_IDLE_CONNECTIONS {
61
+ self . remove_oldest ( ) ;
62
+ }
63
+ self . lru . push_back ( key. clone ( ) ) ;
64
+ self . recycle . insert ( key, stream) ;
65
+ }
66
+
67
+ fn remove_oldest ( & mut self ) {
68
+ if let Some ( key) = self . lru . pop_front ( ) {
69
+ let removed = self . recycle . remove ( & key) ;
70
+ assert ! (
71
+ removed. is_some( ) ,
72
+ "invariant failed: key existed in lru but not in recycle"
73
+ ) ;
74
+ } else {
75
+ panic ! ( "tried to remove oldest but no entries found!" ) ;
76
+ }
30
77
}
31
78
32
79
#[ cfg( test) ]
@@ -35,13 +82,26 @@ impl ConnectionPool {
35
82
}
36
83
}
37
84
38
- #[ derive( Debug , PartialEq , Clone , Eq , Hash ) ]
85
+ #[ derive( PartialEq , Clone , Eq , Hash ) ]
39
86
struct PoolKey {
40
87
scheme : String ,
41
88
hostname : String ,
42
89
port : Option < u16 > ,
43
90
}
44
91
92
+ use std:: fmt;
93
+
94
+ impl fmt:: Debug for PoolKey {
95
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
96
+ f. write_fmt ( format_args ! (
97
+ "{}|{}|{}" ,
98
+ self . scheme,
99
+ self . hostname,
100
+ self . port. unwrap_or( 0 )
101
+ ) )
102
+ }
103
+ }
104
+
45
105
impl PoolKey {
46
106
fn new ( url : & Url ) -> Self {
47
107
let port = url. port_or_known_default ( ) ;
@@ -59,6 +119,52 @@ fn poolkey_new() {
59
119
PoolKey :: new ( & Url :: parse ( "zzz:///example.com" ) . unwrap ( ) ) ;
60
120
}
61
121
122
+ #[ test]
123
+ fn pool_size_limit ( ) {
124
+ assert_eq ! ( MAX_IDLE_CONNECTIONS , 100 ) ;
125
+ let mut pool = ConnectionPool :: new ( ) ;
126
+ let hostnames = ( 0 ..200 ) . map ( |i| format ! ( "{}.example" , i) ) ;
127
+ let poolkeys = hostnames. map ( |hostname| PoolKey {
128
+ scheme : "https" . to_string ( ) ,
129
+ hostname,
130
+ port : Some ( 999 ) ,
131
+ } ) ;
132
+ for key in poolkeys. clone ( ) {
133
+ pool. add ( key, Stream :: Cursor ( std:: io:: Cursor :: new ( vec ! [ ] ) ) ) ;
134
+ }
135
+ assert_eq ! ( pool. len( ) , 100 ) ;
136
+
137
+ for key in poolkeys. skip ( 100 ) {
138
+ let result = pool. remove ( & key) ;
139
+ assert ! ( result. is_some( ) , "expected key was not in pool" ) ;
140
+ }
141
+ }
142
+
143
+ #[ test]
144
+ fn pool_duplicates_limit ( ) {
145
+ // Test inserting duplicates into the pool, and subsequently
146
+ // filling and draining it. The duplicates should evict earlier
147
+ // entries with the same key.
148
+ assert_eq ! ( MAX_IDLE_CONNECTIONS , 100 ) ;
149
+ let mut pool = ConnectionPool :: new ( ) ;
150
+ let hostnames = ( 0 ..100 ) . map ( |i| format ! ( "{}.example" , i) ) ;
151
+ let poolkeys = hostnames. map ( |hostname| PoolKey {
152
+ scheme : "https" . to_string ( ) ,
153
+ hostname,
154
+ port : Some ( 999 ) ,
155
+ } ) ;
156
+ for key in poolkeys. clone ( ) {
157
+ pool. add ( key. clone ( ) , Stream :: Cursor ( std:: io:: Cursor :: new ( vec ! [ ] ) ) ) ;
158
+ pool. add ( key, Stream :: Cursor ( std:: io:: Cursor :: new ( vec ! [ ] ) ) ) ;
159
+ }
160
+ assert_eq ! ( pool. len( ) , 100 ) ;
161
+
162
+ for key in poolkeys {
163
+ let result = pool. remove ( & key) ;
164
+ assert ! ( result. is_some( ) , "expected key was not in pool" ) ;
165
+ }
166
+ }
167
+
62
168
/// Read wrapper that returns the stream to the pool once the
63
169
/// read is exhausted (reached a 0).
64
170
///
@@ -91,7 +197,7 @@ impl<R: Read + Sized + Into<Stream>> PoolReturnRead<R> {
91
197
}
92
198
// insert back into pool
93
199
let key = PoolKey :: new ( & unit. url ) ;
94
- agent. pool ( ) . recycle . insert ( key, stream) ;
200
+ agent. pool ( ) . add ( key, stream) ;
95
201
}
96
202
}
97
203
}
0 commit comments