3.2 KB134 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import {RateLimiter} from '../RateLimiter';
9import {nextTick} from '../testUtils';
10import {defer} from '../utils';
11
12describe('RateLimiter', () => {
13 it('immediately invokes if less than max simultaneous requests are running', () => {
14 const d1 = defer();
15 const d2 = defer();
16 const d3 = defer();
17 const rateLimiter = new RateLimiter(3);
18 let ran1 = false;
19 let ran2 = false;
20 let ran3 = false;
21 rateLimiter.enqueueRun(async () => {
22 ran1 = true;
23 await d1.promise;
24 });
25 rateLimiter.enqueueRun(async () => {
26 ran2 = true;
27 await d2.promise;
28 });
29 rateLimiter.enqueueRun(async () => {
30 ran3 = true;
31 await d3.promise;
32 });
33 expect(ran1).toBe(true);
34 expect(ran2).toBe(true);
35 expect(ran3).toBe(true);
36 });
37
38 it('queues requests over max simultaneous until a previous task finishes', async () => {
39 const d1 = defer();
40 const d2 = defer();
41 const rateLimiter = new RateLimiter(2);
42 rateLimiter.enqueueRun(() => d1.promise);
43 rateLimiter.enqueueRun(() => d2.promise);
44
45 let hasId3Resolved = false;
46 rateLimiter
47 .enqueueRun(() => Promise.resolve())
48 .then(() => {
49 hasId3Resolved = true;
50 });
51 expect(hasId3Resolved).toBe(false);
52
53 d2.resolve(undefined);
54 await nextTick();
55 expect(hasId3Resolved).toBe(true);
56 });
57
58 it('can be used as a lock with concurrency limit 1', async () => {
59 const d1 = defer();
60 const d2 = defer();
61 const d3 = defer();
62 const rateLimiter = new RateLimiter(1);
63 let ran1 = false;
64 let ran2 = false;
65 let ran3 = false;
66 rateLimiter.enqueueRun(async () => {
67 await d1.promise;
68 ran1 = true;
69 });
70 rateLimiter.enqueueRun(async () => {
71 await d2.promise;
72 ran2 = true;
73 });
74 rateLimiter.enqueueRun(async () => {
75 await d3.promise;
76 ran3 = true;
77 });
78
79 expect(ran1).toBe(false);
80 expect(ran2).toBe(false);
81 expect(ran3).toBe(false);
82
83 d1.resolve(undefined);
84 await nextTick();
85
86 expect(ran1).toBe(true);
87 expect(ran2).toBe(false);
88 expect(ran3).toBe(false);
89
90 d2.resolve(undefined);
91 await nextTick();
92
93 expect(ran1).toBe(true);
94 expect(ran2).toBe(true);
95 expect(ran3).toBe(false);
96
97 d3.resolve(undefined);
98 await nextTick();
99
100 expect(ran1).toBe(true);
101 expect(ran2).toBe(true);
102 expect(ran3).toBe(true);
103 });
104
105 it('Handles async work that rejects', async () => {
106 const d1 = defer();
107 const d2 = defer();
108 const rateLimiter = new RateLimiter(2);
109 rateLimiter.enqueueRun(() => d1.promise);
110 let sawError = false;
111 rateLimiter
112 .enqueueRun(async () => {
113 await d2.promise;
114 throw new Error();
115 })
116 .catch(() => {
117 sawError = true;
118 });
119
120 let hasId3Resolved = false;
121 rateLimiter
122 .enqueueRun(() => Promise.resolve())
123 .then(() => {
124 hasId3Resolved = true;
125 });
126 expect(hasId3Resolved).toBe(false);
127
128 d2.resolve(undefined);
129 await nextTick();
130 expect(hasId3Resolved).toBe(true);
131 expect(sawError).toBe(true);
132 });
133});
134