12
12
namespace Symfony \Component \RateLimiter \Policy ;
13
13
14
14
use Symfony \Component \Lock \LockInterface ;
15
- use Symfony \Component \RateLimiter \Exception \ReserveNotSupportedException ;
16
15
use Symfony \Component \RateLimiter \LimiterInterface ;
16
+ use Symfony \Component \RateLimiter \Exception \MaxWaitDurationExceededException ;
17
17
use Symfony \Component \RateLimiter \RateLimit ;
18
18
use Symfony \Component \RateLimiter \Reservation ;
19
19
use Symfony \Component \RateLimiter \Storage \StorageInterface ;
@@ -48,11 +48,10 @@ public function __construct(string $id, int $limit, \DateInterval $interval, Sto
48
48
49
49
public function reserve (int $ tokens = 1 , float $ maxTime = null ): Reservation
50
50
{
51
- throw new ReserveNotSupportedException (__CLASS__ );
52
- }
51
+ if ($ tokens > $ this ->limit ) {
52
+ throw new \InvalidArgumentException (sprintf ('Cannot reserve more tokens (%d) than the size of the rate limiter (%d). ' , $ tokens , $ this ->limit ));
53
+ }
53
54
54
- public function consume (int $ tokens = 1 ): RateLimit
55
- {
56
55
$ this ->lock ?->acquire(true );
57
56
58
57
try {
@@ -63,22 +62,43 @@ public function consume(int $tokens = 1): RateLimit
63
62
$ window = SlidingWindow::createFromPreviousWindow ($ window , $ this ->interval );
64
63
}
65
64
65
+ $ now = microtime (true );
66
66
$ hitCount = $ window ->getHitCount ();
67
67
$ availableTokens = $ this ->getAvailableTokens ($ hitCount );
68
- if ($ availableTokens < $ tokens ) {
69
- return new RateLimit ($ availableTokens , $ window ->getRetryAfter (), false , $ this ->limit );
70
- }
68
+ if ($ availableTokens >= $ tokens ) {
69
+ $ window ->add ($ tokens );
71
70
72
- $ window ->add ($ tokens );
71
+ $ reservation = new Reservation ($ now , new RateLimit ($ this ->getAvailableTokens ($ window ->getHitCount ()), \DateTimeImmutable::createFromFormat ('U ' , floor ($ now )), true , $ this ->limit ));
72
+ } else {
73
+ $ waitDuration = $ window ->calculateTimeForTokens ($ this ->limit , max (1 , $ tokens ));
74
+
75
+ if (null !== $ maxTime && $ waitDuration > $ maxTime ) {
76
+ // process needs to wait longer than set interval
77
+ throw new MaxWaitDurationExceededException (sprintf ('The rate limiter wait time ("%d" seconds) is longer than the provided maximum time ("%d" seconds). ' , $ waitDuration , $ maxTime ), new RateLimit ($ this ->getAvailableTokens ($ window ->getHitCount ()), \DateTimeImmutable::createFromFormat ('U ' , floor ($ now + $ waitDuration )), false , $ this ->limit ));
78
+ }
79
+
80
+ $ window ->add ($ tokens );
81
+
82
+ $ reservation = new Reservation ($ now + $ waitDuration , new RateLimit ($ this ->getAvailableTokens ($ window ->getHitCount ()), \DateTimeImmutable::createFromFormat ('U ' , floor ($ now + $ waitDuration )), false , $ this ->limit ));
83
+ }
73
84
74
85
if (0 < $ tokens ) {
75
86
$ this ->storage ->save ($ window );
76
87
}
77
-
78
- return new RateLimit ($ this ->getAvailableTokens ($ window ->getHitCount ()), $ window ->getRetryAfter (), true , $ this ->limit );
79
88
} finally {
80
89
$ this ->lock ?->release();
81
90
}
91
+
92
+ return $ reservation ;
93
+ }
94
+
95
+ public function consume (int $ tokens = 1 ): RateLimit
96
+ {
97
+ try {
98
+ return $ this ->reserve ($ tokens , 0 )->getRateLimit ();
99
+ } catch (MaxWaitDurationExceededException $ e ) {
100
+ return $ e ->getRateLimit ();
101
+ }
82
102
}
83
103
84
104
private function getAvailableTokens (int $ hitCount ): int
0 commit comments