11import asyncio
22import logging
3+ import os
34import queue
45import json
56from typing import AsyncGenerator , Optional
1112from .protocol import StreamProtocol
1213from .last_value_cache import LastValueCache
1314
15+ DEFAULT_PUBLISH_QUEUE_MAXSIZE = 1
16+
17+
18+ def _publish_queue_maxsize () -> int :
19+ raw = os .environ .get ("LIVE_TRICKLE_PUBLISH_QUEUE_MAXSIZE" )
20+ if raw is None or raw .strip () == "" :
21+ return DEFAULT_PUBLISH_QUEUE_MAXSIZE
22+ try :
23+ value = int (raw )
24+ except ValueError :
25+ logging .warning (
26+ "Invalid LIVE_TRICKLE_PUBLISH_QUEUE_MAXSIZE=%r, using %s" ,
27+ raw ,
28+ DEFAULT_PUBLISH_QUEUE_MAXSIZE ,
29+ )
30+ return DEFAULT_PUBLISH_QUEUE_MAXSIZE
31+ return max (1 , value )
32+
33+
34+ def _put_drop_oldest (_queue : queue .Queue , item ) -> int :
35+ dropped = 0
36+ while True :
37+ try :
38+ _queue .put_nowait (item )
39+ return dropped
40+ except queue .Full :
41+ try :
42+ old = _queue .get_nowait ()
43+ except queue .Empty :
44+ continue
45+ dropped += 1
46+ del old
47+
48+
1449class TrickleProtocol (StreamProtocol ):
1550 def __init__ (self , subscribe_url : str , publish_url : str , control_url : Optional [str ] = None , events_url : Optional [str ] = None , input_width : Optional [int ] = DEFAULT_WIDTH , input_height : Optional [int ] = DEFAULT_HEIGHT , output_width : Optional [int ] = None , output_height : Optional [int ] = None ):
1651 self .subscribe_url = subscribe_url
1752 self .publish_url = publish_url
1853 self .control_url = control_url
1954 self .events_url = events_url
2055 self .subscribe_queue = queue .Queue [InputFrame ]()
21- self .publish_queue = queue .Queue [OutputFrame ]()
56+ self .publish_queue = queue .Queue [OutputFrame ](maxsize = _publish_queue_maxsize ())
57+ self .publish_queue_dropped = 0
2258 self .control_subscriber = None
2359 self .events_publisher = None
2460 self .subscribe_task = None
@@ -30,7 +66,8 @@ def __init__(self, subscribe_url: str, publish_url: str, control_url: Optional[s
3066
3167 async def start (self ):
3268 self .subscribe_queue = queue .Queue [InputFrame ]()
33- self .publish_queue = queue .Queue [OutputFrame ]()
69+ self .publish_queue = queue .Queue [OutputFrame ](maxsize = _publish_queue_maxsize ())
70+ self .publish_queue_dropped = 0
3471 metadata_cache = LastValueCache [dict ]() # to pass video metadata from decoder to encoder
3572 self .subscribe_task = asyncio .create_task (
3673 media .run_subscribe (self .subscribe_url , self .subscribe_queue .put , metadata_cache .put , self .emit_monitoring_event , self .input_width , self .input_height )
@@ -49,7 +86,7 @@ async def stop(self):
4986
5087 # send sentinel None values to stop the trickle tasks gracefully
5188 self .subscribe_queue .put (None )
52- self .publish_queue . put ( None )
89+ _put_drop_oldest ( self .publish_queue , None )
5390
5491 if self .control_subscriber :
5592 await self .control_subscriber .close ()
@@ -86,17 +123,30 @@ def dequeue_frame():
86123 # TEMP: Put audio immediately into the publish queue
87124 # TODO: Remove once there is ComfyUI audio support
88125 if isinstance (image , AudioFrame ):
89- publish_queue .put (AudioOutput ([image ]))
126+ self .publish_queue_dropped += _put_drop_oldest (
127+ publish_queue ,
128+ AudioOutput ([image ]),
129+ )
90130 continue
91131 yield image
92132
93133 async def egress_loop (self , output_frames : AsyncGenerator [OutputFrame , None ]):
94134 publish_queue = self .publish_queue
95135 def enqueue_bytes (frame : OutputFrame ):
96- publish_queue . put ( frame )
136+ return _put_drop_oldest ( publish_queue , frame )
97137
98138 async for frame in output_frames :
99- await asyncio .to_thread (enqueue_bytes , frame )
139+ try :
140+ dropped = await asyncio .to_thread (enqueue_bytes , frame )
141+ self .publish_queue_dropped += dropped
142+ if dropped and self .publish_queue_dropped % 100 == 1 :
143+ logging .warning (
144+ "Dropped stale publish frames dropped_total=%s queue_maxsize=%s" ,
145+ self .publish_queue_dropped ,
146+ publish_queue .maxsize ,
147+ )
148+ finally :
149+ del frame
100150
101151 async def emit_monitoring_event (self , event : dict , queue_event_type : str = "ai_stream_events" ):
102152 if not self .events_publisher :
@@ -134,4 +184,3 @@ async def control_loop(self, done: asyncio.Event) -> AsyncGenerator[dict, None]:
134184 except Exception :
135185 logging .error (f"Error in control loop" , exc_info = True )
136186 continue
137-
0 commit comments