@@ -331,12 +331,40 @@ def test_estimate_memory_footprint_put_small(self):
331331 assert footprint == 2 * 1024 * 1024
332332
333333 def test_estimate_memory_footprint_put_large (self ):
334- """PUT with large file should use 2x buffer size (buffer + ciphertext) ."""
334+ """Large PUT reserves the real internal-part buffer the upload holds ."""
335335 import s3proxy .concurrency as concurrency_module
336-
337- # 100MB file → 16MB footprint (8MB buffer + 8MB ciphertext simultaneously)
338- footprint = concurrency_module .estimate_memory_footprint ("PUT" , 100 * 1024 * 1024 )
339- assert footprint == concurrency_module .MAX_BUFFER_SIZE * 2
336+ from s3proxy import crypto
337+
338+ for mb in (50 , 100 , 512 , 1024 ):
339+ cl = mb * 1024 * 1024
340+ footprint = concurrency_module .estimate_memory_footprint ("PUT" , cl )
341+ assert footprint == crypto .memory_bounded_part_size (cl )
342+
343+ def test_large_uploads_bounded_below_pod_memory (self ):
344+ """Regression for the barman OOM. Two linked invariants:
345+ 1. an internal part never expands beyond the per-client allocation range
346+ (or part numbers collide) -- for ANY client part size, and
347+ 2. the reservation tracks the real buffer, so admitted x footprint never
348+ exceeds the budget (limiter guarantee), and barman-scale parts admit
349+ only ~2 concurrent (the old flat-16MB estimate admitted ~4 -> OOM).
350+ """
351+ import s3proxy .concurrency as concurrency_module
352+ from s3proxy import crypto
353+ from s3proxy .state import MAX_INTERNAL_PARTS_PER_CLIENT
354+
355+ budget = concurrency_module .get_memory_limit ()
356+ for mb in (50 , 128 , 320 , 512 , 1024 , 4096 ):
357+ cl = mb * 1024 * 1024
358+ part = crypto .memory_bounded_part_size (cl )
359+ internal_parts = - (- cl // part )
360+ assert internal_parts <= MAX_INTERNAL_PARTS_PER_CLIENT , "would collide part numbers"
361+ footprint = concurrency_module .estimate_memory_footprint ("PUT" , cl )
362+ # limiter guarantee: total admitted memory never exceeds the budget
363+ assert (budget // footprint ) * footprint <= budget
364+
365+ # barman-scale parts: bounded to ~2 concurrent on the default 64MB budget
366+ footprint_512 = concurrency_module .estimate_memory_footprint ("PUT" , 512 * 1024 * 1024 )
367+ assert budget // footprint_512 <= 2
340368
341369 def test_estimate_memory_footprint_get (self ):
342370 """GET should always use fixed buffer size."""
0 commit comments