From 32f5e74449ae2b3230d243ce2ed8a0b501a21b0a Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Tue, 26 May 2026 12:52:31 -0300 Subject: [PATCH] Skip lock acquisition in CrewTrainingHandler.load when file is missing (#5935) Every agent kickoff calls _use_trained_data, which calls CrewTrainingHandler(...).load(). Since #4827 wrapped load() in store_lock, that means every kickoff acquires the cross-process (Redis-backed when REDIS_URL is set) lock even on deployments that never train and have no trained-agents file on disk. Move the missing/empty-file short-circuit above store_lock so the lock is only acquired when there is actually a file to read. save() and the real read remain locked. --- .../src/crewai/utilities/file_handler.py | 19 +++++++---------- lib/crewai/tests/agents/test_agent.py | 15 +++++++++++++ .../tests/utilities/test_training_handler.py | 21 +++++++++++++++++++ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/lib/crewai/src/crewai/utilities/file_handler.py b/lib/crewai/src/crewai/utilities/file_handler.py index 437e267d8..13d903725 100644 --- a/lib/crewai/src/crewai/utilities/file_handler.py +++ b/lib/crewai/src/crewai/utilities/file_handler.py @@ -167,17 +167,12 @@ class PickleHandler: Returns: The data loaded from the file. """ - with store_lock(f"file:{os.path.realpath(self.file_path)}"): - if ( - not os.path.exists(self.file_path) - or os.path.getsize(self.file_path) == 0 - ): - return {} + if not os.path.exists(self.file_path): + return {} - with open(self.file_path, "rb") as file: - try: + with store_lock(f"file:{os.path.realpath(self.file_path)}"): + try: + with open(self.file_path, "rb") as file: return pickle.load(file) # noqa: S301 - except EOFError: - return {} - except Exception: - raise + except (FileNotFoundError, EOFError): + return {} diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index 7f96efea7..b549b3e3c 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -1083,6 +1083,21 @@ def test_agent_use_trained_data_honors_env_var(crew_training_handler, monkeypatc ) +def test_agent_use_trained_data_skips_load_when_file_missing(tmp_path, monkeypatch): + monkeypatch.setenv( + "CREWAI_TRAINED_AGENTS_FILE", str(tmp_path / "does_not_exist.pkl") + ) + agent = Agent(role="researcher", goal="test goal", backstory="test backstory") + + with patch( + "crewai.utilities.file_handler.store_lock", + side_effect=AssertionError("kickoff acquired lock with no trained-agents file"), + ): + result = agent._use_trained_data(task_prompt="What is 1 + 1?") + + assert result == "What is 1 + 1?" + + def test_agent_max_retry_limit(): agent = Agent( role="test role", diff --git a/lib/crewai/tests/utilities/test_training_handler.py b/lib/crewai/tests/utilities/test_training_handler.py index beca43b0a..2a8503c44 100644 --- a/lib/crewai/tests/utilities/test_training_handler.py +++ b/lib/crewai/tests/utilities/test_training_handler.py @@ -1,6 +1,7 @@ import os import tempfile import unittest +from unittest.mock import patch from crewai.utilities.training_handler import CrewTrainingHandler @@ -53,3 +54,23 @@ class InternalCrewTrainingHandler(unittest.TestCase): # Assert that the new agent and data are appended correctly data = self.handler.load() assert data[agent_id][train_iteration] == new_data + + def test_load_missing_file_does_not_acquire_lock(self): + handler = CrewTrainingHandler(self.temp_file.name + ".missing") + + with patch( + "crewai.utilities.file_handler.store_lock", + side_effect=AssertionError("load() acquired lock for missing file"), + ): + assert handler.load() == {} + + def test_load_acquires_lock_for_zero_size_file(self): + # Empty file mimics a concurrent save() mid-truncation (open "wb"). + assert os.path.getsize(self.temp_file.name) == 0 + + with patch( + "crewai.utilities.file_handler.store_lock", + side_effect=AssertionError("load() short-circuited on size 0"), + ): + with self.assertRaises(AssertionError): + self.handler.load()