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