@@ -39,7 +39,6 @@ class ConversationState:
3939 conversation_id: Unique identifier for this conversation.
4040 current_turn: Last completed turn number (0 = not started).
4141 pending_client_turn: Turn number of in-flight client turn (None if idle).
42- turn_complete_event: Threading event to signal turn completion.
4342 expected_client_turns: Expected number of client turns (for completion tracking).
4443 issued_client_turns: Count of client turns issued.
4544 completed_client_turns: Count of client turns with responses.
@@ -54,7 +53,6 @@ class ConversationState:
5453 conversation_id : str
5554 current_turn : int = 0
5655 pending_client_turn : int | None = None
57- turn_complete_event : threading .Event = field (default_factory = threading .Event )
5856 expected_client_turns : int | None = None
5957 issued_client_turns : int = 0
6058 completed_client_turns : int = 0
@@ -106,8 +104,6 @@ def add_assistant_turn(self, content: str | None = None):
106104 self .completed_client_turns += 1
107105 self .condition .notify_all ()
108106
109- self .turn_complete_event .set ()
110-
111107 if self .is_complete ():
112108 if self .failed_client_turns > 0 :
113109 logger .info (
@@ -145,8 +141,6 @@ def mark_turn_failed(self):
145141 )
146142 self .condition .notify_all ()
147143
148- self .turn_complete_event .set ()
149-
150144 if self .is_complete ():
151145 logger .info (
152146 f"Conversation { self .conversation_id } completed with failures: "
@@ -165,14 +159,11 @@ def is_complete(self) -> bool:
165159 return False # Unknown completion, can't determine
166160 return self .completed_client_turns >= self .expected_client_turns
167161
168- def is_ready_for_turn (self , turn : int ) -> bool :
169- """Check if ready to issue this turn (previous turn must be complete).
170-
171- Args:
172- turn: Turn number to check (unused; sequencing is based on completion counts).
162+ def is_ready_for_turn (self ) -> bool :
163+ """Check if the previous turn has completed and the next may be issued.
173164
174165 Returns:
175- True if ready to issue this turn, False otherwise.
166+ True if ready to issue the next turn, False otherwise.
176167 """
177168 return (
178169 self .pending_client_turn is None
@@ -195,17 +186,14 @@ class ConversationManager:
195186 Each ConversationState carries its own Condition so that state changes
196187 (turn issued / turn complete) only wake the single pipeline thread waiting
197188 on that conversation, not all pipeline threads across all conversations.
198- A separate _created_condition handles the narrow case where a pipeline
199- thread calls wait_for_turn_issued before get_or_create has run .
189+ All conversation states are pre-created by the scheduler before pipeline
190+ threads start, so wait_for_turn_issued never races against get_or_create .
200191 """
201192
202193 def __init__ (self ):
203194 """Initialize conversation manager with empty state."""
204195 self ._conversations : dict [str , ConversationState ] = {}
205196 self ._lock = threading .Lock ()
206- # Fired whenever a new conversation state is registered.
207- # Only needed by wait_for_turn_issued when state is None on entry.
208- self ._created_condition = threading .Condition (self ._lock )
209197
210198 def get_or_create (
211199 self ,
@@ -233,15 +221,13 @@ def get_or_create(
233221 conversation_id = conversation_id ,
234222 current_turn = 0 ,
235223 pending_client_turn = None ,
236- turn_complete_event = threading .Event (),
237224 expected_client_turns = expected_client_turns ,
238225 issued_client_turns = 0 ,
239226 completed_client_turns = 0 ,
240227 failed_client_turns = 0 ,
241228 message_history = initial_history ,
242229 )
243230 self ._conversations [conversation_id ] = state
244- self ._created_condition .notify_all ()
245231 return self ._conversations [conversation_id ]
246232
247233 def wait_for_turn_ready (
@@ -272,11 +258,11 @@ def wait_for_turn_ready(
272258
273259 deadline = None if timeout is None else time .monotonic () + timeout
274260 with state .condition :
275- while not state .is_ready_for_turn (turn ):
261+ while not state .is_ready_for_turn ():
276262 if deadline is not None :
277263 remaining_timeout = deadline - time .monotonic ()
278264 if remaining_timeout <= 0 :
279- return state .is_ready_for_turn (turn )
265+ return state .is_ready_for_turn ()
280266 remaining_timeout = max (MIN_TIMEOUT_SECONDS , remaining_timeout )
281267 else :
282268 remaining_timeout = None
@@ -298,34 +284,24 @@ def wait_for_turn_issued(
298284 so the pipeline would enqueue subsequent turns before the load generator has
299285 registered the current one as in-flight.
300286
287+ All conversation states are pre-created by the scheduler before pipeline
288+ threads start, so this method can look up the state directly without waiting
289+ for it to be registered.
290+
301291 Args:
302292 conversation_id: Conversation to wait for.
303293 min_issued: Minimum number of issued turns to wait for.
304294 timeout: Maximum seconds to wait (None = infinite).
305295
306296 Returns:
307297 True if condition met, False if timeout.
298+
299+ Raises:
300+ KeyError: If conversation_id not found (programming error — state must be
301+ pre-created by the scheduler before pipeline threads are spawned).
308302 """
303+ state = self ._conversations [conversation_id ]
309304 deadline = None if timeout is None else time .monotonic () + timeout
310-
311- # Phase 1: wait until the conversation state exists (guarded by
312- # _created_condition so only the threads waiting on *this* conversation
313- # being created are woken — not all pipeline threads).
314- with self ._created_condition :
315- state = self ._conversations .get (conversation_id )
316- while state is None :
317- if deadline is not None :
318- remaining = deadline - time .monotonic ()
319- if remaining <= 0 :
320- return False
321- self ._created_condition .wait (
322- timeout = max (MIN_TIMEOUT_SECONDS , remaining )
323- )
324- else :
325- self ._created_condition .wait ()
326- state = self ._conversations .get (conversation_id )
327-
328- # Phase 2: wait for the issued counter using the per-conversation Condition.
329305 with state .condition :
330306 while state .issued_client_turns < min_issued :
331307 if deadline is not None :
@@ -384,8 +360,7 @@ def mark_turn_complete(
384360 state = self ._conversations .get (conversation_id )
385361 if state is None :
386362 raise KeyError (f"Conversation { conversation_id } not initialized" )
387- with state .condition :
388- state .add_assistant_turn (response if store_in_history else None )
363+ state .add_assistant_turn (response if store_in_history else None )
389364
390365 def mark_turn_failed (self , conversation_id : str ):
391366 """Mark that assistant response failed (error/timeout).
0 commit comments