Skip to content

Commit 2e67482

Browse files
authored
fix: racing condition on conduit when printing (#3308)
1 parent a275492 commit 2e67482

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

crates/chat-cli-ui/src/conduit.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::legacy_ui_util::ThemeSource;
1515
use crate::protocol::{
1616
Event,
1717
LegacyPassThroughOutput,
18+
MetaEvent,
1819
ToolCallRejection,
1920
ToolCallStart,
2021
};
@@ -53,6 +54,7 @@ impl ViewEnd {
5354
pub fn into_legacy_mode(
5455
self,
5556
theme_source: impl ThemeSource,
57+
prompt_ack: Option<std::sync::mpsc::Sender<()>>,
5658
mut stderr: std::io::Stderr,
5759
mut stdout: std::io::Stdout,
5860
) -> Result<(), ConduitError> {
@@ -153,7 +155,17 @@ impl ViewEnd {
153155
Event::ReasoningMessageEnd(_reasoning_message_end) => {},
154156
Event::ReasoningMessageChunk(_reasoning_message_chunk) => {},
155157
Event::ReasoningEnd(_reasoning_end) => {},
156-
Event::MetaEvent(_meta_event) => {},
158+
Event::MetaEvent(MetaEvent { meta_type, payload }) => {
159+
if meta_type.as_str() == "timing" {
160+
if let serde_json::Value::String(s) = payload {
161+
if s.as_str() == "prompt_user" {
162+
if let Some(prompt_ack) = prompt_ack.as_ref() {
163+
_ = prompt_ack.send(());
164+
}
165+
}
166+
}
167+
}
168+
},
157169
Event::ToolCallRejection(tool_call_rejection) => {
158170
let ToolCallRejection { reason, name, .. } = tool_call_rejection;
159171

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ pub struct ChatSession {
605605
inner: Option<ChatState>,
606606
ctrlc_rx: broadcast::Receiver<()>,
607607
wrap: Option<WrapMode>,
608+
prompt_ack_rx: std::sync::mpsc::Receiver<()>,
608609
}
609610

610611
impl ChatSession {
@@ -630,11 +631,12 @@ impl ChatSession {
630631
let should_send_structured_msg = should_send_structured_message(os);
631632
let (view_end, _byte_receiver, mut control_end_stderr, control_end_stdout) =
632633
get_legacy_conduits(should_send_structured_msg);
634+
let (prompt_ack_tx, prompt_ack_rx) = std::sync::mpsc::channel::<()>();
633635

634636
tokio::task::spawn_blocking(move || {
635637
let stderr = std::io::stderr();
636638
let stdout = std::io::stdout();
637-
if let Err(e) = view_end.into_legacy_mode(StyledText, stderr, stdout) {
639+
if let Err(e) = view_end.into_legacy_mode(StyledText, Some(prompt_ack_tx), stderr, stdout) {
638640
error!("Conduit view end legacy mode exited: {:?}", e);
639641
}
640642
});
@@ -739,6 +741,7 @@ impl ChatSession {
739741
inner: Some(ChatState::default()),
740742
ctrlc_rx,
741743
wrap,
744+
prompt_ack_rx,
742745
})
743746
}
744747

@@ -1942,6 +1945,25 @@ impl ChatSession {
19421945

19431946
execute!(self.stderr, StyledText::reset(), StyledText::reset_attributes())?;
19441947
let prompt = self.generate_tool_trust_prompt(os).await;
1948+
1949+
// Here we are signaling to the ui layer that the event loop wants to prompt user
1950+
// This is necessitated by the fact that what is actually writing to stderr or stdout is
1951+
// not on the same thread as what is prompting the user (in an ideal world they would be).
1952+
// As a bandaid fix (to hold us until we move to the new event loop where everything is in
1953+
// their rightful place), we are first signaling to the ui layer we are about to prompt
1954+
// users, and we are going to wait until the ui layer acknowledges.
1955+
// Note that this works because [std::sync::mpsc] preserves order between sending and
1956+
// receiving
1957+
self.stderr
1958+
.send(Event::MetaEvent(chat_cli_ui::protocol::MetaEvent {
1959+
meta_type: "timing".to_string(),
1960+
payload: serde_json::Value::String("prompt_user".to_string()),
1961+
}))
1962+
.map_err(|_e| ChatError::Custom("Error sending timing event for prompting user".into()))?;
1963+
if let Err(e) = self.prompt_ack_rx.recv_timeout(std::time::Duration::from_secs(10)) {
1964+
error!("Failed to receive user prompting acknowledgement from UI: {:?}", e);
1965+
}
1966+
19451967
let user_input = match self.read_user_input(&prompt, false) {
19461968
Some(input) => input,
19471969
None => return Ok(ChatState::Exit),

0 commit comments

Comments
 (0)