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)
Prerequisites
Description
ResponseContextManager.__init__in fasthttp.py shares a dict between two objects usingself.__dict__ = response.__dict__. This, combined withrequest_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:And in FastHttpSession.request():
Proposed fix
Copy
response.__dict__instead of sharing it:Related
Issue #3050
Issue #3207
Command line
locust -f locustfile.py --headless -u 1 -r 2 -t 60s
Locustfile contents
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)