Skip to content

Commit f4ac8e1

Browse files
committed
test: pass action name to dirty client and stabilize after TTOU spam
- /unlimited and /limited handlers passed the data dict where the dirty client expected the action (method) name, surfacing as a 500 from getattr(self, action) on the dirty worker. Pass 'process' as the action so the call routes to DirtyApp.process(data). - TestUnlimitedApps now bumps worker count via TTIN and polls both apps for readiness before each test. The preceding TTOU-spam test pins the worker count at the LimitedTask floor (2) and the arbiter takes a moment to rebind apps to the surviving workers; the previous tests raced that rebind and saw 'No workers available'.
1 parent 54d38af commit f4ac8e1

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

tests/docker/dirty_ttin_ttou/app.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ def app(environ, start_response):
4545
start_response('200 OK', [('Content-Type', 'text/plain')])
4646
return [b'OK']
4747

48+
# client.execute(app_path, action, *args, **kwargs) — action is the
49+
# method name on the DirtyApp. The original fixture passed the data
50+
# dict where ``action`` belongs, which surfaced as a 500 from
51+
# ``getattr(self, action)`` on the dirty worker.
4852
if path == '/unlimited':
4953
try:
5054
client = get_dirty_client()
51-
result = client.execute('app:UnlimitedTask', {'test': 'data'})
55+
result = client.execute('app:UnlimitedTask', 'process', {'test': 'data'})
5256
start_response('200 OK', [('Content-Type', 'application/json')])
5357
return [json.dumps(result).encode()]
5458
except Exception as e:
@@ -59,7 +63,7 @@ def app(environ, start_response):
5963
if path == '/limited':
6064
try:
6165
client = get_dirty_client()
62-
result = client.execute('app:LimitedTask', {'test': 'data'})
66+
result = client.execute('app:LimitedTask', 'process', {'test': 'data'})
6367
start_response('200 OK', [('Content-Type', 'application/json')])
6468
return [json.dumps(result).encode()]
6569
except Exception as e:

tests/docker/dirty_ttin_ttou/test_ttin_ttou_docker.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,30 @@ def send_signal_to_dirty_arbiter(sig):
103103
)
104104

105105

106+
def wait_for_apps_ready(*paths, timeout=10):
107+
"""Poll the given app endpoints until each returns 200.
108+
109+
The dirty arbiter rebalances apps across workers asynchronously after
110+
TTIN/TTOU signals. Tests that care about app availability — rather
111+
than worker counts — should call this between scaling and the request
112+
so they don't race the rebalance.
113+
"""
114+
deadline = time.time() + timeout
115+
pending = list(paths)
116+
while pending and time.time() < deadline:
117+
for path in list(pending):
118+
try:
119+
resp = requests.get(f"{BASE_URL}{path}", timeout=2)
120+
if resp.status_code == 200:
121+
pending.remove(path)
122+
except requests.RequestException:
123+
pass
124+
if pending:
125+
time.sleep(0.5)
126+
if pending:
127+
raise RuntimeError(f"Apps did not become ready: {pending}")
128+
129+
106130
class TestTTINSignal:
107131
"""Test SIGTTIN increases dirty workers."""
108132

@@ -162,14 +186,24 @@ def test_ttou_respects_minimum(self, docker_services):
162186
class TestUnlimitedApps:
163187
"""Test apps with worker_count=None work correctly."""
164188

165-
def test_unlimited_app_works(self, docker_services):
189+
@pytest.fixture(autouse=True)
190+
def _ready(self, docker_services):
191+
# The TTOU-spam test before this class may leave the arbiter at
192+
# the floor (2 workers). Bump the count back up so LimitedTask
193+
# has spare capacity, then wait for both apps to be reachable.
194+
for _ in range(2):
195+
send_signal_to_dirty_arbiter("TTIN")
196+
time.sleep(0.5)
197+
wait_for_apps_ready("/unlimited", "/limited", timeout=30)
198+
199+
def test_unlimited_app_works(self):
166200
"""UnlimitedTask should work."""
167201
resp = requests.get(f"{BASE_URL}/unlimited", timeout=10)
168202
assert resp.status_code == 200
169203
data = resp.json()
170204
assert data["task"] == "unlimited"
171205

172-
def test_limited_app_works(self, docker_services):
206+
def test_limited_app_works(self):
173207
"""LimitedTask should work."""
174208
resp = requests.get(f"{BASE_URL}/limited", timeout=10)
175209
assert resp.status_code == 200

0 commit comments

Comments
 (0)