)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"04760640437cb66de2e92ce63040d7527ff01d00","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"62a08dd6_0033744c","updated":"2026-03-13 18:16:43.000000000","message":"recheck\n\nswift-tox-lower-constraints timed out","commit_id":"010be858b3cc2b058a1b98d88e3d104ddd0c6f05"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy"},"change_message_id":"7c105c00cd9e002117f2774fd237c86a47b0ff11","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":12,"id":"d061398b_a24ef98d","updated":"2026-06-30 11:59:58.000000000","message":"I need the clarification about the decrement for current_size before giving a +1. Besides that, everything looks really good.","commit_id":"0fd3c0f57b28b8344d753f2792445e37554da3f7"}],"swift/common/concurrency.py":[{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy"},"change_message_id":"7c105c00cd9e002117f2774fd237c86a47b0ff11","unresolved":true,"context_lines":[{"line_number":263,"context_line":"                    # Wait until notified by put"},{"line_number":264,"context_line":"                    self.available.wait(remaining)"},{"line_number":265,"context_line":""},{"line_number":266,"context_line":"                self.current_size -\u003d 1"},{"line_number":267,"context_line":"                return self.free_items.popleft()"},{"line_number":268,"context_line":""},{"line_number":269,"context_line":"        def put(self, item):"}],"source_content_type":"text/x-python","patch_set":12,"id":"5fec6e3e_07aed184","line":266,"updated":"2026-06-30 11:59:58.000000000","message":"Requirement: I might be wrong, but I think this decrement introduces a but because `current_size` should track total items ever created, not items currently in use. When re-using a pooled item from `free_items` we\u0027re not creating a new item and decrementing makes the pool \"forget\" an item was created. This way Pool can create more items than max_size.\n\nI used Claude to help me reproduce this issue in a SAIO machine. I am adding the test I ran because it might help you understand what I meant.\n\n```python\nimport sys\nsys.path.insert(0, \u0027/opt/stack/swift\u0027)\nimport os\nos.environ[\u0027USE_EVENTLET\u0027] \u003d \u0027false\u0027\n\nimport threading\nimport time\nfrom swift.common.concurrency import Pool\n\nclass TestPool(Pool):\n    created \u003d 0\n    def create(self):\n        TestPool.created +\u003d 1\n        print(f\"  [create] Creating item {TestPool.created}\")\n        return f\"item-{TestPool.created}\"\n\nprint(\"\u003d\u003d\u003d Testing the ACTUAL wait path (where the bug would manifest) \u003d\u003d\u003d\\n\")\n\npool \u003d TestPool(max_size\u003d2)\nresults \u003d []\n\n# Thread A: Get item 1\nitem1 \u003d pool.get()\nprint(f\"Main thread got {item1}: current_size\u003d{pool.current_size}, free_items\u003d{list(pool.free_items)}\")\n\n# Thread B: Get item 2\nitem2 \u003d pool.get()\nprint(f\"Main thread got {item2}: current_size\u003d{pool.current_size}, free_items\u003d{list(pool.free_items)}\")\nprint(f\"Pool now exhausted: current_size\u003d{pool.current_size}, max_size\u003d{pool.max_size}\\n\")\n\n# Thread C: This will WAIT because pool is exhausted\ndef waiter():\n    print(f\"[Thread C] Starting get() - pool is exhausted, will WAIT in while loop...\")\n    print(f\"[Thread C] Before wait: current_size\u003d{pool.current_size}, free_items\u003d{list(pool.free_items)}\")\n    item \u003d pool.get()\n    print(f\"[Thread C] Woke up and got {item}\")\n    print(f\"[Thread C] After get: current_size\u003d{pool.current_size}, free_items\u003d{list(pool.free_items)}\")\n    results.append((\u0027C\u0027, item, pool.current_size))\n\nthread_c \u003d threading.Thread(target\u003dwaiter)\nthread_c.start()\n\n# Give thread C time to enter the wait\ntime.sleep(0.2)\nprint(f\"\\n[Main] Thread C is now waiting. Returning item1 to wake it up...\")\n\n# Return item1 - this will wake Thread C\npool.put(item1)\nprint(f\"[Main] After put({item1}): current_size\u003d{pool.current_size}, free_items\u003d{list(pool.free_items)}\")\n\n# Wait for thread C to complete\nthread_c.join()\n\nprint(f\"\\n[Main] Thread C finished. Final state:\")\nprint(f\"  current_size\u003d{pool.current_size}\")\nprint(f\"  free_items\u003d{list(pool.free_items)}\")\nprint(f\"  Items in circulation: {item2}\")\n\n# The bug check: If current_size was decremented, it would be 1 instead of 2\nif pool.current_size \u003d\u003d 1:\n    print(f\"\\n❌ BUG DETECTED: current_size\u003d{pool.current_size} but 2 items were created!\")\n    print(f\"   The decrement on line 262 caused pool to \u0027forget\u0027 an item was created.\")\n    print(f\"   This allows creating more than max_size items!\")\n\n    # Prove it: try to get another item\n    print(f\"\\n   Attempting to get a 3rd item (should block, but won\u0027t due to bug):\")\n    try:\n        item3 \u003d pool.get(timeout\u003d0.1)\n        print(f\"   ❌ CREATED 3rd item: {item3} (max_size\u003d2, but we created 3 items!)\")\n    except Exception as e:\n        print(f\"   ✅ Correctly blocked: {e}\")\n\nelif pool.current_size \u003d\u003d 2:\n    print(f\"\\n✅ CORRECT: current_size\u003d{pool.current_size} matches the 2 items created.\")\n    print(f\"   The line 262 decrement did NOT execute (or the code was fixed).\")\nelse:\n    print(f\"\\n❓ UNEXPECTED: current_size\u003d{pool.current_size}\")\n\n```\n\nAlso I ran the following script to compare with eventlet behaviour.\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nCompare the threading Pool implementation with eventlet\u0027s original Pool.\n\nThis script tests both implementations with the same scenario to show\nthe difference in current_size handling.\n\"\"\"\n\nimport sys\nimport time\nimport threading\nimport inspect\n\nprint(\"\u003d\" * 80)\nprint(\"COMPARING: Threading Pool vs Eventlet Pool\")\nprint(\"\u003d\" * 80)\n\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n# Part 1: Show eventlet\u0027s original implementation\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nprint(\"\\n### PART 1: Eventlet\u0027s Original Pool.get() Implementation ###\\n\")\n\ntry:\n    import eventlet.pools\n    print(\"eventlet.pools.Pool.get() source code:\")\n    print(\"-\" * 80)\n    source \u003d inspect.getsource(eventlet.pools.Pool.get)\n    print(source)\n    print(\"-\" * 80)\n\n    # Analyze the decrement placement\n    print(\"\\nANALYSIS OF current_size DECREMENT:\")\n    lines \u003d source.split(\u0027\\n\u0027)\n    for i, line in enumerate(lines):\n        if \u0027current_size -\u003d 1\u0027 in line or \u0027current_size -\u003d\u0027 in line:\n            print(f\"  Line {i}: {line.strip()}\")\n            # Check what comes after\n            if i + 1 \u003c len(lines):\n                print(f\"  Next line: {lines[i+1].strip()}\")\n\n    print(\"\\n✅ Key observation:\")\n    print(\"   Eventlet decrements current_size BEFORE waiting (to undo speculative increment)\")\n    print(\"   Then it waits on channel.get() for a recycled item\")\n    print(\"   The decrement does NOT happen after receiving the recycled item\")\n\nexcept ImportError:\n    print(\"❌ eventlet not installed - cannot compare\")\n    sys.exit(1)\n\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n# Part 2: Test eventlet\u0027s Pool behavior\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nprint(\"\\n\\n### PART 2: Testing Eventlet\u0027s Pool Behavior ###\\n\")\n\nclass EventletTestPool(eventlet.pools.Pool):\n    created \u003d 0\n    def create(self):\n        EventletTestPool.created +\u003d 1\n        print(f\"  [eventlet] Creating item {EventletTestPool.created}\")\n        return f\"eventlet-item-{EventletTestPool.created}\"\n\nprint(\"Testing eventlet Pool with the same scenario:\\n\")\n\npool_eventlet \u003d EventletTestPool(max_size\u003d2)\nprint(f\"Initial: current_size\u003d{pool_eventlet.current_size}\")\n\n# Exhaust the pool\nitem1 \u003d pool_eventlet.get()\nprint(f\"Got {item1}: current_size\u003d{pool_eventlet.current_size}\")\n\nitem2 \u003d pool_eventlet.get()\nprint(f\"Got {item2}: current_size\u003d{pool_eventlet.current_size}\")\nprint(f\"Pool exhausted: current_size\u003d{pool_eventlet.current_size}, max_size\u003d{pool_eventlet.max_size}\\n\")\n\n# Spawn a greenthread that will wait\ndef eventlet_waiter():\n    print(f\"[Greenthread] Getting from exhausted pool (will wait)...\")\n    print(f\"[Greenthread] Before get: current_size\u003d{pool_eventlet.current_size}\")\n    item \u003d pool_eventlet.get()\n    print(f\"[Greenthread] Got {item}\")\n    print(f\"[Greenthread] After get: current_size\u003d{pool_eventlet.current_size}\")\n    return item\n\ngt \u003d eventlet.spawn(eventlet_waiter)\neventlet.sleep(0)  # Let greenthread start and block\n\nprint(f\"[Main] Greenthread is waiting. Returning {item1}...\")\npool_eventlet.put(item1)\nprint(f\"[Main] After put: current_size\u003d{pool_eventlet.current_size}\\n\")\n\n# Wait for greenthread to complete\nresult \u003d gt.wait()\n\nprint(f\"Eventlet final state:\")\nprint(f\"  current_size\u003d{pool_eventlet.current_size}\")\nprint(f\"  Total items created: {EventletTestPool.created}\")\n\nif pool_eventlet.current_size \u003d\u003d 2:\n    print(f\"  ✅ current_size correctly tracks total created items (2)\")\nelif pool_eventlet.current_size \u003d\u003d 1:\n    print(f\"  ❌ current_size incorrectly decremented to 1\")\n\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n# Part 3: Test Swift\u0027s threading Pool behavior\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nprint(\"\\n\\n### PART 3: Testing Swift\u0027s Threading Pool Behavior ###\\n\")\n\nsys.path.insert(0, \u0027/opt/stack/swift\u0027)\nimport os\nos.environ[\u0027USE_EVENTLET\u0027] \u003d \u0027false\u0027\n\nfrom swift.common.concurrency import Pool\n\nclass ThreadingTestPool(Pool):\n    created \u003d 0\n    def create(self):\n        ThreadingTestPool.created +\u003d 1\n        print(f\"  [threading] Creating item {ThreadingTestPool.created}\")\n        return f\"threading-item-{ThreadingTestPool.created}\"\n\nprint(\"Testing Swift\u0027s threading Pool with the same scenario:\\n\")\n\npool_threading \u003d ThreadingTestPool(max_size\u003d2)\nprint(f\"Initial: current_size\u003d{pool_threading.current_size}\")\n\n# Exhaust the pool\nitem1 \u003d pool_threading.get()\nprint(f\"Got {item1}: current_size\u003d{pool_threading.current_size}\")\n\nitem2 \u003d pool_threading.get()\nprint(f\"Got {item2}: current_size\u003d{pool_threading.current_size}\")\nprint(f\"Pool exhausted: current_size\u003d{pool_threading.current_size}, max_size\u003d{pool_threading.max_size}\\n\")\n\n# Spawn a thread that will wait\nthreading_result \u003d []\n\ndef threading_waiter():\n    print(f\"[Thread] Getting from exhausted pool (will wait)...\")\n    print(f\"[Thread] Before get: current_size\u003d{pool_threading.current_size}\")\n    item \u003d pool_threading.get()\n    print(f\"[Thread] Got {item}\")\n    print(f\"[Thread] After get: current_size\u003d{pool_threading.current_size}\")\n    threading_result.append(item)\n\nthread \u003d threading.Thread(target\u003dthreading_waiter)\nthread.start()\ntime.sleep(0.2)  # Let thread start and block\n\nprint(f\"[Main] Thread is waiting. Returning {item1}...\")\npool_threading.put(item1)\nprint(f\"[Main] After put: current_size\u003d{pool_threading.current_size}\\n\")\n\n# Wait for thread to complete\nthread.join()\n\nprint(f\"Threading Pool final state:\")\nprint(f\"  current_size\u003d{pool_threading.current_size}\")\nprint(f\"  Total items created: {ThreadingTestPool.created}\")\n\nif pool_threading.current_size \u003d\u003d 2:\n    print(f\"  ✅ current_size correctly tracks total created items (2)\")\nelif pool_threading.current_size \u003d\u003d 1:\n    print(f\"  ❌ current_size incorrectly decremented to 1\")\n\n# Try to create a 3rd item\nprint(f\"\\n  Testing max_size constraint:\")\ntry:\n    from swift.common.concurrency import Timeout\n    item3 \u003d pool_threading.get(timeout\u003d0.1)\n    print(f\"  ❌ Created 3rd item: {item3} (violated max_size\u003d2!)\")\n    print(f\"  current_size\u003d{pool_threading.current_size}\")\n    print(f\"  Total items created: {ThreadingTestPool.created}\")\nexcept Timeout:\n    print(f\"  ✅ Correctly blocked (respects max_size\u003d2)\")\n\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n# Part 4: Side-by-side comparison\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nprint(\"\\n\\n### PART 4: Side-by-Side Comparison ###\\n\")\n\nprint(f\"{\u0027Metric\u0027:\u003c40} {\u0027Eventlet Pool\u0027:\u003c20} {\u0027Threading Pool\u0027:\u003c20}\")\nprint(\"-\" * 80)\nprint(f\"{\u0027After waiter gets recycled item:\u0027:\u003c40}\")\nprint(f\"  {\u0027current_size\u0027:\u003c38} {pool_eventlet.current_size:\u003c20} {pool_threading.current_size:\u003c20}\")\nprint(f\"  {\u0027Total items created\u0027:\u003c38} {EventletTestPool.created:\u003c20} {ThreadingTestPool.created:\u003c20}\")\nprint(f\"  {\u0027Respects max_size?\u0027:\u003c38} {\u0027Yes\u0027 if EventletTestPool.created \u003d\u003d 2 else \u0027No\u0027:\u003c20} {\u0027Yes\u0027 if ThreadingTestPool.created \u003d\u003d 2 else \u0027No\u0027:\u003c20}\")\n\nif pool_eventlet.current_size !\u003d pool_threading.current_size:\n    print(\"\\n⚠️  BEHAVIOR MISMATCH DETECTED!\")\n    print(f\"   Eventlet: current_size\u003d{pool_eventlet.current_size}\")\n    print(f\"   Threading: current_size\u003d{pool_threading.current_size}\")\n\n    if pool_threading.current_size \u003c pool_eventlet.current_size:\n        print(f\"\\n   ❌ Threading Pool has a bug: line 262 decrements at the WRONG time\")\n        print(f\"   This causes the pool to violate max_size constraint!\")\nelse:\n    print(\"\\n✅ Both implementations match!\")\n\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n# Part 5: Explain the difference\n# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nprint(\"\\n\\n### PART 5: Understanding the Difference ###\\n\")\n\nprint(\"EVENTLET\u0027s flow:\")\nprint(\"  1. No free items\")\nprint(\"  2. current_size +\u003d 1  (speculative)\")\nprint(\"  3. if current_size \u003c\u003d max_size: create and return\")\nprint(\"  4. else: current_size -\u003d 1  (undo - we can\u0027t create)\")\nprint(\"  5.       return channel.get()  (wait for recycled item)\")\nprint(\"  → Decrement happens BEFORE waiting, to undo the increment\")\nprint(\"  → After getting recycled item, current_size unchanged\")\n\nprint(\"\\nSWIFT\u0027s flow:\")\nprint(\"  1. No free items\")\nprint(\"  2. if current_size \u003c max_size: increment and create\")\nprint(\"  3. else: while not free_items: wait()\")\nprint(\"  4.       current_size -\u003d 1  ❌ (WRONG - after getting recycled item)\")\nprint(\"  5.       return free_items.popleft()\")\nprint(\"  → Decrement happens AFTER waiting, when returning recycled item\")\nprint(\"  → This makes pool \u0027forget\u0027 an item was created\")\n\nprint(\"\\n\" + \"\u003d\" * 80)\nprint(\"CONCLUSION\")\nprint(\"\u003d\" * 80)\n\nif ThreadingTestPool.created \u003e 2:\n    print(\"\\n❌ BUG CONFIRMED in threading Pool implementation!\")\n    print(f\"   - Created {ThreadingTestPool.created} items despite max_size\u003d2\")\n    print(f\"   - Root cause: Line 262 \u0027self.current_size -\u003d 1\u0027 is in the WRONG place\")\n    print(f\"\")\n    print(f\"   EVENTLET: Decrements BEFORE waiting (to undo speculative increment)\")\n    print(f\"   SWIFT:    Decrements AFTER waiting (incorrectly tracks recycled items)\")\n    print(f\"\")\n    print(f\"   FIX: Remove line 262 entirely. Swift\u0027s implementation doesn\u0027t need it\")\n    print(f\"        because it checks \u0027if current_size \u003c max_size\u0027 before incrementing,\")\n    print(f\"        unlike eventlet which speculatively increments first.\")\nelif EventletTestPool.created \u003d\u003d 2 and ThreadingTestPool.created \u003d\u003d 2:\n    print(\"\\n✅ Both implementations correctly respect max_size constraint\")\n\nprint(\"\\n\" + \"\u003d\" * 80)\n```\n\nI believe removing the decrement is enough to fix this.","commit_id":"0fd3c0f57b28b8344d753f2792445e37554da3f7"}],"swift/common/threadpool.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"bb9ed7220c7b8cffd6a74f33e31dcaf44781be77","unresolved":true,"context_lines":[{"line_number":30,"context_line":"        self.max_size \u003d max_size"},{"line_number":31,"context_line":"        self.current_size \u003d 0"},{"line_number":32,"context_line":"        self.free_items \u003d collections.deque()"},{"line_number":33,"context_line":"        self.lock \u003d threading.Lock()"},{"line_number":34,"context_line":"        self.available \u003d threading.Condition(self.lock)"},{"line_number":35,"context_line":""},{"line_number":36,"context_line":"        if create is not None:"}],"source_content_type":"text/x-python","patch_set":2,"id":"10931ce5_67ba5abe","line":33,"updated":"2026-03-12 20:55:34.000000000","message":"OK, so we specifically *don\u0027t* want an `RLock` -- any particular reason?","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"1a808026a5171d7fedc6385339f60cce0d082dc5","unresolved":true,"context_lines":[{"line_number":30,"context_line":"        self.max_size \u003d max_size"},{"line_number":31,"context_line":"        self.current_size \u003d 0"},{"line_number":32,"context_line":"        self.free_items \u003d collections.deque()"},{"line_number":33,"context_line":"        self.lock \u003d threading.Lock()"},{"line_number":34,"context_line":"        self.available \u003d threading.Condition(self.lock)"},{"line_number":35,"context_line":""},{"line_number":36,"context_line":"        if create is not None:"}],"source_content_type":"text/x-python","patch_set":2,"id":"3331b390_b5d73506","line":33,"in_reply_to":"10931ce5_67ba5abe","updated":"2026-03-13 11:37:07.000000000","message":"No particular reason, and indeed RLock should be used here.","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a96c7ed031b57403b03fbe18bbbc0276e58d0fd9","unresolved":true,"context_lines":[{"line_number":30,"context_line":"        self.max_size \u003d max_size"},{"line_number":31,"context_line":"        self.current_size \u003d 0"},{"line_number":32,"context_line":"        self.free_items \u003d collections.deque()"},{"line_number":33,"context_line":"        self.lock \u003d threading.Lock()"},{"line_number":34,"context_line":"        self.available \u003d threading.Condition(self.lock)"},{"line_number":35,"context_line":""},{"line_number":36,"context_line":"        if create is not None:"}],"source_content_type":"text/x-python","patch_set":2,"id":"6f50bc87_26b3ce3a","line":33,"in_reply_to":"3331b390_b5d73506","updated":"2026-03-13 15:41:14.000000000","message":"OK, cool -- so then I think we can stop tracking `self.lock` entirely, and just let `self.available \u003d threading.Condition()` create its own lock.","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"f8c6d783ac8db508cfbb933e4d6ee619a61bfa13","unresolved":false,"context_lines":[{"line_number":30,"context_line":"        self.max_size \u003d max_size"},{"line_number":31,"context_line":"        self.current_size \u003d 0"},{"line_number":32,"context_line":"        self.free_items \u003d collections.deque()"},{"line_number":33,"context_line":"        self.lock \u003d threading.Lock()"},{"line_number":34,"context_line":"        self.available \u003d threading.Condition(self.lock)"},{"line_number":35,"context_line":""},{"line_number":36,"context_line":"        if create is not None:"}],"source_content_type":"text/x-python","patch_set":2,"id":"99f8b12a_636aadf1","line":33,"in_reply_to":"6f50bc87_26b3ce3a","updated":"2026-06-01 14:05:15.000000000","message":"Done","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"bb9ed7220c7b8cffd6a74f33e31dcaf44781be77","unresolved":true,"context_lines":[{"line_number":53,"context_line":"                    self.current_size -\u003d 1"},{"line_number":54,"context_line":"                    raise"},{"line_number":55,"context_line":"                return created"},{"line_number":56,"context_line":"            self.current_size -\u003d 1  # did not create"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"            while not self.free_items:"},{"line_number":59,"context_line":"                # Wait until notified by put"}],"source_content_type":"text/x-python","patch_set":2,"id":"5f917686_2747bd9e","line":56,"updated":"2026-03-12 20:55:34.000000000","message":"WDYT about\n```\ndiff --git a/swift/common/threadpool.py b/swift/common/threadpool.py\nindex d797255a1..50e598e3f 100644\n--- a/swift/common/threadpool.py\n+++ b/swift/common/threadpool.py\n@@ -46,15 +46,14 @@ class Pool(object):\n             if self.free_items:\n                 return self.free_items.popleft()\n\n-            self.current_size +\u003d 1\n-            if self.current_size \u003c\u003d self.max_size:\n+            if self.current_size \u003c self.max_size:\n+                self.current_size +\u003d 1\n                 try:\n                     created \u003d self.create()\n                 except BaseException:\n                     self.current_size -\u003d 1\n                     raise\n                 return created\n-            self.current_size -\u003d 1  # did not create\n\n             while not self.free_items:\n                 # Wait until notified by put\n```\n? Should be equivalent, and one less spot to track `self.current_size`...","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"1a808026a5171d7fedc6385339f60cce0d082dc5","unresolved":true,"context_lines":[{"line_number":53,"context_line":"                    self.current_size -\u003d 1"},{"line_number":54,"context_line":"                    raise"},{"line_number":55,"context_line":"                return created"},{"line_number":56,"context_line":"            self.current_size -\u003d 1  # did not create"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"            while not self.free_items:"},{"line_number":59,"context_line":"                # Wait until notified by put"}],"source_content_type":"text/x-python","patch_set":2,"id":"fd53a2f0_e14ac842","line":56,"in_reply_to":"5f917686_2747bd9e","updated":"2026-03-13 11:37:07.000000000","message":"Yes, I agree - this is the cleaner approach.","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"bb9ed7220c7b8cffd6a74f33e31dcaf44781be77","unresolved":true,"context_lines":[{"line_number":64,"context_line":""},{"line_number":65,"context_line":"    def put(self, item):"},{"line_number":66,"context_line":"        with self.available:  # acquires the lock"},{"line_number":67,"context_line":"            if self.current_size \u003e self.max_size:"},{"line_number":68,"context_line":"                return"},{"line_number":69,"context_line":""},{"line_number":70,"context_line":"            self.free_items.append(item)"}],"source_content_type":"text/x-python","patch_set":2,"id":"5f7c39fd_d4e307c9","line":67,"updated":"2026-03-12 20:55:34.000000000","message":"Surely this would represent some programming error, yeah? Why *shouldn\u0027t* we raise a `RuntimeError` instead of quietly dropping `item` on the floor?","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"1a808026a5171d7fedc6385339f60cce0d082dc5","unresolved":true,"context_lines":[{"line_number":64,"context_line":""},{"line_number":65,"context_line":"    def put(self, item):"},{"line_number":66,"context_line":"        with self.available:  # acquires the lock"},{"line_number":67,"context_line":"            if self.current_size \u003e self.max_size:"},{"line_number":68,"context_line":"                return"},{"line_number":69,"context_line":""},{"line_number":70,"context_line":"            self.free_items.append(item)"}],"source_content_type":"text/x-python","patch_set":2,"id":"15800995_2e72727a","line":67,"in_reply_to":"5f7c39fd_d4e307c9","updated":"2026-03-13 11:37:07.000000000","message":"You\u0027re right - adding a raise RuntimeError in the follow up.","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"}],"test/unit/common/test_threadpool.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"bb9ed7220c7b8cffd6a74f33e31dcaf44781be77","unresolved":true,"context_lines":[{"line_number":60,"context_line":"        def worker():"},{"line_number":61,"context_line":"            try:"},{"line_number":62,"context_line":"                item \u003d pool.get()"},{"line_number":63,"context_line":"                sleep(0.01)"},{"line_number":64,"context_line":"                results.append(item)"},{"line_number":65,"context_line":"                pool.put(item)"},{"line_number":66,"context_line":"            except Exception as e:"}],"source_content_type":"text/x-python","patch_set":2,"id":"233dc1b1_9f772332","line":63,"updated":"2026-03-12 20:55:34.000000000","message":"Why the sleep?","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"1a808026a5171d7fedc6385339f60cce0d082dc5","unresolved":true,"context_lines":[{"line_number":60,"context_line":"        def worker():"},{"line_number":61,"context_line":"            try:"},{"line_number":62,"context_line":"                item \u003d pool.get()"},{"line_number":63,"context_line":"                sleep(0.01)"},{"line_number":64,"context_line":"                results.append(item)"},{"line_number":65,"context_line":"                pool.put(item)"},{"line_number":66,"context_line":"            except Exception as e:"}],"source_content_type":"text/x-python","patch_set":2,"id":"d72c6a76_b98d67d2","line":63,"in_reply_to":"233dc1b1_9f772332","updated":"2026-03-13 11:37:07.000000000","message":"Uhh, I think that was a leftover when I was testing with slow workers, and at that time not executing the real sleep when seconds\u003d0. Will be removed in next patchset.","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"bb9ed7220c7b8cffd6a74f33e31dcaf44781be77","unresolved":true,"context_lines":[{"line_number":61,"context_line":"            try:"},{"line_number":62,"context_line":"                item \u003d pool.get()"},{"line_number":63,"context_line":"                sleep(0.01)"},{"line_number":64,"context_line":"                results.append(item)"},{"line_number":65,"context_line":"                pool.put(item)"},{"line_number":66,"context_line":"            except Exception as e:"},{"line_number":67,"context_line":"                errors.append(e)"}],"source_content_type":"text/x-python","patch_set":2,"id":"8ef50b30_e4eab63d","line":64,"updated":"2026-03-12 20:55:34.000000000","message":"Oh good, https://docs.python.org/3/library/threadsafety.html#thread-safety-for-list-objects calls out that `append` is atomic; I was worried for a sec that we should be using `queue` here.","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"bb9ed7220c7b8cffd6a74f33e31dcaf44781be77","unresolved":true,"context_lines":[{"line_number":72,"context_line":"        for t in threads:"},{"line_number":73,"context_line":"            t.join(timeout\u003d5)"},{"line_number":74,"context_line":""},{"line_number":75,"context_line":"        self.assertEqual(len(errors), 0, errors)"},{"line_number":76,"context_line":"        self.assertEqual(len(results), 4)"},{"line_number":77,"context_line":""},{"line_number":78,"context_line":"    def test_put_notifies_waiting_get(self):"}],"source_content_type":"text/x-python","patch_set":2,"id":"f9127971_f3adea99","line":75,"updated":"2026-03-12 20:55:34.000000000","message":"Alternatively, `self.assertFalse(errors)`","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"1a808026a5171d7fedc6385339f60cce0d082dc5","unresolved":true,"context_lines":[{"line_number":72,"context_line":"        for t in threads:"},{"line_number":73,"context_line":"            t.join(timeout\u003d5)"},{"line_number":74,"context_line":""},{"line_number":75,"context_line":"        self.assertEqual(len(errors), 0, errors)"},{"line_number":76,"context_line":"        self.assertEqual(len(results), 4)"},{"line_number":77,"context_line":""},{"line_number":78,"context_line":"    def test_put_notifies_waiting_get(self):"}],"source_content_type":"text/x-python","patch_set":2,"id":"63cc7d4b_5514b831","line":75,"in_reply_to":"f9127971_f3adea99","updated":"2026-03-13 11:37:07.000000000","message":"Ack","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"bb9ed7220c7b8cffd6a74f33e31dcaf44781be77","unresolved":true,"context_lines":[{"line_number":73,"context_line":"            t.join(timeout\u003d5)"},{"line_number":74,"context_line":""},{"line_number":75,"context_line":"        self.assertEqual(len(errors), 0, errors)"},{"line_number":76,"context_line":"        self.assertEqual(len(results), 4)"},{"line_number":77,"context_line":""},{"line_number":78,"context_line":"    def test_put_notifies_waiting_get(self):"},{"line_number":79,"context_line":"        pool \u003d self._make_pool(max_size\u003d1)"}],"source_content_type":"text/x-python","patch_set":2,"id":"9f27374e_48e4b338","line":76,"updated":"2026-03-12 20:55:34.000000000","message":"Better as `self.assertEqual(len(results), 4, results)`\n\nThough maybe it won\u0027t matter much in practice; if it\u0027s not 4, there should be some errors, so we would\u0027ve popped on the line above.","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"bb9ed7220c7b8cffd6a74f33e31dcaf44781be77","unresolved":true,"context_lines":[{"line_number":85,"context_line":""},{"line_number":86,"context_line":"        t \u003d threading.Thread(target\u003dgetter)"},{"line_number":87,"context_line":"        t.start()"},{"line_number":88,"context_line":"        sleep(0.25)  # let getter block"},{"line_number":89,"context_line":"        pool.put(first)  # return item, unblock getter"},{"line_number":90,"context_line":"        t.join(timeout\u003d5)"},{"line_number":91,"context_line":"        self.assertEqual(result, [first])"}],"source_content_type":"text/x-python","patch_set":2,"id":"f3d71d8f_d7677ec5","line":88,"updated":"2026-03-12 20:55:34.000000000","message":"Better like\n```\ndiff --git a/test/unit/common/test_threadpool.py b/test/unit/common/test_threadpool.py\nindex 36b180643..309870741 100644\n--- a/test/unit/common/test_threadpool.py\n+++ b/test/unit/common/test_threadpool.py\n@@ -85,7 +85,9 @@ class TestThreadSafePool(unittest.TestCase):\n\n         t \u003d threading.Thread(target\u003dgetter)\n         t.start()\n-        sleep(0.25)  # let getter block\n+        t.join(timeout\u003d0.25)\n+        self.assertTrue(t.is_alive())\n+        self.assertEqual(result, [])\n         pool.put(first)  # return item, unblock getter\n         t.join(timeout\u003d5)\n         self.assertEqual(result, [first])\n```\nMaybe *even better* like\n```\ndiff --git a/test/unit/common/test_threadpool.py b/test/unit/common/test_threadpool.py\nindex 36b180643..009cb5413 100644\n--- a/test/unit/common/test_threadpool.py\n+++ b/test/unit/common/test_threadpool.py\n@@ -79,13 +79,17 @@ class TestThreadSafePool(unittest.TestCase):\n         pool \u003d self._make_pool(max_size\u003d1)\n         first \u003d pool.get()  # exhaust pool\n         result \u003d []\n+        e \u003d threading.Event()\n\n         def getter():\n+            e.set()\n             result.append(pool.get())\n\n         t \u003d threading.Thread(target\u003dgetter)\n         t.start()\n-        sleep(0.25)  # let getter block\n+        e.wait()\n+        self.assertTrue(t.is_alive())\n+        self.assertEqual(result, [])\n         pool.put(first)  # return item, unblock getter\n         t.join(timeout\u003d5)\n         self.assertEqual(result, [first])\n```","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"1a808026a5171d7fedc6385339f60cce0d082dc5","unresolved":true,"context_lines":[{"line_number":85,"context_line":""},{"line_number":86,"context_line":"        t \u003d threading.Thread(target\u003dgetter)"},{"line_number":87,"context_line":"        t.start()"},{"line_number":88,"context_line":"        sleep(0.25)  # let getter block"},{"line_number":89,"context_line":"        pool.put(first)  # return item, unblock getter"},{"line_number":90,"context_line":"        t.join(timeout\u003d5)"},{"line_number":91,"context_line":"        self.assertEqual(result, [first])"}],"source_content_type":"text/x-python","patch_set":2,"id":"04096102_71c0388c","line":88,"in_reply_to":"f3d71d8f_d7677ec5","updated":"2026-03-13 11:37:07.000000000","message":"Good idea, will include this in the next patchset.","commit_id":"26c8c297fd9d9621d0a8198691c7a9ef7524ce6d"}]}
