Skip to content

Commit f81e5e4

Browse files
additional diagnostics info
1 parent fbd1db2 commit f81e5e4

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

tests/integration/test_async_lease_extension.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,20 @@ def setUpClass(cls):
177177
cls._task_handler.start_processes()
178178
time.sleep(3) # let workers start (once, not per test)
179179

180+
# Confirm every worker this module needs was actually started with a
181+
# live process. If one silently fails to start, its task sits in
182+
# SCHEDULED forever and surfaces later as an opaque "workflow still
183+
# RUNNING" assertion; fail loudly here naming the exact worker instead.
184+
started = {}
185+
for worker, process in zip(cls._task_handler.workers,
186+
cls._task_handler.task_runner_processes):
187+
started[worker.get_task_definition_name()] = process.is_alive()
188+
logger.info("Started workers (name -> alive): %s", started)
189+
missing = [n for n in cls.WORKER_TASK_NAMES
190+
if not started.get(n, False)]
191+
assert not missing, (
192+
f"workers not started/alive: {missing}; started={started}")
193+
180194
@classmethod
181195
def tearDownClass(cls):
182196
handler = getattr(cls, '_task_handler', None)
@@ -219,6 +233,37 @@ def _wait_for_workflow(self, wf_id, timeout_seconds=90):
219233
time.sleep(1)
220234
return self.workflow_client.get_workflow(wf_id, include_tasks=True)
221235

236+
def _dump_workflow_diagnostics(self, wf):
237+
"""Print task statuses plus server-side poll data / queue size so a
238+
non-terminal workflow shows *why* (e.g. a task stuck in SCHEDULED with
239+
no poller = the worker isn't consuming it), rather than only a bare
240+
status assertion. Poll data is server-side, so it survives regardless
241+
of worker child-process log capture.
242+
"""
243+
from conductor.client.orkes.orkes_task_client import OrkesTaskClient
244+
task_client = OrkesTaskClient(self.config)
245+
246+
# Worker processes can die *after* setUpClass; report current liveness
247+
# so we can tell "worker crashed mid-suite" apart from "worker alive but
248+
# not polling / server not timing out".
249+
handler = getattr(self, '_task_handler', None)
250+
if handler is not None:
251+
alive = {}
252+
for worker, process in zip(handler.workers, handler.task_runner_processes):
253+
alive[worker.get_task_definition_name()] = process.is_alive()
254+
print(f" [diag] worker liveness: {alive}")
255+
256+
for task in (getattr(wf, 'tasks', None) or []):
257+
print(f" [diag] task {task.task_def_name}: {task.status}")
258+
try:
259+
queue_size = task_client.get_queue_size_for_task(task.task_def_name)
260+
poll_data = task_client.get_task_poll_data(task.task_def_name) or []
261+
pollers = [(p.worker_id, p.domain, p.last_poll_time) for p in poll_data]
262+
print(f" [diag] {task.task_def_name}: "
263+
f"queue_size={queue_size} pollers={pollers}")
264+
except Exception as e: # diagnostics must never mask the real failure
265+
print(f" [diag] {task.task_def_name}: failed to fetch poll data: {e!r}")
266+
222267
# -- Tests ----------------------------------------------------------------
223268

224269
def test_01_async_with_heartbeat_completes(self):
@@ -268,6 +313,9 @@ def test_02_async_without_heartbeat_times_out(self):
268313
for task in (wf.tasks or []):
269314
print(f" Task {task.task_def_name}: {task.status}")
270315

316+
if wf.status not in ('FAILED', 'TIMED_OUT'):
317+
self._dump_workflow_diagnostics(wf)
318+
271319
self.assertIn(wf.status, ('FAILED', 'TIMED_OUT'),
272320
f"Workflow should FAIL/TIMEOUT without heartbeat, got {wf.status}")
273321

0 commit comments

Comments
 (0)