fix: resolve tokio::sync::Mutex deadlock in recipe retry path (#7832)

Signed-off-by: Wilfried Roset <wilfriedroset@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Wilfried ROSET
2026-03-13 17:52:38 +01:00
committed by GitHub
parent b0bfb57ee0
commit 196ee3ccc8
2 changed files with 40 additions and 31 deletions
+37 -29
View File
@@ -1136,12 +1136,10 @@ impl Agent {
break; break;
} }
if let Some(final_output_tool) = self.final_output_tool.lock().await.as_ref() { {
if final_output_tool.final_output.is_some() { let guard = self.final_output_tool.lock().await;
let final_event = AgentEvent::Message( if let Some(ref output) = guard.as_ref().and_then(|fot| fot.final_output.clone()) {
Message::assistant().with_text(final_output_tool.final_output.clone().unwrap()) yield AgentEvent::Message(Message::assistant().with_text(output));
);
yield final_event;
break; break;
} }
} }
@@ -1600,42 +1598,52 @@ impl Agent {
} }
if no_tools_called { if no_tools_called {
if let Some(final_output_tool) = self.final_output_tool.lock().await.as_ref() { // Lock, extract state, drop guard before branching — handle_retry_logic
if final_output_tool.final_output.is_none() { // also locks final_output_tool and tokio::sync::Mutex is not reentrant.
let final_output = {
let guard = self.final_output_tool.lock().await;
guard.as_ref().map(|fot| fot.final_output.clone())
};
match final_output {
Some(None) => {
warn!("Final output tool has not been called yet. Continuing agent loop."); warn!("Final output tool has not been called yet. Continuing agent loop.");
let message = Message::user().with_text(FINAL_OUTPUT_CONTINUATION_MESSAGE); let message = Message::user().with_text(FINAL_OUTPUT_CONTINUATION_MESSAGE);
session_manager.add_message(&session_config.id, &message).await?; session_manager.add_message(&session_config.id, &message).await?;
conversation.push(message.clone()); conversation.push(message.clone());
yield AgentEvent::Message(message); yield AgentEvent::Message(message);
} else { }
let message = Message::assistant().with_text(final_output_tool.final_output.clone().unwrap()); Some(Some(output)) => {
let message = Message::assistant().with_text(output);
session_manager.add_message(&session_config.id, &message).await?; session_manager.add_message(&session_config.id, &message).await?;
conversation.push(message.clone()); conversation.push(message.clone());
yield AgentEvent::Message(message); yield AgentEvent::Message(message);
exit_chat = true; exit_chat = true;
} }
} else if did_recovery_compact_this_iteration { None if did_recovery_compact_this_iteration => {
// Avoid setting exit_chat; continue from last user message in the conversation // continue from last user message after recovery compact
} else { }
match self.handle_retry_logic(&mut conversation, &session_config, &initial_messages).await { None => {
Ok(should_retry) => { match self.handle_retry_logic(&mut conversation, &session_config, &initial_messages).await {
if should_retry { Ok(should_retry) => {
info!("Retry logic triggered, restarting agent loop"); if should_retry {
session_manager.replace_conversation(&session_config.id, &conversation).await?; info!("Retry logic triggered, restarting agent loop");
yield AgentEvent::HistoryReplaced(conversation.clone()); session_manager.replace_conversation(&session_config.id, &conversation).await?;
} else { yield AgentEvent::HistoryReplaced(conversation.clone());
} else {
exit_chat = true;
}
}
Err(e) => {
error!("Retry logic failed: {}", e);
yield AgentEvent::Message(
Message::assistant().with_text(
format!("Retry logic encountered an error: {}", e)
)
);
exit_chat = true; exit_chat = true;
} }
} }
Err(e) => {
error!("Retry logic failed: {}", e);
yield AgentEvent::Message(
Message::assistant().with_text(
format!("Retry logic encountered an error: {}", e)
)
);
exit_chat = true;
}
} }
} }
} }
+3 -2
View File
@@ -104,8 +104,9 @@ impl RetryManager {
*messages = Conversation::new_unvalidated(initial_messages.to_vec()); *messages = Conversation::new_unvalidated(initial_messages.to_vec());
info!("Reset message history to initial state for retry"); info!("Reset message history to initial state for retry");
if let Some(final_output_tool) = final_output_tool.lock().await.as_mut() { let mut guard = final_output_tool.lock().await;
final_output_tool.final_output = None; if let Some(fot) = guard.as_mut() {
fot.final_output = None;
info!("Cleared final output tool state for retry"); info!("Cleared final output tool state for retry");
} }
} }