@@ -28,35 +28,45 @@ mod sync {
2828    use  core:: sync:: atomic:: { AtomicUsize ,  Ordering } ; 
2929
3030    pub  struct  ReadLimiter  { 
31-         pub  limit :  AtomicUsize , 
31+         limit :  AtomicUsize , 
32+         error_on_limit_exceeded :  bool , 
3233    } 
3334
3435    impl  ReadLimiter  { 
35-         pub  fn  new ( limit :  u64 )  -> ReadLimiter  { 
36-             if  limit > core:: usize:: MAX  as  u64  { 
37-                 panic ! ( "traversal_limit_in_words cannot be bigger than core::usize::MAX" ) 
38-             } 
39- 
40-             ReadLimiter  { 
41-                 limit :  AtomicUsize :: new ( limit as  usize ) , 
36+         pub  fn  new ( limit :  Option < usize > )  -> ReadLimiter  { 
37+             match  limit { 
38+                 Some ( value)  => { 
39+                     ReadLimiter  { 
40+                         limit :  AtomicUsize :: new ( value) , 
41+                         error_on_limit_exceeded :  true , 
42+                     } 
43+                 } 
44+                 None  => { 
45+                     ReadLimiter  { 
46+                         limit :  AtomicUsize :: new ( usize:: MAX ) , 
47+                         error_on_limit_exceeded :  false , 
48+                     } 
49+                 } 
4250            } 
4351        } 
4452
4553        #[ inline]  
4654        pub  fn  can_read ( & self ,  amount :  usize )  -> Result < ( ) >  { 
47-             let  cur_limit = self . limit . load ( Ordering :: Relaxed ) ; 
48-             if  cur_limit < amount { 
49-                 return  Err ( Error :: failed ( format ! ( "read limit exceeded" ) ) ) ; 
50-             } 
55+             // We use separate AtomicUsize::load() and AtomicUsize::store() steps, which may 
56+             // result in undercounting reads if multiple threads are reading at the same. 
57+             // That's okay -- a denial of service attack will eventually hit the limit anyway. 
58+             // 
59+             // We could instead do a single fetch_sub() step, but that seems to be slower. 
5160
52-             let  prev_limit = self . limit . fetch_sub ( amount,  Ordering :: Relaxed ) ; 
53-             if  prev_limit < amount { 
54-                 // if the previous limit was lower than the amount we read, the limit has underflowed 
55-                 // and wrapped around so we need to reset it to 0 for next reader to fail 
56-                 self . limit . store ( 0 ,  Ordering :: Relaxed ) ; 
61+             let  current = self . limit . load ( Ordering :: Relaxed ) ; 
62+             if  amount > current && self . error_on_limit_exceeded  { 
5763                return  Err ( Error :: failed ( format ! ( "read limit exceeded" ) ) ) ; 
64+             }  else  { 
65+                 // The common case is current >= amount. Note that we only branch once in that case. 
66+                 // If we combined the fields into an Option<AtomicUsize>, we would 
67+                 // need to branch twice in the common case. 
68+                 self . limit . store ( current. wrapping_sub ( amount) ,  Ordering :: Relaxed ) ; 
5869            } 
59- 
6070            Ok ( ( ) ) 
6171        } 
6272    } 
@@ -71,24 +81,38 @@ mod unsync {
7181    use  core:: cell:: Cell ; 
7282
7383    pub  struct  ReadLimiter  { 
74-         pub  limit :  Cell < u64 > , 
84+         limit :  Cell < usize > , 
85+         error_on_limit_exceeded :  bool , 
7586    } 
7687
7788    impl  ReadLimiter  { 
78-         pub  fn  new ( limit :  u64 )  -> ReadLimiter  { 
79-             ReadLimiter  { 
80-                 limit :  Cell :: new ( limit) , 
89+         pub  fn  new ( limit :  Option < usize > )  -> ReadLimiter  { 
90+             match  limit { 
91+                 Some ( value)  => { 
92+                     ReadLimiter  { 
93+                         limit :  Cell :: new ( value) , 
94+                         error_on_limit_exceeded :  true , 
95+                     } 
96+                 } 
97+                 None  => { 
98+                     ReadLimiter  { 
99+                         limit :  Cell :: new ( usize:: MAX ) , 
100+                         error_on_limit_exceeded :  false , 
101+                     } 
102+                 } 
81103            } 
82104        } 
83105
84106        #[ inline]  
85107        pub  fn  can_read ( & self ,  amount :  usize )  -> Result < ( ) >  { 
86-             let  amount = amount as  u64 ; 
87108            let  current = self . limit . get ( ) ; 
88-             if  amount > current { 
109+             if  amount > current &&  self . error_on_limit_exceeded   { 
89110                Err ( Error :: failed ( format ! ( "read limit exceeded" ) ) ) 
90111            }  else  { 
91-                 self . limit . set ( current - amount) ; 
112+                 // The common case is current >= amount. Note that we only branch once in that case. 
113+                 // If we combined the fields into an Option<Cell<usize>>, we would 
114+                 // need to branch twice in the common case. 
115+                 self . limit . set ( current. wrapping_sub ( amount) ) ; 
92116                Ok ( ( ) ) 
93117            } 
94118        } 
0 commit comments