Skip to content

FastHttpUser crashes with TypeError: on_request() missing 4 required positional arguments on Python 3.13+ #3388

@antonkislinskiy

Description

@antonkislinskiy

Prerequisites

Description

ResponseContextManager.__init__ in fasthttp.py shares a dict between two objects using self.__dict__ = response.__dict__. This, combined with request_meta["response"] = response, creates a reference cycle. On Python 3.13+, the GC collects this cycle mid-request and clears request_meta to {}. When the with-block exits, events.request.fire(**{}) is called with no arguments, triggering the TypeError.

The bug is intermittent and more likely with large responses because gevent yields to the GC during I/O.

Root cause

ResponseContextManager.__init__ in locust/contrib/fasthttp.py:

def __init__(self, response, request_event, request_meta, catch_response):
    self.__dict__ = response.__dict__   # <- REFERENCE share, not a copy
    self.request_meta = request_meta    # <- stored in the shared dict

And in FastHttpSession.request():

request_meta = {
    ...
    "response": response,   # <- response object stored here
}
return ResponseContextManager(response, self.request_event, request_meta, catch_response)
# After this return, 'response' local variable goes out of scope

Proposed fix

Copy response.__dict__ instead of sharing it:

def __init__(self, response, request_event, request_meta, catch_response):
    self.__dict__.update(response.__dict__)  # copy, not reference share
    try:
        self._cached_content = response._cached_content
    except AttributeError:
        pass
    self._request_event = request_event
    self.request_meta = request_meta
    self._catch_response = catch_response

Related

Issue #3050
Issue #3207

Command line

locust -f locustfile.py --headless -u 1 -r 2 -t 60s

Locustfile contents

from locust import task, FastHttpUser

class POCUser(FastHttpUser):
    host = "https://api.openalex.org"

    @task
    def get_response(self):
        with self.client.get(
            "/works?search=machine+learning&per-page=1",
            name="works",
            catch_response=True,
        ) as response:
            response.json()  # large response -> gevent I/O -> GC fires-> TypeError

Python version

3.14.0 (also reproducible on 3.13.x per issues #3050 and #3207)

Locust version

2.43.3

Operating system

macOS (also Linux)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions