FrameProcessor: use finished_event for push_interruption_task_frame_and_wait

This commit is contained in:
Aleix Conchillo Flaqué
2025-10-10 20:27:50 -07:00
parent c33b81bb92
commit 234aae3091
2 changed files with 20 additions and 27 deletions

View File

@@ -53,6 +53,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fixed an issue that would cause wrong user/assistant context ordering when
using interruption strategies.
- Fixed an issue where the `SmallWebRTCRequest` dataclass in runner would scrub - Fixed an issue where the `SmallWebRTCRequest` dataclass in runner would scrub
arbitrary request data from client due to camelCase typing. This fixes data arbitrary request data from client due to camelCase typing. This fixes data
passthrough for JS clients where `APIRequest` is used. passthrough for JS clients where `APIRequest` is used.

View File

@@ -93,7 +93,7 @@ class FrameProcessorQueue(asyncio.PriorityQueue):
self.__high_counter = 0 self.__high_counter = 0
self.__low_counter = 0 self.__low_counter = 0
async def put(self, item: Tuple[Frame, FrameDirection, FrameCallback]): async def put(self, item: Tuple[Frame, FrameDirection, Optional[FrameCallback]]):
"""Put an item into the priority queue. """Put an item into the priority queue.
System frames (`SystemFrame`) have higher priority than any other System frames (`SystemFrame`) have higher priority than any other
@@ -228,11 +228,9 @@ class FrameProcessor(BaseObject):
# To interrupt a pipeline, we push an `InterruptionTaskFrame` upstream. # To interrupt a pipeline, we push an `InterruptionTaskFrame` upstream.
# Then we wait for the corresponding `InterruptionFrame` to travel from # Then we wait for the corresponding `InterruptionFrame` to travel from
# the start of the pipeline back to the processor that sent the # start to end of the pipeline. When it reaches the end we will be
# `InterruptionTaskFrame`. This wait is handled using the following # notified through the assigned event.
# event.
self._wait_for_interruption = False self._wait_for_interruption = False
self._wait_interruption_event = asyncio.Event()
# Frame processor events. # Frame processor events.
self._register_event_handler("on_before_process_frame", sync=True) self._register_event_handler("on_before_process_frame", sync=True)
@@ -567,10 +565,6 @@ class FrameProcessor(BaseObject):
if self._cancelling: if self._cancelling:
return return
# If we are waiting for an interruption we will bypass all queued system
# frames and we will process the frame right away. This is because a
# previous system frame might be waiting for the interruption frame and
# it's blocking the input task.
if self._wait_for_interruption and isinstance(frame, InterruptionFrame): if self._wait_for_interruption and isinstance(frame, InterruptionFrame):
await self.__process_frame(frame, direction, callback) await self.__process_frame(frame, direction, callback)
return return
@@ -661,31 +655,27 @@ class FrameProcessor(BaseObject):
await self._call_event_handler("on_after_push_frame", frame) await self._call_event_handler("on_after_push_frame", frame)
# If we are waiting for an interruption and we get an interruption, then
# we can unblock `push_interruption_task_frame_and_wait()`.
if self._wait_for_interruption and isinstance(frame, InterruptionFrame):
self._wait_interruption_event.set()
async def push_interruption_task_frame_and_wait(self): async def push_interruption_task_frame_and_wait(self):
"""Push an interruption task frame upstream and wait for the interruption. """Interrupt the pipeline and wait for the interruption to complete.
This function sends an `InterruptionTaskFrame` upstream with an
associated asyncio event. It then waits for the generated
`InterruptionFrame` to reach the end of the pipeline where the event
will be set.
This function sends an `InterruptionTaskFrame` upstream to the pipeline
task and waits to receive the corresponding `InterruptionFrame`. When
the function finishes it is guaranteed that the `InterruptionFrame` has
been pushed downstream.
""" """
self._wait_for_interruption = True self._wait_for_interruption = True
await self.push_frame(InterruptionTaskFrame(), FrameDirection.UPSTREAM) finished_event = asyncio.Event()
# Wait for an `InterruptionFrame` to come to this processor and be await self.push_frame(
# pushed. Take a look at `push_frame()` to see how we first push the InterruptionTaskFrame(finished_event=finished_event), FrameDirection.UPSTREAM
# `InterruptionFrame` and then we set the event in order to maintain )
# frame ordering.
await self._wait_interruption_event.wait()
# Clean the event. # Wait for the event to be set. This event is set when the
self._wait_interruption_event.clear() # `InterruptionFrame` pushed by the pipeline task reaches the end of the
# pipeline.
await finished_event.wait()
self._wait_for_interruption = False self._wait_for_interruption = False