Skip to content

Attachment upload can block pytest indefinitely because the configured API timeout is not applied #513

Description

@ArtyrKa1300

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions