Files
troupe-standup/wizard-work/beam/test_bulkhead.py
T

127 lines
3.7 KiB
Python

import threading
import time
import unittest
from bulkhead import Bulkhead, BulkheadFull
class TestBulkheadBasic(unittest.TestCase):
def test_invalid_args(self):
with self.assertRaises(ValueError):
Bulkhead(0)
with self.assertRaises(ValueError):
Bulkhead(-1)
with self.assertRaises(ValueError):
Bulkhead(1, -1)
with self.assertRaises(ValueError):
Bulkhead(1, max_queue=-1)
def test_try_acquire_fast_path(self):
bh = Bulkhead(2)
h1 = bh.try_acquire()
h2 = bh.try_acquire()
self.assertIsNotNone(h1)
self.assertIsNotNone(h2)
self.assertIsNone(bh.try_acquire())
self.assertEqual(bh.active(), 2)
h1.release()
h2.release()
self.assertEqual(bh.active(), 0)
def test_acquire_release_context(self):
bh = Bulkhead(1)
with bh.slot():
self.assertEqual(bh.active(), 1)
self.assertIsNone(bh.try_acquire())
self.assertEqual(bh.active(), 0)
def test_run(self):
bh = Bulkhead(1)
self.assertEqual(bh.run(lambda x: x + 1, 4), 5)
def test_queue_full_raises(self):
bh = Bulkhead(1, max_queue=0)
h = bh.acquire()
with self.assertRaises(BulkheadFull):
bh.acquire()
h.release()
def test_queued_counter(self):
bh = Bulkhead(1, max_queue=2)
h = bh.acquire()
# two waiters queue
results = []
def worker(i):
try:
hh = bh.acquire(timeout=5)
results.append(("ok", i))
time.sleep(0.2)
hh.release()
except Exception as e:
results.append(("err", i, type(e).__name__))
ts = [threading.Thread(target=worker, args=(i,)) for i in range(2)]
for t in ts: t.start()
time.sleep(0.1)
self.assertEqual(bh.queued(), 2)
h.release()
for t in ts: t.join(timeout=5)
self.assertEqual(bh.active(), 0)
self.assertEqual(len([r for r in results if r[0] == "ok"]), 2)
def test_timeout(self):
bh = Bulkhead(1, max_queue=1)
h = bh.acquire()
t0 = time.monotonic()
with self.assertRaises(TimeoutError):
bh.acquire(timeout=0.1)
elapsed = time.monotonic() - t0
self.assertLess(elapsed, 1.0)
# queued counter restored
self.assertEqual(bh.queued(), 0)
h.release()
def test_timeout_negative(self):
bh = Bulkhead(1)
with self.assertRaises(ValueError):
bh.acquire(timeout=-1)
def test_double_release_idempotent(self):
bh = Bulkhead(1)
h = bh.acquire()
h.release()
h.release()
self.assertEqual(bh.active(), 0)
# slot reusable
h2 = bh.try_acquire()
self.assertIsNotNone(h2)
h2.release()
def test_concurrent_stress(self):
bh = Bulkhead(4, max_queue=100)
active_peak = [0]
lock = threading.Lock()
def work():
with bh.slot():
with lock:
# approximate peak via active()
pass
time.sleep(0.01)
ts = [threading.Thread(target=work) for _ in range(50)]
for t in ts: t.start()
for t in ts: t.join(timeout=10)
self.assertEqual(bh.active(), 0)
self.assertEqual(bh.queued(), 0)
def test_handle_context_exit_releases_on_exception(self):
bh = Bulkhead(1)
try:
with bh.slot():
raise ValueError("boom")
except ValueError:
pass
self.assertEqual(bh.active(), 0)
if __name__ == "__main__":
unittest.main()