@@ -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