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:
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user