Skip to content

[Type 3] Audit thread-safety or other concurrency #1148

Open

Description

@septatrix

Summary

Currently it is not very clear if this library is thread-safe or not. According to psf/requests#2766 a single session per thread is preferred so should we create multiple Server instances for each thread?

Alternatively I would be very interested in an asyncio Version of this library using httpx or a similar library. This is generally the direction in which Python is now also moving and a lot easier to reason about than multithreaded concurrency. Furthermore it would lead to better code style as all the hidden IO which is currently performed would then be eliminated.

Activity

  1. dxdc commented on Apr 26, 2023

    @dxdc
    Contributor

    Ran into a similar issue, and as a result, I am quite sure the library is not thread safe.

    These kinds of calls were causing race conditions for me (and as a result, giving me PDFs with the wrong filenames). I decided to use a semaphore for these calls in the meantime.

    To reproduce, I was using this code with ThreadPoolExecutor, and specifying different filters (via pdf_request_options) for the same view. E.g.,

    server.views.populate_pdf(view, pdf_request_options)
    return view.pdf

    You can also see the problem in a different way with the latest version of the library and the logging module, e.g., I can get multiple such calls in a row because this blocking state is not shared across threads.

    2023-04-25 21:12:20,725 [DEBUG] None
    None
    2023-04-25 21:12:20,726 [DEBUG] None
    None
    2023-04-25 21:12:20,727 [DEBUG] None
    [21:12:20] Async request failed: retrying
    2023-04-25 21:12:20,727 [DEBUG] [21:12:20] Async request failed: retrying
    [21:12:20] Async request failed: retrying
    2023-04-25 21:12:20,727 [DEBUG] [21:12:20] Async request failed: retrying
    [21:12:20] Async request failed: retrying
    2023-04-25 21:12:20,727 [DEBUG] [21:12:20] Async request failed: retrying
    [21:12:20] Begin blocking request to https://tableauserver.com/api/3.14/sites/xxxxx-site-1/views/xxxxx-view-2/pdf
    2023-04-25 21:12:20,727 [DEBUG] [21:12:20] Begin blocking request to https://tableauserver.com/api/3.14/sites/xxxxx-site-1/views/xxxxx-view-2/pdf
    [21:12:20] Begin blocking request to https://tableauserver.com/api/3.14/sites/xxxxx-site-1/views/xxxxx-view-2/pdf
    2023-04-25 21:12:20,728 [DEBUG] [21:12:20] Begin blocking request to https://tableauserver.com/api/3.14/sites/xxxxx-site-1/views/xxxxx-view-2/pdf
    [21:12:20] Begin blocking request to https://tableauserver.com/api/3.14/sites/xxxxx-site-1/views/xxxxx-view-2/pdf
    2023-04-25 21:12:20,728 [DEBUG] [21:12:20] Begin blocking request to https://tableauserver.com/api/3.14/sites/xxxxx-site-1/views/xxxxx-view-2/pdf
    [21:12:21] Call finished
    2023-04-25 21:12:21,221 [DEBUG] [21:12:21] Call finished
    [21:12:21] Request complete
    2023-04-25 21:12:21,221 [DEBUG] [21:12:21] Request complete
    [21:12:21] Call finished
    2023-04-25 21:12:21,230 [DEBUG] [21:12:21] Call finished
    [21:12:21] Request complete
    2023-04-25 21:12:21,230 [DEBUG] [21:12:21] Request complete
    Response status: <Response [200]>
    2023-04-25 21:12:21,230 [DEBUG] Response status: <Response [200]>
    

    This problem appears to extend to the Endpoint class itself, as rewriting _blocking_request and send_request_while_show_progress_threaded alone did not fix the problem.

    def _blocking_request(self, method, url, parameters={}) -> Optional["Response"]:
    self.async_response = None
    response = None
    logger.debug("[{}] Begin blocking request to {}".format(datetime.timestamp(), url))
    try:
    response = method(url, **parameters)
    self.async_response = response
    logger.debug("[{}] Call finished".format(datetime.timestamp()))
    except Exception as e:
    logger.debug("Error making request to server: {}".format(e))
    self.async_response = e
    finally:
    if response and not self.async_response:
    logger.debug("Request response not saved")
    return None
    logger.debug("[{}] Request complete".format(datetime.timestamp()))
    return self.async_response

  2. github-actions commented on Aug 18, 2026

    @github-actions

    This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. If this is still relevant, please comment to keep it open.

  3. septatrix commented on Aug 23, 2026

    @septatrix
    Author

    Stil relevant

  4. dxdc commented on Aug 24, 2026

    @dxdc
    Contributor

    @septatrix I just wrote up PR #1871. httpx would be better, but is a larger refactor.

  5. jorwoods commented on Aug 24, 2026

    @jorwoods
    Contributor

    @dxdc Awesome! Unfortunately it seems to me like HTTPX may need to be avoided.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions