Skip to content

Commit f103929

Browse files
authored
Merge pull request #3321 from Abduaziz-Forks/capacity-limiter-zero-tokens
Allow `CapacityLimiter` to have zero total_tokens
2 parents c1e1167 + 4ef6ba8 commit f103929

File tree

3 files changed

+85
-6
lines changed

3 files changed

+85
-6
lines changed

newsfragments/3321.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow `trio.CapacityLimiter` to have zero total_tokens.

src/trio/_sync.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,8 @@ def total_tokens(self) -> int | float:
283283
def total_tokens(self, new_total_tokens: int | float) -> None: # noqa: PYI041
284284
if not isinstance(new_total_tokens, int) and new_total_tokens != math.inf:
285285
raise TypeError("total_tokens must be an int or math.inf")
286-
if new_total_tokens < 1:
287-
raise ValueError("total_tokens must be >= 1")
286+
if new_total_tokens < 0:
287+
raise ValueError("total_tokens must be >= 0")
288288
self._total_tokens = new_total_tokens
289289
self._wake_waiters()
290290

src/trio/_tests/test_sync.py

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ async def child() -> None:
5555

5656

5757
async def test_CapacityLimiter() -> None:
58+
assert CapacityLimiter(0).total_tokens == 0
5859
with pytest.raises(TypeError):
5960
CapacityLimiter(1.0)
60-
with pytest.raises(ValueError, match=r"^total_tokens must be >= 1$"):
61+
with pytest.raises(ValueError, match=r"^total_tokens must be >= 0$"):
6162
CapacityLimiter(-1)
6263
c = CapacityLimiter(2)
6364
repr(c) # smoke test
@@ -145,10 +146,10 @@ async def test_CapacityLimiter_change_total_tokens() -> None:
145146
with pytest.raises(TypeError):
146147
c.total_tokens = 1.0
147148

148-
with pytest.raises(ValueError, match=r"^total_tokens must be >= 1$"):
149-
c.total_tokens = 0
149+
with pytest.raises(ValueError, match=r"^total_tokens must be >= 0$"):
150+
c.total_tokens = -1
150151

151-
with pytest.raises(ValueError, match=r"^total_tokens must be >= 1$"):
152+
with pytest.raises(ValueError, match=r"^total_tokens must be >= 0$"):
152153
c.total_tokens = -10
153154

154155
assert c.total_tokens == 2
@@ -190,6 +191,83 @@ async def test_CapacityLimiter_memleak_548() -> None:
190191
assert len(limiter._pending_borrowers) == 0
191192

192193

194+
async def test_CapacityLimiter_zero_limit_tokens() -> None:
195+
c = CapacityLimiter(5)
196+
197+
assert c.total_tokens == 5
198+
199+
async with _core.open_nursery() as nursery:
200+
c.total_tokens = 0
201+
202+
for i in range(5):
203+
nursery.start_soon(c.acquire_on_behalf_of, i)
204+
await wait_all_tasks_blocked()
205+
206+
assert set(c.statistics().borrowers) == set()
207+
assert c.statistics().tasks_waiting == 5
208+
209+
c.total_tokens = 5
210+
211+
assert set(c.statistics().borrowers) == {0, 1, 2, 3, 4}
212+
213+
nursery.start_soon(c.acquire_on_behalf_of, 5)
214+
await wait_all_tasks_blocked()
215+
216+
assert c.statistics().tasks_waiting == 1
217+
218+
for i in range(5):
219+
c.release_on_behalf_of(i)
220+
221+
assert c.statistics().tasks_waiting == 0
222+
c.release_on_behalf_of(5)
223+
224+
# making sure that zero limit capacity limiter doesn't let any tasks through
225+
226+
c.total_tokens = 0
227+
228+
with pytest.raises(_core.WouldBlock):
229+
c.acquire_nowait()
230+
231+
nursery.start_soon(c.acquire_on_behalf_of, 6)
232+
await wait_all_tasks_blocked()
233+
234+
assert c.statistics().tasks_waiting == 1
235+
assert c.statistics().borrowers == []
236+
237+
c.total_tokens = 1
238+
assert c.statistics().tasks_waiting == 0
239+
assert c.statistics().borrowers == [6]
240+
c.release_on_behalf_of(6)
241+
242+
await c.acquire_on_behalf_of(0) # total_tokens is 1
243+
244+
nursery.start_soon(c.acquire_on_behalf_of, 1)
245+
await wait_all_tasks_blocked()
246+
c.total_tokens = 0
247+
248+
assert c.statistics().borrowers == [0]
249+
250+
c.release_on_behalf_of(0)
251+
await wait_all_tasks_blocked()
252+
assert c.statistics().borrowers == []
253+
assert c.statistics().tasks_waiting == 1
254+
255+
c.total_tokens = 1
256+
await wait_all_tasks_blocked()
257+
assert c.statistics().borrowers == [1]
258+
assert c.statistics().tasks_waiting == 0
259+
260+
c.release_on_behalf_of(1)
261+
262+
c.total_tokens = 0
263+
264+
nursery.cancel_scope.cancel()
265+
266+
assert c.total_tokens == 0
267+
assert c.statistics().borrowers == []
268+
assert c._pending_borrowers == {}
269+
270+
193271
async def test_Semaphore() -> None:
194272
with pytest.raises(TypeError):
195273
Semaphore(1.0) # type: ignore[arg-type]

0 commit comments

Comments
 (0)