hello!
Description
We have reproduced an intermittent hang in qase-pytest when uploading attachments.
The tests finish normally, but one pytest-xdist worker remains active indefinitely. Pytest cannot complete and is eventually terminated by the CI timeout.
The issue occurs when an attachment upload starts but the HTTP request neither returns a response nor raises an exception.
Environment
qase-pytest==9.0.0
qase-python-commons==5.1.4
qase-api-client==2.0.14
qase-api-v2-client==2.0.8
pytest==9.0.3
pytest-xdist==3.8.0
urllib3==2.7.0
- Python
3.13.14
- Four xdist workers
- Result batch size:
20 - 50
- Custom Qase API host/proxy
Configuration
QASE_TESTOPS_API_TIMEOUT=30
QASE_TESTOPS_API_RETRIES=3
QASE_TESTOPS_API_RETRY_BACKOFF=2
Actual behavior
The Qase debug log contains:
- 2,519 attachment upload starts
- 2,518 successful attachment uploads
- No error for the remaining attachment upload
- Three completed Qase workers out of four
- No Qase run completion event
The unmatched operation is:
[Qase][debug] Uploading batch 1/1 with 6 file(s) for project AA
There is no corresponding message:
Successfully uploaded batch ...
or:
Error uploading batch ...
The affected reporter thread remains active indefinitely.
The configured result batch size was 20. A total of 2,430 results were created, but only 2,410 reached the API v2 result submission stage. Therefore, the complete batch containing the stalled attachment upload was never submitted.
API v2 retries work correctly
During the same run, five API v2 result submission requests failed with:
ConnectionResetError: Connection reset by peer
All five requests were retried successfully.
There were:
- 128 API v2 submission attempts
- 5 retry events
- 123 successful logical batch submissions
- No failures after retry exhaustion
The hang is therefore related to attachment upload through the API v1 client, rather than API v2 result submission.
Technical analysis
ApiV2Client.send_results() prepares all results before submitting the batch:
results_to_send = [
self._prepare_result(project_code, result)
for result in results
]
During result preparation, attachments are uploaded through _upload_attachment():
attach_id = self._upload_attachment(
project_code,
attachments_to_upload
)
The attachment request is made through the API v1 client:
response = attach_api.upload_attachment(
project_code,
file=files_for_upload
)
The generated AttachmentsApi.upload_attachment() method supports the _request_timeout argument, but qase-python-commons does not pass it.
The configured API timeout is passed to API v2 result submission:
api_results.create_results_v2(
...,
_request_timeout=self.config.testops.api.timeout
)
However, it is not passed to API v1 attachment uploads.
If the attachment connection remains open without returning data, the call blocks indefinitely. Since no exception is raised, the outer send_with_retry() wrapper cannot perform a retry.
The reporter thread remains active, and complete_worker() waits indefinitely:
while self.count_running_threads > 0:
time.sleep(DEFAULT_THREAD_POLL_INTERVAL)
Consequently, the xdist worker and the entire pytest process cannot finish.
There is also a related retry issue: _upload_attachment() catches attachment exceptions internally and continues. Therefore, attachment failures that do raise an exception are not handled by the outer retry mechanism.
Expected behavior
Attachment uploads should:
- Use the configured API timeout
- Have bounded retry behavior for retryable transport failures
- Never block a reporter thread indefinitely
- Allow results to be submitted even if an attachment cannot be uploaded
- Allow all pytest-xdist workers to exit normally
Suggested fix
Pass the configured timeout to the attachment API call:
response = attach_api.upload_attachment(
project_code,
file=files_for_upload,
_request_timeout=self.config.testops.api.timeout
)
Attachment uploads should also have a separate bounded retry mechanism.
After attachment retry exhaustion, the reporter should log the failure, omit the affected attachment, and continue submitting the result batch.
Acceptance criteria
QASE_TESTOPS_API_TIMEOUT is applied to API v1 attachment uploads
- A stalled attachment upload is interrupted after the configured timeout
- Retryable attachment failures are retried a bounded number of times
- Attachment retry exhaustion does not prevent result submission
- A reporter thread cannot remain active indefinitely because of an attachment request
- All pytest-xdist workers exit after an attachment timeout
hello!
Description
We have reproduced an intermittent hang in
qase-pytestwhen uploading attachments.The tests finish normally, but one
pytest-xdistworker remains active indefinitely. Pytest cannot complete and is eventually terminated by the CI timeout.The issue occurs when an attachment upload starts but the HTTP request neither returns a response nor raises an exception.
Environment
qase-pytest==9.0.0qase-python-commons==5.1.4qase-api-client==2.0.14qase-api-v2-client==2.0.8pytest==9.0.3pytest-xdist==3.8.0urllib3==2.7.03.13.1420-50Configuration
Actual behavior
The Qase debug log contains:
The unmatched operation is:
There is no corresponding message:
or:
The affected reporter thread remains active indefinitely.
The configured result batch size was 20. A total of 2,430 results were created, but only 2,410 reached the API v2 result submission stage. Therefore, the complete batch containing the stalled attachment upload was never submitted.
API v2 retries work correctly
During the same run, five API v2 result submission requests failed with:
All five requests were retried successfully.
There were:
The hang is therefore related to attachment upload through the API v1 client, rather than API v2 result submission.
Technical analysis
ApiV2Client.send_results()prepares all results before submitting the batch:During result preparation, attachments are uploaded through
_upload_attachment():The attachment request is made through the API v1 client:
The generated
AttachmentsApi.upload_attachment()method supports the_request_timeoutargument, butqase-python-commonsdoes not pass it.The configured API timeout is passed to API v2 result submission:
However, it is not passed to API v1 attachment uploads.
If the attachment connection remains open without returning data, the call blocks indefinitely. Since no exception is raised, the outer
send_with_retry()wrapper cannot perform a retry.The reporter thread remains active, and
complete_worker()waits indefinitely:Consequently, the xdist worker and the entire pytest process cannot finish.
There is also a related retry issue:
_upload_attachment()catches attachment exceptions internally and continues. Therefore, attachment failures that do raise an exception are not handled by the outer retry mechanism.Expected behavior
Attachment uploads should:
Suggested fix
Pass the configured timeout to the attachment API call:
Attachment uploads should also have a separate bounded retry mechanism.
After attachment retry exhaustion, the reporter should log the failure, omit the affected attachment, and continue submitting the result batch.
Acceptance criteria
QASE_TESTOPS_API_TIMEOUTis applied to API v1 attachment uploads