Bug Description
In src/backend/base/langflow/memory.py, the aadd_messagetables function catches asyncio.CancelledError but completely fails to re-raise it. This is a severe asyncio anti-pattern.
When the scheduler or agent sends a cancellation signal (e.g., due to timeout, quota limits, or safe-guards), the code rolls back the database session but then either suppresses the signal via a recursive retry or translates it into a standard ValueError. In the Python asyncio ecosystem, silently swallowing a CancelledError breaks the task group cancellation chain. The event loop is misled into believing the task completed normally. Under high-concurrency, long-running agent interactions, this causes the underlying coroutines to escape the task manager's control, becoming "zombie coroutines" that spin aimlessly and eventually exhaust the host's CPU time slices and memory.
Reproduction
Because this is an underlying architectural state bug, it occurs under load when task cancellations are triggered (e.g., via build_public_tmp).
Code Snippet showing the exact vulnerability:
Location: src/backend/base/langflow/memory.py -> aadd_messagetables
# ...
except asyncio.CancelledError:
await session.rollback()
if retry_count >= max_retries:
await logger.awarning(...)
error_msg = "Add Message operation cancelled after multiple retries"
# BUG: Swallows CancelledError by raising a standard ValueError
raise ValueError(error_msg) from None
# BUG: Returns retry result, silencing the cancellation signal to the event loop
return await aadd_messagetables(messages, session, retry_count + 1)
except asyncio.CancelledError as e:
await logger.aexception(e)
error_msg = "Operation cancelled"
# BUG: Translates CancelledError to ValueError, breaking the async cancellation chain
raise ValueError(error_msg) from e
Expected behavior
When asyncio.CancelledError is caught, the function should perform its necessary cleanup (such as await session.rollback()) and then strictly re-raise the CancelledError (or let it propagate). It should not be wrapped into a ValueError or suppressed via returning a retry loop, ensuring the asyncio event loop can properly terminate the task hierarchy and garbage collect the resources.
Who can help?
@italojohnny
Operating System
macos
Langflow Version
main
Python Version
3.11
Screenshot
(Not applicable - Core asynchronous logic bug)
Flow File
(Not applicable)
Bug Description
In src/backend/base/langflow/memory.py, the aadd_messagetables function catches asyncio.CancelledError but completely fails to re-raise it. This is a severe asyncio anti-pattern.
When the scheduler or agent sends a cancellation signal (e.g., due to timeout, quota limits, or safe-guards), the code rolls back the database session but then either suppresses the signal via a recursive retry or translates it into a standard ValueError. In the Python asyncio ecosystem, silently swallowing a CancelledError breaks the task group cancellation chain. The event loop is misled into believing the task completed normally. Under high-concurrency, long-running agent interactions, this causes the underlying coroutines to escape the task manager's control, becoming "zombie coroutines" that spin aimlessly and eventually exhaust the host's CPU time slices and memory.
Reproduction
Because this is an underlying architectural state bug, it occurs under load when task cancellations are triggered (e.g., via build_public_tmp).
Code Snippet showing the exact vulnerability:
Location: src/backend/base/langflow/memory.py -> aadd_messagetables
Expected behavior
When asyncio.CancelledError is caught, the function should perform its necessary cleanup (such as await session.rollback()) and then strictly re-raise the CancelledError (or let it propagate). It should not be wrapped into a ValueError or suppressed via returning a retry loop, ensuring the asyncio event loop can properly terminate the task hierarchy and garbage collect the resources.
Who can help?
@italojohnny
Operating System
macos
Langflow Version
main
Python Version
3.11
Screenshot
(Not applicable - Core asynchronous logic bug)
Flow File
(Not applicable)